? ? ? ?上一篇文章我們講解了線程間的互斥技術,使用關鍵字synchronize來實現線程間的互斥技術。根據不同的業務情況,我們可以選擇某一種互斥的方法來實現線程間的互斥調用。例如:自定義對象實現互斥(synchronize("自定義對象"){}),同一個類實例對象(synchronize(this){}),類的字節碼對象(synchronize(字節碼對象){})。這三種方法均可實現線程間的互斥,我們實際運用中靈活使用。
下面進入今天的正題:線程--線程間的同步通信技術;我們還是以傳智播客中的代碼為例來講解,子線程運行10次,主線程運行10次,如此交替運行50次。
首先看不適用同步技術時的問題代碼:
public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for(int i=1;i<=50;i++){for(int j=1;j<=10;j++){System.out.println("子線程:"+Thread.currentThread().getName()+"運行第"+i+"次,重復第"+j+"次");}}}}).start();for(int i=1;i<=50;i++){for(int j=1;j<=10;j++){System.out.println("主線程:"+Thread.currentThread().getName()+"運行第"+i+"次,重復第"+j+"次");}}
}
上面代碼的運行結果可知,子線程與主線程間是雜亂無章的運行,顯然不能滿足我的要求。那我們來稍作調整。代碼如下:
public class ThreadSynchronizedTechnology {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for(int i=1;i<=50;i++){
//使用類的字節碼作為互斥對象synchronized(ThreadSynchronizedTechnology.class){for(int j=1;j<=10;j++){System.out.println("子線程:"+Thread.currentThread().getName()+"運行第"+i+"次,重復第"+j+"次");}}}}}).start();for(int i=1;i<=50;i++){synchronized(ThreadSynchronizedTechnology.class){for(int j=1;j<=10;j++){System.out.println("主線程:"+Thread.currentThread().getName()+"運行第"+i+"次,重復第"+j+"次");}}}}}
上面的代碼我們使用類的字節碼作為互斥對象,顯然程序不再是雜亂無章的運行,子線程與主線程都能完整的運行完,但是沒有實現我們要求的交替運行,不要急我接著調整,我非常喜歡張孝祥老師循序漸進的講課方式(我不是托,是發自內心的),下面我們接著調整:
public class ThreadSynchronizedTechnology {public static void main(String[] args) {final Songzl song = new Songzl();new Thread(new Runnable() {@Overridepublic void run() {for(int i=1;i<=50;i++){song.sub(i);}}}).start();for(int i=1;i<=50;i++){song.main(i);}}
}
class Songzl{//子線程運行的方法public void sub(int i){synchronized(Songzl.class){for(int j=1;j<=10;j++){System.out.println("子線程:"+Thread.currentThread().getName()+"運行第"+i+"次,重復第"+j+"次");}}}//主線程運行的方法public void main(int i){synchronized(Songzl.class){for(int j=1;j<=10;j++){System.out.println("主線程:"+Thread.currentThread().getName()+"運行第"+i+"次,重復第"+j+"次");}}}
}
打印結果依然不是交替運行,我調成這樣是為了體現編程的面向對象思想,將相關聯的方法封裝到同一個類中,方便代碼運維。我們接著調整代碼:
public class ThreadSynchronizedTechnology {public static void main(String[] args) {final Songzl song = new Songzl();new Thread(new Runnable() {@Overridepublic void run() {for(int i=1;i<=50;i++){song.sub(i);}}}).start();for(int i=1;i<=50;i++){song.main(i);}}
}
class Songzl{//實現線程同步通信,互斥的方法共享次變量private boolean jiaoti = true;//子線程運行的方法:同一個類中方法互斥,類似與synchronized(this){}public synchronized void sub(int i) {if(!jiaoti){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}for(int j=1;j<=10;j++){System.out.println("子線程:"+Thread.currentThread().getName()+"運行第"+i+"次,重復第"+j+"次");}jiaoti = false;this.notify();}//主線程運行的方法:同一個類中方法互斥,類似與synchronized(this){}public synchronized void main(int i){if(jiaoti){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}for(int j=1;j<=10;j++){System.out.println("主線程:"+Thread.currentThread().getName()+"運行第"+i+"次,重復第"+j+"次");}jiaoti = true;this.notify();}
}
打印結果可見,已經實現子線程和主線程有條不紊的交替運行,線程間既能互斥,同時又可以相互同步通信運行;線程的互斥是通過synchronized實現的,線程間的同步通信是兩個線程間共同持有一個變量來實現的。但是線程有一個“假喚醒”的情況,雖然發生率低,但是我們不能忽略,繼續調整代碼:
public class ThreadSynchronizedTechnology {public static void main(String[] args) {final Songzl song = new Songzl();new Thread(new Runnable() {@Overridepublic void run() {for(int i=1;i<=50;i++){song.sub(i);}}}).start();for(int i=1;i<=50;i++){song.main(i);}}
}
class Songzl{//實現線程同步通信,互斥的方法共享次變量private boolean jiaoti = true;//子線程運行的方法:同一個類中方法互斥,類似與synchronized(this){}public synchronized void sub(int i) {//避免線程的假喚醒while(!jiaoti){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}for(int j=1;j<=10;j++){System.out.println("子線程:"+Thread.currentThread().getName()+"運行第"+i+"次,重復第"+j+"次");}jiaoti = false;this.notify();}//主線程運行的方法:同一個類中方法互斥,類似與synchronized(this){}public synchronized void main(int i){//避免線程的假喚醒while(jiaoti){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}for(int j=1;j<=10;j++){System.out.println("主線程:"+Thread.currentThread().getName()+"運行第"+i+"次,重復第"+j+"次");}jiaoti = true;this.notify();}
}
我們使用while循環來避免這種假喚醒的情況,當CPU任性的給與不該執行的線程、或者線程神經病的自己喚醒自己,我們可以使用while循環來避免上述情況。好了到此為止,代碼已經完全滿足我們的需求了。通過上面代碼的循序漸進,我們很容易理解線程間的同步與互斥技術。
總結:線程之間的制約關系?
?????? 當線程并發執行時,由于資源共享和線程協作,使用線程之間會存在以下兩種制約關系。
???? (1)間接相互制約。一個系統中的多個線程必然要共享某種系統資源,如共享CPU,共享I/O設備,所謂間接相互制約即源于這種資源共享,打印機就是最好的例子,線程A在使用打印機時,其它線程都要等待。
???? (2)直接相互制約。這種制約主要是因為線程之間的合作,如有線程A將計算結果提供給線程B作進一步處理,那么線程B在線程A將數據送達之前都將處于阻塞狀態。
?????? 間接相互制約可以稱為互斥,直接相互制約可以稱為同步,對于互斥可以這樣理解,線程A和線程B互斥訪問某個資源則它們之間就會產個順序問題——要么線程A等待線程B操作完畢,要么線程B等待線程操作完畢,這其實就是線程的同步了。因此同步包括互斥,互斥其實是一種特殊的同步。
?