線程間通信:
多個線程在處理同一資源,但是他們的任務不同(一部分線程生產鴨子,另一部分線程銷售鴨子)
從下面的代碼開始,一步步的引出問題並解決
1 public class Text2 { 2 public static void main(String[] args) { 3 Ziyuan r=new Ziyuan(); //創建資源 4 Inp a=new Inp(r); //創建輸入任務 5 Outp b=new Outp(r); //創建輸出任務 6 Thread t=new Thread(a); //創建輸入線程,執行路徑(執行輸入任務) 7 Thread t1=new Thread(b); //創建輸出線程,執行路徑(執行輸出任務) 8 t.start(); //開啟線程 9 t1.start(); 10 } 11 } 12 class Ziyuan{ 13 String name; 14 String sex; 15 } 16 class Inp implements Runnable{ 17 Ziyuan r; 18 int a=0; 19 Inp(Ziyuan r){ 20 this.r=r; 21 } 22 public void run(){ 23 while(true){ 24 if(a==0){ 25 r.name="黑"; 26 r.sex="男"; 27 }else{ 28 r.name="白白"; 29 r.sex="女女"; 30 } 31 a=(a+1)%2; 32 } 33 } 34 } 35 class Outp implements Runnable{ 36 Ziyuan r; 37 int a=0; 38 Outp(Ziyuan r){ 39 this.r=r; 40 } 41 public void run(){ 42 while(true) 43 System.out.println(r.name+"...."+r.sex); 44 } 45 }
輸出的結果會出現這種情況:
黑....女女
白白....男
會出現這種情況是因為有多個線程操作共享資源,並且操作共享資源的代碼有多條,可以用同步解決這種安全問題
修改后的代碼為:(就是簡單的加上一個鎖就可以解決問題,要保證線程使用的是同一個鎖)
1 public class Text2 { 2 public static void main(String[] args) { 3 Ziyuan r=new Ziyuan(); //創建資源 4 Inp a=new Inp(r); //創建輸入任務 5 Outp b=new Outp(r); //創建輸出任務 6 Thread t=new Thread(a); //創建輸入線程,執行路徑(執行輸入任務) 7 Thread t1=new Thread(b); //創建輸出線程,執行路徑(執行輸出任務) 8 t.start(); //開啟線程 9 t1.start(); 10 } 11 } 12 class Ziyuan{ 13 String name; 14 String sex; 15 } 16 class Inp implements Runnable{ 17 Ziyuan r; 18 int a=0; 19 Inp(Ziyuan r){ 20 this.r=r; 21 } 22 public void run(){ 23 while(true){ 24 synchronized(r){ 25 if(a==0){ 26 r.name="黑"; 27 r.sex="男"; 28 }else{ 29 r.name="白白"; 30 r.sex="女女"; 31 } 32 a=(a+1)%2; 33 } 34 } 35 } 36 } 37 class Outp implements Runnable{ 38 Ziyuan r; 39 int a=0; 40 Outp(Ziyuan r){ 41 this.r=r; 42 } 43 public void run(){ 44 while(true) 45 synchronized(r){ 46 System.out.println(r.name+"...."+r.sex); 47 } 48 } 49 }
這樣雖然解決了安全問題,但是輸入(輸出)都是成片存在的,假設我們是在賣票,這樣就會出現一張票被重復賣出多次。
現在我就想,輸入一個,打印一個。這樣就引出了等待喚醒機制。
等待喚醒機制:
涉及的方法:
wait() 讓線程進入凍結狀態, 把線程存儲在線程池中
notify() 喚醒線程池中的一個線程
notifyall() 喚醒線程池中所有線程
這些方法都是操做線程狀態的
這些方法必須定義在同步中
這些方法必須明確屬於哪個鎖
因為這些方法是監視器的方法,同步中一把鎖只能有一組監視器,鎖是任意的對象,任意的對象調用的方式一定定義在Object類,所以這些方法定義在Object類中。(我的理解,鎖調用這些方法去改變線程狀態,要保證鎖一定能調用這些方法,那就只能把這些方法定義在Object中)
1 public class Text2 { 2 public static void main(String[] args) { 3 Ziyuan r=new Ziyuan(); //創建資源 4 Inp a=new Inp(r); //創建輸入任務 5 Outp b=new Outp(r); //創建輸出任務 6 Thread t=new Thread(a); //創建輸入線程,執行路徑(執行輸入任務) 7 Thread t1=new Thread(b); //創建輸出線程,執行路徑(執行輸出任務) 8 t.start(); //開啟線程 9 t1.start(); 10 } 11 } 12 class Ziyuan{ 13 String name; 14 String sex; 15 boolean bool=false; 16 } 17 class Inp implements Runnable{ 18 Ziyuan r; 19 int a=0; 20 Inp(Ziyuan r){ 21 this.r=r; 22 } 23 public void run(){ 24 while(true){ 25 synchronized(r){ 26 if(r.bool){ 27 try {r.wait();} catch (InterruptedException e) {} 28 } 29 if(a==0){ 30 r.name="黑"; 31 r.sex="男"; 32 }else{ 33 r.name="白白"; 34 r.sex="女女"; 35 } 36 a=(a+1)%2; 37 r.bool=true; 38 r.notify(); 39 } 40 } 41 } 42 } 43 class Outp implements Runnable{ 44 Ziyuan r; 45 int a=0; 46 Outp(Ziyuan r){ 47 this.r=r; 48 } 49 public void run(){ 50 while(true) 51 synchronized(r){ 52 if(!r.bool){ 53 try {r.wait();} catch (InterruptedException e) {} 54 } 55 System.out.println(r.name+"...."+r.sex); 56 r.bool=false; 57 r.notify(); 58 } 59 } 60 }
這樣問題便得到了解決
可是我不滿足於現在的單線程輸入 單線程輸出;搞點猛地多線程輸入 多線程輸出。引出經典操作“多生產者多消費者”
多生產者多消費者:
在上面的代碼基礎上簡單的修改成買賣鴨子,並實現多生產者多消費者,看看會出什么問題:
1 public class Text2 { 2 public static void main(String[] args) { 3 Ziyuan r=new Ziyuan(); //創建資源 4 Inp a=new Inp(r); //創建輸入任務 5 Outp b=new Outp(r); //創建輸出任務 6 Thread t0=new Thread(a); //創建輸入線程,執行路徑(執行輸入任務) 7 Thread t1=new Thread(a); //創建輸出線程,執行路徑(執行輸出任務) 8 Thread t2=new Thread(b); 9 Thread t3=new Thread(b); 10 t0.start(); //開啟線程 11 t1.start(); 12 t2.start(); 13 t3.start(); 14 } 15 } 16 class Ziyuan{ 17 boolean bool=false; 18 int a=0; 19 } 20 class Inp implements Runnable{ 21 Ziyuan r; 22 Inp(Ziyuan r){ 23 this.r=r; 24 } 25 public void run(){ 26 while(true){ 27 synchronized(r){ 28 if(r.bool){ 29 try {r.wait();} catch (InterruptedException e) {} 30 } 31 r.a++; 32 System.out.println(Thread.currentThread().getName()+"生產鴨子。。。"+r.a); 33 r.bool=true; 34 r.notify(); 35 } 36 } 37 } 38 } 39 class Outp implements Runnable{ 40 Ziyuan r; 41 Outp(Ziyuan r){ 42 this.r=r; 43 } 44 public void run(){ 45 while(true) 46 synchronized(r){ 47 if(!r.bool){ 48 try {r.wait();} catch (InterruptedException e) {} 49 } 50 System.out.println(Thread.currentThread().getName()+"賣出鴨子"+r.a--); 51 r.bool=false; 52 r.notify(); 53 } 54 } 55 }
28 47 行使用的是if判斷,這就表示如果線程0在生產了鴨子后,凍結在28行處的1線程醒了,那么他不會在判斷,而是繼續生產鴨子,這就導致錯誤數據的出現(這時將if換成while)。
換成了while后發現,出現了死鎖情況(原因:比如.線程1.2.3已經進入了凍結狀態,線程0進凍結前,喚醒了t1線程,然后t1獲得了CPU的執行權,在t1進行了判斷后也進入了凍結狀態,這是所有的線程都進入了凍結狀態)。
只要我們保證一定能夠喚醒對方線程,那么就能解決問題,所以使用notifyAll()方法替換notify()方法就可以解決問題。
注意:1.if只有一次判斷,會導致不該運行的線程運行 2.while判斷標記,解決了線程獲得執行權之后是否要運行的問題,如果while和notify一起使用會導致死鎖 3.notifyAll解決了一定會喚醒對方線程的問題(如果己方線程總是獲得CPU執行權,那么效率會降低)
Lock接口和Condition接口
如果只是為了喚醒對方線程,而去喚醒所有線程,很明顯是不明智的,所以在JDK1.5版本后為我們提供了解決方法
Lock: lock()獲取鎖 unlock()釋放鎖 ReentrantLock(已知實現子類) Lock代替了synchronized方法和語句的使用
Condition: await()凍結線程 signal()喚醒一個線程 signalAll()喚醒所有線程 Condition代替了Objcet監視器方法的使用
同步中,一把鎖只能有一組監視器,但是Lock鎖已有多組Condition監視器
如果線程發生了異常,那么鎖將無法釋放,所以unlock()一定要放在finally中
使用升級后的JDK所提供的方法重新寫代碼,提高效率
1 import java.util.concurrent.locks.Condition; 2 import java.util.concurrent.locks.Lock; 3 import java.util.concurrent.locks.ReentrantLock; 4 5 6 public class Text2 { 7 public static void main(String[] args) { 8 Ziyuan r=new Ziyuan(); 9 Inp in=new Inp(r); 10 Outp out=new Outp(r); 11 Thread t0=new Thread(in); 12 Thread t1=new Thread(in); 13 Thread t2=new Thread(out); 14 Thread t3=new Thread(out); 15 t0.start(); 16 t1.start(); 17 t2.start(); 18 t3.start(); 19 20 } 21 } 22 class Ziyuan{ 23 private String name; 24 private String sex; 25 private boolean bool=false; 26 private int mun=1; 27 Lock suo=new ReentrantLock(); 28 Condition inJian=suo.newCondition(); 29 Condition outJian=suo.newCondition(); 30 31 void show(String name){ 32 try{ 33 suo.lock(); 34 while(bool){ 35 try {inJian.await();} catch (InterruptedException e) {} 36 } 37 this.name=name+mun++; 38 System.out.println(Thread.currentThread().getName()+"生產。。。"+this.name); 39 bool=true; 40 outJian.signal(); 41 }finally{ 42 suo.unlock(); 43 } 44 45 } 46 void show1(){ 47 try{ 48 suo.lock(); 49 while(!bool){ 50 try {outJian.await();} catch (InterruptedException e) {} 51 } 52 System.out.println(Thread.currentThread().getName()+"消費.................."+name); 53 bool=false; 54 inJian.signal(); 55 }finally{ 56 suo.unlock(); 57 } 58 59 } 60 } 61 class Inp implements Runnable{ 62 Ziyuan r; 63 Inp(Ziyuan r){ 64 this.r=r; 65 } 66 public void run(){ 67 while(true){ 68 r.show("烤鴨"); 69 } 70 } 71 } 72 class Outp implements Runnable{ 73 Ziyuan r; 74 Outp(Ziyuan r){ 75 this.r=r; 76 } 77 public void run(){ 78 while(true){ 79 r.show1(); 80 } 81 } 82 }
wait和sleep的區別:
1:wait可以指定時間,也可以不指定
sleep一定要指定時間
2:wait釋放執行權,釋放鎖
sleep釋放執行權,不釋放鎖
停止線程:
stop() 過時了,存在安全隱患
run() 方法結束(run方法運行完了,該線程會自動結束)(使用標記結束run方法),代碼如下:
1 class Producer implements Runnable 2 { 3 int a=0; 4 boolean bool=true; //標記 5 public void run() 6 { 7 while(bool){ 8 System.out.println(Thread.currentThread().getName()+" "+a++); 9 } 10 } 11 public void set(boolean bool){ //修改標記,用來控制run()方法的結束 12 this.bool=bool; 13 } 14 15 } 16 class ProducerConsumerDemo2 17 { 18 public static void main(String[] args) 19 { 20 Producer pro = new Producer(); 21 Thread t0 = new Thread(pro); 22 t0.start(); 23 for(int i=0;i<500;i++){ 24 System.out.println(Thread.currentThread().getName()+".................."+i); 25 if(i==499){ 26 pro.set(false); 27 } 28 29 } 30 System.out.println(Thread.currentThread().getName()+"..................................."+"over"); 31 } 32 }
如果線程處於凍結狀態,那么利用標記來結束線程是行不通的;所以,Thread提供了interrupt()方法強制喚醒線程(因此會拋異常)
1 class Producer implements Runnable 2 { 3 int a=0; 4 boolean bool=true; //標記 5 public synchronized void run() 6 { 7 while(bool){ 8 try { 9 wait(); 10 } 11 catch (InterruptedException e) { 12 bool=false; //因為這中喚醒機制會發生InterruptedException異常,一定會運行catch 所以一般吧標記寫在catch中 13 } 14 System.out.println(Thread.currentThread().getName()+" "+a++); 15 } 16 } 17 } 18 class ProducerConsumerDemo2 19 { 20 public static void main(String[] args) 21 { 22 Producer pro = new Producer(); 23 Thread t0 = new Thread(pro); 24 t0.start(); 25 for(int i=0;i<500;i++){ 26 System.out.println(Thread.currentThread().getName()+".................."+i); 27 if(i==499){ 28 t0.interrupt(); 29 } 30 31 } 32 System.out.println(Thread.currentThread().getName()+"..................................."+"over"); 33 } 34 }
守護線程(后台線程):
steDaemon(boolean) (當參數為true的時候這個線程為守護線程)
后台和前台線程基本一樣,唯一不同的就是,當前台線程全部運行結束后,后台線程也會跟着結束。(當正在運行的程序全都為守護線程的時候,JAVA虛擬機會自動退出)
其他方法:
join() 一般臨時加入一個線程執行運算,會調用這個方法(假設有t0 t1 main三個線程,如果t0調用了這個方法,那么主線程會凍結,t0 t1搶奪CPU的執行權,當t0運算完成,main才接觸凍結狀態)
setPriority(int) 設置線程的優先級(1-10)一般分為三個等級(1-5-10)注意並不是設置成了10這個最高級,這個線程就會拼命運行,只不過是CPU比較照顧一點。