Java多线程间的通信
Java多線程間的通信
?
Java還提供了一種線程間通信的機制,這種通信通什么實現?
wait,notify等機制???
或使用pipeInputStream和pipeOutputStream
?
?1. 線程的幾種狀態
線程有四種狀態,任何一個線程肯定處于這四種狀態中的一種:
1) 產生(New):線程對象已經產生,但尚未被啟動,所以無法執行。如通過new產生了一個線程對象后沒對它調用start()函數之前。
2) 可執行(Runnable):每個支持多線程的系統都有一個排程器,排程器會從線程池中選擇一個線程并啟動它。當一個線程處于可執行狀態時,表示它可能正處于線程池中等待排排程器啟動它;也可能它已正在執行。如執行了一個線程對象的start()方法后,線程就處于可執行狀態,但顯而易見的是此時線程不一定正在執行中。
3) 死亡(Dead):當一個線程正常結束,它便處于死亡狀態。如一個線程的run()函數執行完畢后線程就進入死亡狀態。
4) 停滯(Blocked):當一個線程處于停滯狀態時,系統排程器就會忽略它,不對它進行排程。
?
(另外一篇請看Java線程及多線程技術及應用??http://wenku.baidu.com/view/f214392fb4daa58da0114a32.html?from=rec&pos=1&weight=5&lastweight=3&count=4)
一. 實現多線程
1. 虛假的多線程
例1:
publicclassTestThread?
{?
inti=0, j=0;?
publicvoidgo(intflag){?
while(true){?
try{?
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);?
}?
catch(java/lang/InterruptedException.java.html"target="_blank">
InterruptedException
e){?
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");?
}?
if(flag==0)?
i++;?
java/lang/System.java.html" target="_blank">
System
.out.println("i=" + i);?
}?
else{?
j++;?
java/lang/System.java.html" target="_blank">
System
.out.println("j=" + j);?
}?
}?
}?
publicstaticvoidmain(java/lang/String.java.html"target="_blank">
String
[] args){?
newTestThread().Go(0);?
newTestThread().go(1);?
}?
}?
上面程序的運行結果為:
i=1
i=2
i=3
。。。
結果將一直打印出I的值。我們的意圖是當在while循環中調用sleep()時,另一個線程就將起動,打印出j的值,但結果卻并不是這樣。關于sleep()為什么不會出現我們預想的結果,在下面將講到。
2. 實現多線程
通過繼承class Thread或實現Runnable接口,我們可以實現多線程
2.1 通過繼承class Thread實現多線程
class Thread中有兩個最重要的函數run()和start()。
1) run()函數必須進行覆寫,把要在多個線程中并行處理的代碼放到這個函數中。
2) 雖然run()函數實現了多個線程的并行處理,但我們不能直接調用run()函數,而是通過調用start()函數來調用run()函數。在調用start()的時候,start()函數會首先進行與多線程相關的初始化(這也是為什么不能直接調用run()函數的原因),然后再調用run()函數。
例2:
publicclassTestThread extendsjava/lang/Thread.java.html"target="_blank">
Thread
{?
privatestaticintthreadCount = 0;?
privateintthreadNum = ++threadCount;?
privateinti = 5;?
publicvoidrun(){?
while(true){?
try{?
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100); ?
}?
catch(java/lang/InterruptedException.java.html"target="_blank">
InterruptedException
e){?
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");?
}?
java/lang/System.java.html" target="_blank">
System
.out.println("Thread " + threadNum + " = " + i);?
if(--i==0) return;?
}?
}?
>publicstaticvoidmain(java/lang/String.java.html" target="_blank">
String
[] args){?
for(inti=0; i<5; i++)?
newTestThread().start();?
}?
}?
運行結果為:
Thread 1 = 5
Thread 2 = 5
Thread 3 = 5
Thread 4 = 5
Thread 5 = 5
Thread 1 = 4
Thread 2 = 4
Thread 3 = 4
Thread 4 = 4
Thread 1 = 3
Thread 2 = 3
Thread 5 = 4
Thread 3 = 3
Thread 4 = 3
Thread 1 = 2
Thread 2 = 2
Thread 5 = 3
Thread 3 = 2
Thread 4 = 2
Thread 1 = 1
Thread 2 = 1
Thread 5 = 2
Thread 3 = 1
Thread 4 = 1
Thread 5 = 1
從結果可見,例2能實現多線程的并行處理。
**:在上面的例子中,我們只用new產生Thread對象,并沒有用reference來記錄所產生的Thread對象。根據垃圾回收機制,當一個對象沒有被reference引用時,它將被回收。但是垃圾回收機制對Thread對象“不成立”。因為每一個Thread都會進行注冊動作,所以即使我們在產生Thread對象時沒有指定一個reference指向這個對象,實際上也會在某個地方有個指向該對象的reference,所以垃圾回收器無法回收它們。
3) 通過Thread的子類產生的線程對象是不同對象的線程
classTestSynchronized extendsjava/lang/Thread.java.html"target="_blank">
Thread
{?
publicTestSynchronized(java/lang/String.java.html"target="_blank">
String
name){?
super(name);?
}?
publicsynchronizedstaticvoidprt(){?
for(inti=10; i<20; i++){?
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i);?
try{?
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);?
}?
catch(java/lang/InterruptedException.java.html"target="_blank">
InterruptedException
e){?
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");?
}?
}?
}?
publicsynchronizedvoidrun(){?
for(inti=0; i<3; i++){?
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i);?
try{?
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);?
}?
catch(java/lang/InterruptedException.java.html"target="_blank">
InterruptedException
e){?
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");?
}?
}?
}?
}?
publicclassTestThread{?
publicstaticvoidmain(java/lang/String.java.html"target="_blank">
String
[] args){?
TestSynchronized t1 = newTestSynchronized("t1");?
TestSynchronized t2 = newTestSynchronized("t2");?
t1.start();?
t1.start(); //(1)
//t2.start(); (2)
}?
}?
運行結果為:
t1 : 0
t1 : 1
t1 : 2
t1 : 0
t1 : 1
t1 : 2
由于是同一個對象啟動的不同線程,所以run()函數實現了synchronized。如果去掉(2)的注釋,把代碼(1)注釋掉,結果將變為:
t1 : 0
t2 : 0
t1 : 1
t2 : 1
t1 : 2
t2 : 2
由于t1和t2是兩個對象,所以它們所啟動的線程可同時訪問run()函數。
2.2 通過實現Runnable接口實現多線程
如果有一個類,它已繼承了某個類,又想實現多線程,那就可以通過實現Runnable接口來實現。
1) Runnable接口只有一個run()函數。
2) 把一個實現了Runnable接口的對象作為參數產生一個Thread對象,再調用Thread對象的start()函數就可執行并行操作。如果在產生一個Thread對象時以一個Runnable接口的實現類的對象作為參數,那么在調用start()函數時,start()會調用Runnable接口的實現類中的run()函數。
例3.1:
publicclassTestThread implementsjava/lang/Runnable.java.html"target="_blank">
Runnable
{?
privatestaticintthreadCount = 0;?
privateintthreadNum = ++threadCount;?
privateinti = 5;?
publicvoidrun(){?
while(true){?
try{?
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);?
}?
catch(java/lang/InterruptedException.java.html"target="_blank">
InterruptedException
e){?
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");?
}?
java/lang/System.java.html" target="_blank">
System
.out.println("Thread " + threadNum + " = " + i);?
if(--i==0) return;?
}?
}?
publicstaticvoidmain(java/lang/String.java.html"target="_blank">
String
[] args){?
for(inti=0; i<5; i++)?
newjava/lang/Thread.java.html" target="_blank">
Thread
(newTestThread()).start(); //(1)
}?
}?
運行結果為:
Thread 1 = 5
Thread 2 = 5
Thread 3 = 5
Thread 4 = 5
Thread 5 = 5
Thread 1 = 4
Thread 2 = 4
Thread 3 = 4
Thread 4 = 4
Thread 4 = 3
Thread 5 = 4
Thread 1 = 3
Thread 2 = 3
Thread 3 = 3
Thread 4 = 2
Thread 5 = 3
Thread 1 = 2
Thread 2 = 2
Thread 3 = 2
Thread 4 = 1
Thread 5 = 2
Thread 1 = 1
Thread 2 = 1
Thread 3 = 1
Thread 5 = 1
例3是對例2的修改,它通過實現Runnable接口來實現并行處理。代碼(1)處可見,要調用TestThread中的并行操作部分,要把一個TestThread對象作為參數來產生Thread對象,再調用Thread對象的start()函數。
3) 同一個實現了Runnable接口的對象作為參數產生的所有Thread對象是同一對象下的線程。
例3.2:
packagemypackage1;?
publicclassTestThread implementsjava/lang/Runnable.java.html"target="_blank">
Runnable
{?
publicsynchronizedvoidrun(){?
for(inti=0; i<5; i++){?
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i);?
try{?
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);?
}?
catch(java/lang/InterruptedException.java.html"target="_blank">
InterruptedException
e){?
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");?
}?
}?
}?
publicstaticvoidmain(java/lang/String.java.html"target="_blank">
String
[] args){?
TestThread testThread = newTestThread();?
for(inti=0; i<5; i++)?
//new Thread(testThread, "t" + i).start(); (1)
newjava/lang/Thread.java.html" target="_blank">
Thread
(newTestThread(), "t" + i).start(); (2)?
}?
}?
運行結果為:
t0 : 0
t1 : 0
t2 : 0
t3 : 0
t4 : 0
t0 : 1
t1 : 1
t2 : 1
t3 : 1
t4 : 1
t0 : 2
t1 : 2
t2 : 2
t3 : 2
t4 : 2
t0 : 3
t1 : 3
t2 : 3
t3 : 3
t4 : 3
t0 : 4
t1 : 4
t2 : 4
t3 : 4
t4 : 4
由于代碼(2)每次都是用一個新的TestThread對象來產生Thread對象的,所以產生出來的Thread對象是不同對象的線程,所以所有Thread對象都可同時訪問run()函數。如果注釋掉代碼(2),并去掉代碼(1)的注釋,結果為:
t0 : 0
t0 : 1
t0 : 2
t0 : 3
t0 : 4
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 4
t2 : 0
t2 : 1
t2 : 2
t2 : 3
t2 : 4
t3 : 0
t3 : 1
t3 : 2
t3 : 3
t3 : 4
t4 : 0
t4 : 1
t4 : 2
t4 : 3
t4 : 4
由于代碼(1)中每次都是用同一個TestThread對象來產生Thread對象的,所以產生出來的Thread對象是同一個對象的線程,所以實現run()函數的同步。
?
二. 共享資源的同步
1. 同步的必要性
例4:
classSeq{?
privatestaticintnumber = 0;?
privatestaticSeq seq = newSeq();?
privateSeq() {}?
publicstaticSeq getInstance(){?
returnseq;?
}?
publicintget(){?
number++; //(a)
returnnumber; //(b)
}?
}?
publicclassTestThread{?
publicstaticvoidmain(java/lang/String.java.html"target="_blank">
String
[] args){?
Seq.getInstance().get(); //(1)
Seq.getInstance().get(); //(2)
}?
}?
上面是一個取得序列號的單例模式的例子,但調用get()時,可能會產生兩個相同的序列號:
當代碼(1)和(2)都試圖調用get()取得一個唯一的序列。當代碼(1)執行完代碼(a),正要執行代碼(b)時,它被中斷了并開始執行代碼(2)。一旦當代碼(2)執行完(a)而代碼(1)還未執行代碼(b),那么代碼(1)和代碼(2)就將得到相同的值。
2. 通過synchronized實現資源同步
2.1 鎖標志
2.1.1 每個對象都有一個標志鎖。當對象的一個線程訪問了對象的某個synchronized數據(包括函數)時,這個對象就將被“上鎖”,所以被聲明為synchronized的數據(包括函數)都不能被調用(因為當前線程取走了對象的“鎖標志”)。只有當前線程訪問完它要訪問的synchronized數據,釋放“鎖標志”后,同一個對象的其它線程才能訪問synchronized數據。
2.1.2 每個class也有一個“鎖標志”。對于synchronized static數據(包括函數)可以在整個class下進行鎖定,避免static數據的同時訪問。
例5:
classSeq{?
privatestaticintnumber = 0;?
privatestaticSeq seq = newSeq();?
privateSeq() {}?
publicstaticSeq getInstance(){?
returnseq;?
}?
publicsynchronizedintget(){ //(1)
number++;?
returnnumber;?
}?
}?
例5在例4的基礎上,把get()函數聲明為synchronized,那么在同一個對象中,就只能有一個線程調用get()函數,所以每個線程取得的number值就是唯一的了。
例6:
classSeq{?
privatestaticintnumber = 0;?
privatestaticSeq seq = null;?
privateSeq() {}?
synchronizedpublicstaticSeq getInstance(){ //(1)
if(seq==null) seq = newSeq();?
returnseq;?
}?
publicsynchronizedintget(){?
number++;?
returnnumber;?
}?
}?
例6把getInstance()函數聲明為synchronized,那樣就保證通過getInstance()得到的是同一個seq對象。
2.2 non-static的synchronized數據只能在同一個對象的純種實現同步訪問,不同對象的線程仍可同時訪問。
例7:
classTestSynchronized implementsjava/lang/Runnable.java.html"target="_blank">
Runnable
{?
publicsynchronizedvoidrun(){ //(1)
for(inti=0; i<10; i++){?
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i);?
/*(2)*/
try{?
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);?
}?
catch(java/lang/InterruptedException.java.html"target="_blank">
InterruptedException
e){?
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");?
}?
}?
}?
}?
publicclassTestThread{?
publicstaticvoidmain(java/lang/String.java.html"target="_blank">
String
[] args){?
TestSynchronized r1 = newTestSynchronized();?
TestSynchronized r2 = newTestSynchronized();?
java/lang/Thread.java.html" target="_blank">
Thread
t1 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t1");?
java/lang/Thread.java.html" target="_blank">
Thread
t2 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r2, "t2"); //(3)
//Thread t2 = new Thread(r1, "t2"); (4)
t1.start();?
t2.start();?
}?
}?
運行結果為:
t1 : 0
t2 : 0
t1 : 1
t2 : 1
t1 : 2
t2 : 2
t1 : 3
t2 : 3
t1 : 4
t2 : 4
t1 : 5
t2 : 5
t1 : 6
t2 : 6
t1 : 7
t2 : 7
t1 : 8
t2 : 8
t1 : 9
t2 : 9
雖然我們在代碼(1)中把run()函數聲明為synchronized,但由于t1、t2是兩個對象(r1、r2)的線程,而run()函數是non-static的synchronized數據,所以仍可被同時訪問(代碼(2)中的sleep()函數由于在暫停時不會釋放“標志鎖”,因為線程中的循環很難被中斷去執行另一個線程,所以代碼(2)只是為了顯示結果)。
如果把例7中的代碼(3)注釋掉,并去年代碼(4)的注釋,運行結果將為:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 4
t1 : 5
t1 : 6
t1 : 7
t1 : 8
t1 : 9
t2 : 0
t2 : 1
t2 : 2
t2 : 3
t2 : 4
t2 : 5
t2 : 6
t2 : 7
t2 : 8
t2 : 9
修改后的t1、t2是同一個對象(r1)的線程,所以只有當一個線程(t1或t2中的一個)執行run()函數,另一個線程才能執行。
2.3 對象的“鎖標志”和class的“鎖標志”是相互獨立的。
例8:
classTestSynchronized extendsjava/lang/Thread.java.html"target="_blank">
Thread
{?
publicTestSynchronized(java/lang/String.java.html"target="_blank">
String
name){?
super(name);?
}?
publicsynchronizedstaticvoidprt(){?
for(inti=10; i<20; i++){?
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i);?
try{?
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);?
}?
catch(java/lang/InterruptedException.java.html"target="_blank">
InterruptedException
e){?
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");?
}?
}?
}?
publicsynchronizedvoidrun(){?
for(inti=0; i<10; i++){?
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + i);?
try{?
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);?
}?
catch(java/lang/InterruptedException.java.html"target="_blank">
InterruptedException
e){?
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");?
}?
}?
}?
}?
publicclassTestThread{?
publicstaticvoidmain(java/lang/String.java.html"target="_blank">
String
[] args){?
TestSynchronized t1 = newTestSynchronized("t1");?
TestSynchronized t2 = newTestSynchronized("t2");?
t1.start();?
t1.prt(); //(1)
t2.prt(); //(2)
}?
}?
運行結果為:
main : 10
t1 : 0
main : 11
t1 : 1
main : 12
t1 : 2
main : 13
t1 : 3
main : 14
t1 : 4
main : 15
t1 : 5
main : 16
t1 : 6
main : 17
t1 : 7
main : 18
t1 : 8
main : 19
t1 : 9
main : 10
main : 11
main : 12
main : 13
main : 14
main : 15
main : 16
main : 17
main : 18
main : 19
在代碼(1)中,雖然是通過對象t1來調用prt()函數的,但由于prt()是靜態的,所以調用它時不用經過任何對象,它所屬的線程為main線程。
由于調用run()函數取走的是對象鎖,而調用prt()函數取走的是class鎖,所以同一個線程t1(由上面可知實際上是不同線程)調用run()函數且還沒完成run()函數時,它就能調用prt()函數。但prt()函數只能被一個線程調用,如代碼(1)和代碼(2),即使是兩個不同的對象也不能同時調用prt()。
3. 同步的優化
1) synchronized block
語法為:synchronized(reference){do this }
reference用來指定“以某個對象的鎖標志”對“大括號內的代碼”實施同步控制。
例9:
classTestSynchronized implementsjava/lang/Runnable.java.html"target="_blank">
Runnable
{?
staticintj = 0;?
publicsynchronizedvoidrun(){?
for(inti=0; i<5; i++){?
//(1)
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + j++);?
try{?
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);?
}?
catch(java/lang/InterruptedException.java.html"target="_blank">
InterruptedException
e){?
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");?
}?
}?
}?
}?
publicclassTestThread{?
publicstaticvoidmain(java/lang/String.java.html"target="_blank">
String
[] args){?
TestSynchronized r1 = newTestSynchronized();?
TestSynchronized r2 = newTestSynchronized();?
java/lang/Thread.java.html" target="_blank">
Thread
t1 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t1");?
java/lang/Thread.java.html" target="_blank">
Thread
t2 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t2");?
t1.start();?
t2.start();?
}?
}?
運行結果為:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 4
t2 : 5
t2 : 6
t2 : 7
t2 : 8
t2 : 9
上面的代碼的run()函數實現了同步,使每次打印出來的j總是不相同的。但實際上在整個run()函數中,我們只關心j的同步,而其余代碼同步與否我們是不關心的,所以可以對它進行以下修改:
classTestSynchronized implementsjava/lang/Runnable.java.html"target="_blank">
Runnable
{?
staticintj = 0;?
publicvoidrun(){?
for(inti=0; i<5; i++){?
//(1)
synchronized(this){?
java/lang/System.java.html" target="_blank">
System
.out.println(java/lang/Thread.java.html" target="_blank">
Thread
.currentThread().getName() + " : " + j++);?
}?
try{?
java/lang/Thread.java.html" target="_blank">
Thread
.sleep(100);?
}?
catch(java/lang/InterruptedException.java.html"target="_blank">
InterruptedException
e){?
java/lang/System.java.html" target="_blank">
System
.out.println("Interrupted");?
}?
}?
}?
}?
publicclassTestThread{?
publicstaticvoidmain(java/lang/String.java.html"target="_blank">
String
[] args){?
TestSynchronized r1 = newTestSynchronized();?
TestSynchronized r2 = newTestSynchronized();?
java/lang/Thread.java.html" target="_blank">
Thread
t1 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t1");?
java/lang/Thread.java.html" target="_blank">
Thread
t2 = newjava/lang/Thread.java.html" target="_blank">
Thread
(r1, "t2");?
t1.start();?
t2.start();?
}?
}?
運行結果為:
t1 : 0
t2 : 1
t1 : 2
t2 : 3
t1 : 4
t2 : 5
t1 : 6
t2 : 7
t1 : 8
t2 : 9
由于進行同步的范圍縮小了,所以程序的效率將提高。同時,代碼(1)指出,當對大括號內的println()語句進行同步控制時,會取走當前對象的“鎖標志”,即對當前對象“上鎖”,不讓當前對象下的其它線程執行當前對象的其它synchronized數據。
?
三. 線程間的通信
1. 線程的幾種狀態
線程有四種狀態,任何一個線程肯定處于這四種狀態中的一種:
1) 產生(New):線程對象已經產生,但尚未被啟動,所以無法執行。如通過new產生了一個線程對象后沒對它調用start()函數之前。
2) 可執行(Runnable):每個支持多線程的系統都有一個排程器,排程器會從線程池中選擇一個線程并啟動它。當一個線程處于可執行狀態時,表示它可能正處于線程池中等待排排程器啟動它;也可能它已正在執行。如執行了一個線程對象的start()方法后,線程就處于可執行狀態,但顯而易見的是此時線程不一定正在執行中。
3) 死亡(Dead):當一個線程正常結束,它便處于死亡狀態。如一個線程的run()函數執行完畢后線程就進入死亡狀態。
4) 停滯(Blocked):當一個線程處于停滯狀態時,系統排程器就會忽略它,不對它進行排程。當處于停滯狀態的線程重新回到可執行狀態時,它有可能重新執行。如通過對一個線程調用wait()函數后,線程就進入停滯狀態,只有當兩次對該線程調用notify或notifyAll后它才能兩次回到可執行狀態。
2. class Thread下的常用函數函數
2.1 suspend()、resume()
1) 通過suspend()函數,可使線程進入停滯狀態。通過suspend()使線程進入停滯狀態后,除非收到resume()消息,否則該線程不會變回可執行狀態。
2) 當調用suspend()函數后,線程不會釋放它的“鎖標志”。
例11:
class TestThreadMethod extendsThread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
if(shareVar==0){
for(int i=0; i<5; i++){
shareVar++;
if(shareVar==5){
this.suspend(); //(1)
}
}
}
else{
System.out.print(Thread.currentThread().getName());
System.out.println(" shareVar = " + shareVar);
this.resume(); //(2)
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start(); //(5)
//t1.start(); //(3)
t2.start(); //(4)
}
}
運行結果為:
t2 shareVar = 5
i. 當代碼(5)的t1所產生的線程運行到代碼(1)處時,該線程進入停滯狀態。然后排程器從線程池中喚起代碼(4)的t2所產生的線程,此時shareVar值不為0,所以執行else中的語句。
ii. 也許你會問,那執行代碼(2)后為什么不會使t1進入可執行狀態呢?正如前面所說,t1和t2是兩個不同對象的線程,而代碼(1)和(2)都只對當前對象進行操作,所以t1所產生的線程執行代碼(1)的結果是對象t1的當前線程進入停滯狀態;而t2所產生的線程執行代碼(2)的結果是把對象t2中的所有處于停滯狀態的線程調回到可執行狀態。
iii. 那現在把代碼(4)注釋掉,并去掉代碼(3)的注釋,是不是就能使t1重新回到可執行狀態呢?運行結果是什么也不輸出。為什么會這樣呢?也許你會認為,當代碼(5)所產生的線程執行到代碼(1)時,它進入停滯狀態;而代碼(3)所產生的線程和代碼(5)所產生的線程是屬于同一個對象的,那么就當代碼(3)所產生的線程執行到代碼(2)時,就可使代碼(5)所產生的線程執行回到可執行狀態。但是要清楚,suspend()函數只是讓當前線程進入停滯狀態,但并不釋放當前線程所獲得的“鎖標志”。所以當代碼(5)所產生的線程進入停滯狀態時,代碼(3)所產生的線程仍不能啟動,因為當前對象的“鎖標志”仍被代碼(5)所產生的線程占有。
2.2 sleep()
1) sleep ()函數有一個參數,通過參數可使線程在指定的時間內進入停滯狀態,當指定的時間過后,線程則自動進入可執行狀態。
2) 當調用sleep ()函數后,線程不會釋放它的“鎖標志”。
例12:
class TestThreadMethod extendsThread{
class TestThreadMethod extends Thread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
for(int i=0; i<3; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
try{
Thread.sleep(100); //(4)
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start(); (1)
t1.start(); (2)
//t2.start(); (3)
}
}
運行結果為:
t1 : 0
t1 : 1
t1 : 2
t1 : 0
t1 : 1
t1 : 2
由結果可證明,雖然在run()中執行了sleep(),但是它不會釋放對象的“鎖標志”,所以除非代碼(1)的線程執行完run()函數并釋放對象的“鎖標志”,否則代碼(2)的線程永遠不會執行。
如果把代碼(2)注釋掉,并去掉代碼(3)的注釋,結果將變為:
t1 : 0
t2 : 0
t1 : 1
t2 : 1
t1 : 2
t2 : 2
由于t1和t2是兩個對象的線程,所以當線程t1通過sleep()進入停滯時,排程器會從線程池中調用其它的可執行線程,從而t2線程被啟動。
例13:
class TestThreadMethod extendsThread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
for(int i=0; i<5; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
try{
if(Thread.currentThread().getName().equals("t1"))
Thread.sleep(200);
else
Thread.sleep(100);
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start();
//t1.start();
t2.start();
}
}
運行結果為:
t1 : 0
t2 : 0
t2 : 1
t1 : 1
t2 : 2
t2 : 3
t1 : 2
t2 : 4
t1 : 3
t1 : 4
由于線程t1調用了sleep(200),而線程t2調用了sleep(100),所以線程t2處于停滯狀態的時間是線程t1的一半,從從結果反映出來的就是線程t2打印兩倍次線程t1才打印一次。
2.3 yield()
1) 通過yield ()函數,可使線程進入可執行狀態,排程器從可執行狀態的線程中重新進行排程。所以調用了yield()的函數也有可能馬上被執行。
2) 當調用yield ()函數后,線程不會釋放它的“鎖標志”。
例14:
class TestThreadMethod extendsThread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
for(int i=0; i<4; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
Thread.yield();
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start();
t1.start(); //(1)
//t2.start(); (2)
}
}
運行結果為:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 0
t1 : 1
t1 : 2
t1 : 3
從結果可知調用yield()時并不會釋放對象的“鎖標志”。
如果把代碼(1)注釋掉,并去掉代碼(2)的注釋,結果為:
t1 : 0
t1 : 1
t2 : 0
t1 : 2
t2 : 1
t1 : 3
t2 : 2
t2 : 3
從結果可知,雖然t1線程調用了yield(),但它馬上又被執行了。
2.4 sleep()和yield()的區別
1) sleep()使當前線程進入停滯狀態,所以執行sleep()的線程在指定的時間內肯定不會執行;yield()只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入到可執行狀態后馬上又被執行。
2) sleep()可使優先級低的線程得到執行的機會,當然也可以讓同優先級和高優先級的線程有執行的機會;yield()只能使同優先級的線程有執行的機會。
例15:
class TestThreadMethod extendsThread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public void run(){
for(int i=0; i<4; i++){
System.out.print(Thread.currentThread().getName());
System.out.println(" : " + i);
//Thread.yield(); (1)
/* (2) */
try{
Thread.sleep(3000);
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
運行結果為:
t1 : 0
t1 : 1
t2 : 0
t1 : 2
t2 : 1
t1 : 3
t2 : 2
t2 : 3
由結果可見,通過sleep()可使優先級較低的線程有執行的機會。注釋掉代碼(2),并去掉代碼(1)的注釋,結果為:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t2 : 0
t2 : 1
t2 : 2
t2 : 3
可見,調用yield(),不同優先級的線程永遠不會得到執行機會。
2.5 join()
使調用join()的線程執行完畢后才能執行其它線程,在一定意義上,它可以實現同步的功能。
例16:
class TestThreadMethod extendsThread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public void run(){
for(int i=0; i<4; i++){
System.out.println(" " + i);
try{
Thread.sleep(3000);
}
catch(InterruptedException e){
System.out.println("Interrupted");
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
t1.start();
try{
t1.join();
}
catch(InterruptedException e){}
t1.start();
}
}
運行結果為:
0
1
2
3
0
1
2
3
3. class Object下常用的線程函數
wait()、notify()和notifyAll()這三個函數由java.lang.Object類提供,用于協調多個線程對共享數據的存取。
3.1 wait()、notify()和notifyAll()
1) wait()函數有兩種形式:第一種形式接受一個毫秒值,用于在指定時間長度內暫停線程,使線程進入停滯狀態。第二種形式為不帶參數,代表waite()在notify()或notifyAll()之前會持續停滯。
2) 當對一個對象執行notify()時,會從線程等待池中移走該任意一個線程,并把它放到鎖標志等待池中;當對一個對象執行notifyAll()時,會從線程等待池中移走所有該對象的所有線程,并把它們放到鎖標志等待池中。
3) 當調用wait()后,線程會釋放掉它所占有的“鎖標志”,從而使線程所在對象中的其它synchronized數據可被別的線程使用。
例17:
下面,我們將對例11中的例子進行修改
class TestThreadMethod extendsThread{
public static int shareVar = 0;
public TestThreadMethod(String name){
super(name);
}
public synchronized void run(){
if(shareVar==0){
for(int i=0; i<10; i++){
shareVar++;
if(shareVar==5){
try{
this.wait(); //(4)
}
catch(InterruptedException e){}
}
}
}
if(shareVar!=0){
System.out.print(Thread.currentThread().getName());
System.out.println(" shareVar = " + shareVar);
this.notify(); //(5)
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
TestThreadMethod t2 = new TestThreadMethod("t2");
t1.start(); //(1)
//t1.start(); (2)
t2.start(); //(3)
}
}
運行結果為:
t2 shareVar = 5
因為t1和t2是兩個不同對象,所以線程t2調用代碼(5)不能喚起線程t1。如果去掉代碼(2)的注釋,并注釋掉代碼(3),結果為:
t1 shareVar = 5
t1 shareVar = 10
這是因為,當代碼(1)的線程執行到代碼(4)時,它進入停滯狀態,并釋放對象的鎖狀態。接著,代碼(2)的線程執行run(),由于此時shareVar值為5,所以執行打印語句并調用代碼(5)使代碼(1)的線程進入可執行狀態,然后代碼(2)的線程結束。當代碼(1)的線程重新執行后,它接著執行for()循環一直到shareVar=10,然后打印shareVar。
3.2 wait()、notify()和synchronized
waite()和notify()因為會對對象的“鎖標志”進行操作,所以它們必須在synchronized函數或synchronized block中進行調用。如果在non-synchronized函數或non-synchronized block中進行調用,雖然能編譯通過,但在運行時會發生IllegalMonitorStateException的異常。
例18:
class TestThreadMethod extendsThread{
public int shareVar = 0;
public TestThreadMethod(String name){
super(name);
new Notifier(this);
}
public synchronized void run(){
if(shareVar==0){
for(int i=0; i<5; i++){
shareVar++;
System.out.println("i = " + shareVar);
try{
System.out.println("wait......");
this.wait();
}
catch(InterruptedException e){}
}
}
}
}
class Notifier extends Thread{
private TestThreadMethod ttm;
Notifier(TestThreadMethod t){
ttm = t;
start();
}
public void run(){
while(true){
try{
sleep(2000);
}
catch(InterruptedException e){}
/*1 要同步的不是當前對象的做法 */
synchronized(ttm){
System.out.println("notify......");
ttm.notify();
}
}
}
}
public class TestThread{
public static void main(String[] args){
TestThreadMethod t1 = new TestThreadMethod("t1");
t1.start();
}
}
運行結果為:
i = 1
wait......
notify......
i = 2
wait......
notify......
i = 3
wait......
notify......
i = 4
wait......
notify......
i = 5
wait......
notify......
4. wait()、notify()、notifyAll()和suspend()、resume()、sleep()的討論
4.1 這兩組函數的區別
1) wait()使當前線程進入停滯狀態時,還會釋放當前線程所占有的“鎖標志”,從而使線程對象中的synchronized資源可被對象中別的線程使用;而suspend()和sleep()使當前線程進入停滯狀態時不會釋放當前線程所占有的“鎖標志”。
2) 前一組函數必須在synchronized函數或synchronized block中調用,否則在運行時會產生錯誤;而后一組函數可以non-synchronized函數和synchronized block中調用。
4.2 這兩組函數的取舍
Java2已不建議使用后一組函數。因為在調用wait()時不會釋放當前線程所取得的“鎖標志”,這樣很容易造成“死鎖”。
?
?
最后,問題
線程之間的關系是平等的,彼此之間并不存在任何依賴,它們各自競爭CPU資源,互不相讓,并且還無條件地阻止其他線程對共享資源的異步訪問。然而,也有很多現實問題要求不僅要同步的訪問同一共享資源,而且線程間還彼此牽制,通過相互通信來向前推進。那么,多個線程之間是如何進行通信的呢?
解決思路
在現實應用中,很多時候都需要讓多個線程按照一定的次序來訪問共享資源,例如,經典的生產者和消費者問題。這類問題描述了這樣一種情況,假設倉庫中只能存放一件產品,生產者將生產出來的產品放入倉庫,消費者將倉庫中的產品取走消費。如果倉庫中沒有產品,則生產者可以將產品放入倉庫,否則停止生產并等待,直到倉庫中的產品被消費者取走為止。如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費并等待,直到倉庫中再次放入產品為止。顯然,這是一個同步問題,生產者和消費者共享同一資源,并且,生產者和消費者之間彼此依賴,互為條件向前推進。但是,該如何編寫程序來解決這個問題呢?
傳統的思路是利用循環檢測的方式來實現,這種方式通過重復檢查某一個特定條件是否成立來決定線程的推進順序。比如,一旦生產者生產結束,它就繼續利用循環檢測來判斷倉庫中的產品是否被消費者消費,而消費者也是在消費結束后就會立即使用循環檢測的方式來判斷倉庫中是否又放進產品。顯然,這些操作是很耗費CPU資源的,不值得提倡。那么有沒有更好的方法來解決這類問題呢?
首先,當線程在繼續執行前需要等待一個條件方可繼續執行時,僅有synchronized 關鍵字是不夠的。因為雖然synchronized關鍵字可以阻止并發更新同一個共享資源,實現了同步,但是它不能用來實現線程間的消息傳遞,也就是所謂的通信。而在處理此類問題的時候又必須遵循一種原則,即:對于生產者,在生產者沒有生產之前,要通知消費者等待;在生產者生產之后,馬上又通知消費者消費;對于消費者,在消費者消費之后,要通知生產者已經消費結束,需要繼續生產新的產品以供消費。
其實,Java提供了3個非常重要的方法來巧妙地解決線程間的通信問題。這3個方法分別是:wait()、notify()和notifyAll()。它們都是Object類的最終方法,因此每一個類都默認擁有它們。
雖然所有的類都默認擁有這3個方法,但是只有在synchronized關鍵字作用的范圍內,并且是同一個同步問題中搭配使用這3個方法時才有實際的意義。
這些方法在Object類中聲明的語法格式如下所示:
final void wait() throws InterruptedException
final void notify()
final void notifyAll()
其中,調用wait()方法可以使調用該方法的線程釋放共享資源的鎖,然后從運行態退出,進入等待隊列,直到被再次喚醒。而調用notify()方法可以喚醒等待隊列中第一個等待同一共享資源的線程,并使該線程退出等待隊列,進入可運行態。調用notifyAll()方法可以使所有正在等待隊列中等待同一共享資源的線程從等待狀態退出,進入可運行狀態,此時,優先級最高的那個線程最先執行。顯然,利用這些方法就不必再循環檢測共享資源的狀態,而是在需要的時候直接喚醒等待隊列中的線程就可以了。這樣不但節省了寶貴的CPU資源,也提高了程序的效率。
由于wait()方法在聲明的時候被聲明為拋出InterruptedException異常,因此,在調用wait()方法時,需要將它放入try…catch代碼塊中。此外,使用該方法時還需要把它放到一個同步代碼段中,否則會出現如下異常:
"java.lang.IllegalMonitorStateException: current thread notowner"
這些方法是不是就可以實現線程間的通信了呢?下面將通過多線程同步的模型:生產者和消費者問題來說明怎樣通過程序解決多線程間的通信問題。
具體步驟
下面這個程序演示了多個線程之間進行通信的具體實現過程。程序中用到了4個類,其中ShareData類用來定義共享數據和同步方法。在同步方法中調用了wait()方法和notify()方法,并通過一個信號量來實現線程間的消息傳遞。
// 例4.6.1CommunicationDemo.java 描述:生產者和消費者之間的消息傳遞過程
class ShareData
{
private char c;
private boolean isProduced = false; // 信號量
public synchronized void putShareChar(char c) // 同步方法putShareChar()
{
if (isProduced) // 如果產品還未消費,則生產者等待
{
try
{
wait(); // 生產者等待
}catch(InterruptedException e){
e.printStackTrace();
}
}
this.c = c;
isProduced = true; // 標記已經生產
notify(); // 通知消費者已經生產,可以消費
}
public synchronized char getShareChar() // 同步方法getShareChar()
{
if (!isProduced) // 如果產品還未生產,則消費者等待
{
try
{
wait(); // 消費者等待
}catch(InterruptedException e){
e.printStackTrace();
}
}
isProduced = false; // 標記已經消費
notify(); // 通知需要生產
return this.c;
}
}
class Producer extends Thread // 生產者線程
{
private ShareData s;
Producer(ShareData s)
{
this.s = s;
}
public void run()
{
for (char ch = ''A''; ch <= ''D''; ch++)
{
try
{
Thread.sleep((int)(Math.random()*3000));
}catch(InterruptedException e){
e.printStackTrace();
}
s.putShareChar(ch); // 將產品放入倉庫
System.out.println(ch + " is produced by Producer.");
}
}
}
class Consumer extends Thread // 消費者線程
{
private ShareData s;
Consumer(ShareData s)
{
this.s = s;
}
public void run()
{
char ch;
do{
try
{
Thread.sleep((int)(Math.random()*3000));
}catch(InterruptedException e){
e.printStackTrace();
}
ch = s.getShareChar(); // 從倉庫中取出產品
System.out.println(ch + " is consumed by Consumer. ");
}while (ch != ''D'');
}
}
class CommunicationDemo
{
public static void main(String[] args)
{
ShareData s = new ShareData();
new Consumer(s).start();
new Producer(s).start();
}
}
from:?http://blog.csdn.net/it_man/article/details/7196645
總結
以上是生活随笔為你收集整理的Java多线程间的通信的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Eclipse中使用Maven 2.x
- 下一篇: java美元兑换,(Java实现) 美元