Java高并发编程:定时器、互斥、同步通信技术
筆記摘要
這里分析了多線程的一些細節(jié)問題,并介紹了傳統(tǒng)定時器的創(chuàng)建,同時實現(xiàn)了根據(jù)自己的調度計劃的自定義定時器,對于傳統(tǒng)互斥技術中發(fā)現(xiàn)的內部類問題,進行了分析,最后對于同步通信技術,是重點,分析了如何處理類似的問題,如何設計能夠更加清晰簡單,體現(xiàn)了高內聚和程序的健壯性
1. 多線程的幾個知識點
1.1 為何使用實現(xiàn)Runnable的方式創(chuàng)建線程更普遍?
new Runnable()的方式,更加體現(xiàn)面向對象的思想
通過 new Thread()創(chuàng)建一個線程,代碼封裝在runnable對象中,代碼和線程獨立分開來,但最終將它們組合在一起。
Thread thread = new Thread(new Runnable() {@Overridepublic void run() {// code} });1.2 獲取線程名的時候,應使用currentThread().getName()方式
因為this.getName()方式,只有在當前對象是Thread的時候可以,當我們使用runnable方式時,this代表的是runnable對象,它僅是要運行代碼的宿主,而不是線程,當然編譯也無法通過(沒有此方法)。
1.3 創(chuàng)建線程的兩種傳統(tǒng)方式的run方法執(zhí)行問題
查看Thread類的run()方法的源代碼,可以看到其實這兩種方式都是在調用Thread對象的run()方法,如果Thread類的run()方法沒有被覆蓋,并且為該Thread對象設置了一個Runnable對象,該run方法會調用Runnable對象的run()方法。
下面是Thread類的run()方法的源碼,可以看到runnable對象也是調用了Thread的run()方法。
當runnable對象不為null,并且有自己的run()方法,則執(zhí)行自己的,如果target為null,則Thread類的run()方法什么也不執(zhí)行,所以我們在創(chuàng)建線程的時候不直接創(chuàng)建Thread對象,而是創(chuàng)建其子類對象,目的是為了復寫run方法,把要執(zhí)行的代碼放進去,否則該線程沒有意義
Private Runnable target; public void run() { if (target != null) { target.run(); } }1.4 多線程的運行
問題1:如果在Thread子類覆蓋的run()方法中編寫了運行代碼,也為Thread子類對象傳遞了一個Runnable對象,那么,線程運行時的執(zhí)行代碼?
是子類的run方法的代碼?還是Runnable對象的run()方法的代碼?如:
// 匿名內部類的方式實現(xiàn)的一個子類,并在構造方法中傳入了一個Runnable對象 new Thread(new Runnable() {public void run() {// code} }) {@Overridepublic void run() {super.run();// code} }.start();會運行子類的run方法。因為當某個對象調用start方法之后,就會去找自己對象的run方法,如果沒有就會找父類的run方法,父類的run方法會找runnable運行。
其實就是多態(tài)的一種體現(xiàn),覆蓋了父類的就執(zhí)行自己的,沒有覆蓋就去找父類的執(zhí)行
問題2:多線程機制是否會提高程序的運行效率?
多線程機制并不會提高程序的運行效率,反而性能更低,因為CPU需要在不同線程之間頻繁切換。
1.5 多線程下載的誤解?
多線程下載其實是搶了服務器的帶寬,一個線程代表一個用戶,每個線程分配的帶寬是相等的,開啟的線程多,就會分配更多的帶寬,是在搶資源,而不是自己更快。
2. 傳統(tǒng)定時器:Timer類
定時器有兩種:一種在指定時間只執(zhí)行一次,另一種先在指定時間執(zhí)行一次,之后每隔指定時間循環(huán)執(zhí)行。
該示例說明了定時器的創(chuàng)建方式,并通過自定義定時器的方式,在一個定時器內部通過不同切換秒數(shù),來實現(xiàn)在不同的間隔時間實現(xiàn)循環(huán)爆炸,另外還通過兩個類之間的互相實現(xiàn)相同的效果
import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TraditionalTimerTest { private static int count = 0; public static void main(String[] args) { //創(chuàng)建一個定時器并調度任務 /* new Timer().schedule(new TimerTask() { @Override public void run() { System.out.println("bombing!"); } }, 3000);//3秒以后爆炸 */ //}, 10000,3000); //10秒以后爆炸,以后每隔3秒炸一次 //自定義一個定時器 class MyTimerTask extends TimerTask{ @Override public void run() { count = (count+1)%2; //在0和1之間切換 System.out.println("bombing!"); new Timer().schedule(/*new TimerTask() { @Override public void run() { System.out.println("bombing!"); } }*/new MyTimerTask(),2000+2000*count); //實現(xiàn)循環(huán),不能用this,因為是匿名,所以只能執(zhí)行一次, //就像炸彈一樣,炸完后就沒有了,必須布置新的炸彈 //所以創(chuàng)建一個類,每次在最后new一個新的炸彈 } } //開啟定時器,每隔2秒調用一次MyTimerTask new Timer().schedule(new MyTimerTask(), 2000); //為了觀察定時器任務的執(zhí)行:每隔1秒打印一次當前秒數(shù) while(true){ System.out.println(new Date().getSeconds()); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }使用互相調用的方式實現(xiàn)間隔2秒和4秒的連環(huán)爆炸
import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class MyTraditionalTimer { public static void main(String[] args){ new Timer().schedule(new MyTimerTask(), 4000); //打印當前秒數(shù) while(true){ System.out.println(new Date().getSeconds()); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } //每隔2秒調用MyTimerTask2 class MyTimerTask extends TimerTask{ @Override public void run() { // TODO Auto-generated method stub System.out.println("boomping!!!"); new Timer().schedule(new MyTimerTask2(),2000); } } //每隔4秒調用MyTimerTask2 class MyTimerTask2 extends TimerTask{ @Override public void run() { // TODO Auto-generated method stub System.out.println("boomping!!!"); new Timer().schedule(new MyTimerTask(), 4000); } }3. 調度框架:quarts
Quartz是一個開源的作業(yè)調度框架,它完全由Java寫成,并設計用于J2SE和J2EE應用中。它提供了巨大的靈活性而不犧牲簡單性。你能夠用它來為執(zhí)行一個作業(yè)而創(chuàng)建簡單的或復雜的調度。它有很多特征,如:數(shù)據(jù)庫支持,集群,插件,EJB作業(yè)預構建,JavaMail及其它,支持cron-like表達式等等。
對于定時器中不能很好實現(xiàn)的需求,我們可以想到quarts,這里并沒有介紹其使用方式,以后開發(fā)用到,能夠記起,去查資料
4. 傳統(tǒng)線程互斥技術
發(fā)現(xiàn)的問題:在主函數(shù)內部不能創(chuàng)建內部類的實例對象
內部類的一個重要特點就是可以訪問外部類的成員變量,成員變量是對象身上的,對象創(chuàng)建完后,成員變量才分配空間,所以內部類訪問外部類的成員變量需要外部類的實例對象。而靜態(tài)方法先存在,所以不可以。
解決方式:
可以將內部類定義為靜態(tài)的,或者將創(chuàng)建內部類的實例對象的語句封裝在一個外部類的成員方法中,這里定義了一個init方法,因為方法調用需要對象,這個對象就是將來調用該方法的對象
示例說明:
本示例主要是對上面的問題進行了展示,另外對過去的互斥技術中的鎖所使用的對象進行了分析
public class TraditionalThreadSychronized { public static void main(String[] args) { //無法創(chuàng)建,必須關聯(lián)一個外部類的實例對象,可以定義一個方法, //或者將外部類定義為靜態(tài) //final Outputer outputer = new Outputer(); //編譯錯誤 new TraditionalThreadSychronized().init(); } //方法需要對象調用,所以就關聯(lián)了一個外部類的對象 private void init() { final Outputer outputer = new Outputer(); new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } outputer.output("11111"); } } }).start(); new Thread(new Runnable() { @Override public void run() { while (true) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } // outputer.output("are you happy?"); outputer.output2("22222"); } } }).start(); } static class Outputer { // public void output(String name) { // String lock = ""; int len = name.length(); // synchronized(lock){ 使用同一把鎖,任意對象都可以 // synchronized(this){ 同步函數(shù)使用的是鎖是this,即outputer對象 synchronized (Outputer.class) { // 靜態(tài)方法的鎖只能是class字節(jié)碼對象 for (int i = 0; i < len; i++) { System.out.print(name.charAt(i)); } System.out.println(); } } // 同步函數(shù)使用的是鎖是this /* public synchronized void output2(String name){ int len = name.length(); for(int i=0;i<len;i++){ System.out.print(name.charAt(i)); } System.out.println(); } } */ //定義同步的靜態(tài)方法 public static synchronized void output2(String name) { int len = name.length(); for (int i = 0; i < len; i++) { System.out.print(name.charAt(i)); } System.out.println(); } } }5. 傳統(tǒng)線程同步通信技術
這里通過一道面試題進行講解
需求:子線程循環(huán)10次,接著主線程循環(huán)100次,接著又回到子線程循環(huán)10次,接著再回到主線程又循環(huán)100次,如此循環(huán)50次
1、思路:
使用面向對象的方式思考,子線程的任務是循環(huán)10次,子線程的任務是循環(huán)100次,所以可以將它們各自的任務封裝起來,在封裝內部實現(xiàn)各自的同步(鎖是放在代表要操作的資源的類的內部方法中),最后別的對象來調用,循環(huán)50次即可
2、Eclipse小技巧:
這里打印結果過長,我們可以使用eclipse將打印結果輸出到文件中:
Run As → Run Configurations→ Common → File前打勾 → 指定路徑
3、鎖對象的定義
兩個線程執(zhí)行的代碼片段要實現(xiàn)同步互斥的效果,它們必須用同一個鎖對象,鎖是放在代表要操作的資源的類的內部方法中,而不是在線程代碼中。
4、實現(xiàn)按指定的順序執(zhí)行
需要用到wait,Notify,當輪到自己要執(zhí)行的時候,讓對象去喚醒自己,可以定義一個標識,來決定誰可以執(zhí)行
5、wait方法必須放在synchronized的里面,而且調用它的對象必須和synchronized的對象是同一個。
6、While比if更嚴謹,因為會循環(huán)判斷執(zhí)行條件,所以可以防止偽喚醒,(并不是期望的對象來喚醒自己)。
經(jīng)驗:要用到共同數(shù)據(jù)(包括同步鎖)或共同算法的若干個方法應該歸于同一個類上,在這個類的內部去管理各個方法的狀態(tài),這種設計正好體現(xiàn)了高類聚和程序的健壯性
public class TraditionalThreadCommunication { public static void main(String[] args){ //獲取一個業(yè)務對象 final Business business = new Business(); //子線程 new Thread(new Runnable(){ @Override public void run() { for(int i=1;i<=50;i++){ /*for(int j=0;j<10;j++){ System.out.println("sub thread sequence of"+i+",loop of "+j); }*/ business.sub(i); } } }).start(); //主線程 for(int i=1;i<=50;i++){ /* for(int j=0;j<100;j++){ System.out.println("main thread sequence of"+i+", loop of "+j); } */ business.main(i); } } } //定義一個業(yè)務類 class Business{ //定義一個boolean型變量來決定子線程和主線程的執(zhí)行權 private boolean bShouldSub = true; //子線程 public synchronized void sub(int i){//把同步的鎖放在資源身上 //不該子線程執(zhí)行,等待 if(!bShouldSub){ try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } for(int j=1;j<=10;j++){ System.out.println("sub thread sequence of"+i+",loop of "+j); } bShouldSub = false; this.notify(); //喚醒主線程 } //主線程 public synchronized void main(int i){ //若是子線程執(zhí)行,主線程等待 if(bShouldSub){ try { this.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } for(int j=1;j<=100;j++){ System.out.println("main thread sequence of"+i+", loop of "+j); } bShouldSub = true; this.notify(); //喚醒子線程 } }總結
以上是生活随笔為你收集整理的Java高并发编程:定时器、互斥、同步通信技术的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java高并发编程:同步工具类
- 下一篇: Java高并发编程:线程范围内共享数据