多線程安全問題緣由是在cpu執行多線程時,在執行的過程當中可能隨時切換到其餘的線程上執行。java
在以上紅色選中的三個部分,線程都有可能進行切換。只要cpu在這個三個地中的任何地方切換了,均可能致使錯誤數據出現,線程的不安全因素就有了。程序員
形成錯誤數據的緣由是多個線程可能出現同時訪問num的狀況。而任何一個線程在訪問num的過程當中均可以切換到其餘的線程上。而其餘線程一旦把num的數據改變了,再切換回來時,錯誤數據就有了。面試
要解決上述的線程安全問題,錯誤數據問題。在一個線程進入到if中以後,當cpu切換到其餘線程上時,不讓其餘的線程進入if語句,那麼就算線程繼續執行當前其餘的線程,也沒法進入到if中,這樣就不會形成錯誤數據的出現。設計模式
Java中給咱們提供是同步代碼塊技術來解決這個線程的安全問題:api
格式:數組
synchronized(任意的對象(鎖) ) { 寫要被同步的代碼 }
同步代碼塊就能夠認爲成衛生間的門和鎖。安全
多線程安全問題,是因爲多個線程在訪問共享的數據(共享的資源),而且操做共享數據的語句不止一條。那麼這樣在多條操做共享數據的之間線程就可能發生切換。只要切換就有安全問題,那麼咱們就能夠把同步加在這些操做共享數據的代碼上。多線程
ThreadDemo.java源代碼併發
1 //書寫售票的示例 2 class Demo implements Runnable{ 3 //定義變量記錄剩餘的票數 4 int num = 100; 5 6 //建立一個對象,用做同步中的鎖對象 7 Object obj = new Object(); 8 9 //實現run方法 10 public void run() { 11 12 //實現售票的過程 13 while( true ) { 14 // t1 t2 t3 15 //判斷當前有沒有線程正在if中操做num,若是有當前線程就在這裏臨時等待 16 //這種思想稱爲線程的同步 17 //當票數小等於0的時候,就再也不售票了 18 //使用同步代碼塊把線程要執行的任務代碼能夠同步起來 19 synchronized( obj ) //t1 在進入同步以前線程要先獲取鎖 20 /* 21 當某個線程執行到synchronized關鍵字以後,這時JVM會判斷當前 22 同步上的這個對象有沒有已經被其餘線程獲取走了,若是這時沒有其餘 23 線程獲取這個對象,這時就會把當前同步上的這個對象交給當前正要進入 24 同步的這個線程。 25 */ 26 { 27 if( num > 0 ) 28 { 29 //t0 30 try{Thread.sleep(2);}catch( InterruptedException e ){} 31 System.out.println(Thread.currentThread().getName()+"....."+num); 32 num--; 33 } 34 }//線程執行完同步以後,那麼這時當前這個線程就會把鎖釋放掉 35 } 36 } 37 } 38 class ThreadDemo { 39 public static void main(String[] args) { 40 //建立線程任務 41 Demo d = new Demo(); 42 //建立線程對象 43 Thread t = new Thread( d ); 44 Thread t2 = new Thread(d); 45 Thread t3 = new Thread(d); 46 Thread t4 = new Thread(d); 47 //開啓線程 48 t.start(); 49 t2.start(); 50 t3.start(); 51 t4.start(); 52 } 53 }
好處:能夠保證多線程操做共享數據時的安全問題學習
弊端:下降了程序的執行效率。
要同步,必須有多個線程,多線程在操做共享的數據,同時操做共享數據的語句不止一條。
首先查看同步代碼塊的位置是否加在了須要被同步的代碼上。若是同步代碼的位置沒有錯誤,這時就再看同步代碼塊上使用的鎖對象是不是同一個。多個線程是否在共享同一把鎖。
方法上是能夠加同步的,可是不建議把同步加在run方法,若是把同步加在了run方法上,致使任何一個線程在調用start方法開啓以後,JVM去調用run方法的時候,首先都要先獲取同步的鎖對象,只有獲取到了同步的鎖對象以後,才能去執行run方法。而咱們在run中書寫的被多線程操做的代碼,永遠只會有一個線程在裏面執行。只有這個線程把這個run執行完,出去以後,把鎖釋放了,其餘某個線程才能進入到這個run執行。
同步代碼塊使用的鎖是任意對象(由使用者本身來手動的指定)。
非靜態的方法上加的同步使用的鎖是當前對象(當前調用這個方法的那個對象)。
靜態方法上使用的鎖是當前的class文件(當前這個方法所屬的class文件)。
演示同步代碼塊的鎖、同步方法的鎖 、靜態同步方法的鎖源代碼:
1 class Demo implements Runnable 2 { 3 static int num = 100; 4 boolean flag = true; 5 6 Object obj = new Object(); 7 8 public void run() 9 { 10 if( flag ) //使用判斷進行線程執行任務的代碼切換 11 { 12 while(true) 13 { //thread-0 14 synchronized( Demo.class ) 15 { 16 if( num > 0 ) 17 { 18 System.out.println(Thread.currentThread().getName()+"......"+num); 19 num--; 20 } 21 } 22 } 23 } 24 else 25 { 26 while(true) 27 { 28 //thread-1 29 show(); 30 } 31 } 32 } 33 /* 34 show方法中的全部代碼所有是須要被同步的代碼。這時就能夠把這個同步加載show方法 35 在方法上加同步的格式:直接在方法上書寫同步關鍵字便可 36 37 38 非靜態的方法在執行的時候須要被對象調用。 39 這個show方法是被當前的Demo對象調用的,這時在show方法上加的同步使用的鎖就是當前的 40 那個Demo對象。就是this 41 42 43 靜態方法上使用的鎖不是this,靜態方法執行時不須要對象。而靜態方法是被類名直接調用。 44 靜態方法上使用的鎖是當前的class文件 45 */ 46 public static synchronized void show() 47 { 48 if( num > 0 ) 49 { 50 System.out.println(Thread.currentThread().getName()+"================="+num); 51 num--; 52 } 53 } 54 } 55 56 class ThreadDemo3 57 { 58 public static void main(String[] args) 59 { 60 //建立任務 61 Demo d = new Demo(); 62 63 //建立線程 64 Thread t = new Thread(d); 65 Thread t2 = new Thread(d); 66 67 t.start(); 68 //程序是從主線程開始運行,在運行時,雖然開啓了thread-0線程, 69 //可是cpu可能不會馬上切換到thread-0線程上。 70 //爲了保證thread-0必定可以進入到if中執行,thread-1進入else執行 71 //讓主線程在開啓thread-0線程以後,讓主線程休眠 72 try{Thread.sleep(1);}catch(InterruptedException e){} 73 //把標記改成false,讓下一個線程進入的else中執行 74 d.flag = false; 75 t2.start(); 76 } 77 }
售票的例子使用Runnable接口實現並加同步:
1 //書寫售票的示例 2 class Demo implements Runnable{ 3 //定義變量記錄剩餘的票數 4 int num = 100; 5 6 //建立一個對象,用做同步中的鎖對象 7 Object obj = new Object(); 8 9 //實現run方法 10 public void run() { 11 12 //實現售票的過程 13 while( true ) { 14 // t1 t2 t3 15 //判斷當前有沒有線程正在if中操做num,若是有當前線程就在這裏臨時等待 16 //這種思想稱爲線程的同步 17 //當票數小等於0的時候,就再也不售票了 18 //使用同步代碼塊把線程要執行的任務代碼能夠同步起來 19 synchronized( obj ) //t1 在進入同步以前線程要先獲取鎖 20 /* 21 當某個線程執行到synchronized關鍵字以後,這時JVM會判斷當前 22 同步上的這個對象有沒有已經被其餘線程獲取走了,若是這時沒有其餘 23 線程獲取這個對象,這時就會把當前同步上的這個對象交給當前正要進入 24 同步的這個線程。 25 */ 26 { 27 if( num > 0 ) 28 { 29 //t0 30 try{Thread.sleep(2);}catch( InterruptedException e ){} 31 System.out.println(Thread.currentThread().getName()+"....."+num); 32 num--; 33 } 34 }//線程執行完同步以後,那麼這時當前這個線程就會把鎖釋放掉 35 36 37 } 38 } 39 } 40 class ThreadDemo { 41 public static void main(String[] args) { 42 //建立線程任務 43 Demo d = new Demo(); 44 //建立線程對象 45 Thread t = new Thread( d ); 46 Thread t2 = new Thread(d); 47 Thread t3 = new Thread(d); 48 Thread t4 = new Thread(d); 49 //開啓線程 50 t.start(); 51 t2.start(); 52 t3.start(); 53 t4.start(); 54 } 55 }
售票的例子使用Thread實現並加同步:
1 class Ticket extends Thread 2 { 3 static int num = 10000; 4 static Object obj = new Object(); 5 public void run() 6 { 7 while( num > 0 ) 8 { 9 synchronized(obj) 10 { 11 if( num > 0 ) 12 { 13 System.out.println(Thread.currentThread().getName()+"..."+num); 14 15 num--; 16 } 17 } 18 } 19 20 } 21 22 } 23 class ThreadDemo2 24 { 25 public static void main(String[] args) 26 { 27 Ticket t = new Ticket(); 28 Ticket t2 = new Ticket(); 29 Ticket t3 = new Ticket(); 30 Ticket t4 = new Ticket(); 31 32 t.start(); 33 t2.start(); 34 t3.start(); 35 t4.start(); 36 } 37 }
單例設計模式:保證當前程序中的這個類對象惟一。
單例設計模式代碼書寫步驟:
一、私有本類的構造方法
二、建立本類對象
三、對外提供訪問本類對象的方法
經常使用的模版代碼:
1 //餓漢式 2 class Single 3 { 4 private Single(){} 5 6 private static final Single s = new Single(); 7 8 public static Single getInstance() 9 { 10 return s; 11 } 12 13 } 14 15 16 //懶漢式 17 class Single 18 { 19 private Single(){} 20 21 private static Single s = null; 22 23 public static Single getInstance() 24 { 25 if( s == null ) 26 { 27 s = new Single(); 28 } 29 return s; 30 } 31 }
懶漢式的多線程併發訪問的安全問題:
1 //書寫單例類 2 class Single 3 { 4 private static Object obj = new Object(); 5 //私有構造方法 6 private Single() 7 { 8 } 9 //定義成員變量記錄當前本類的對象 10 private static Single s = null; 11 12 //對外提供方法訪問本類的對象 13 public static Single getInstance() 14 { 15 /* 16 這裏有安全問題,當多個線程進來以後,在執行判斷和建立對象之間可能出現 17 cpu在線程之間的切換,一旦cpu切換了線程,就會致使每一個線程都建立 18 一個當前單例類的對象,這樣就會致使當前的單例類再也不保證對象惟一了 19 */ 20 //t1 21 if( s == null )// 這裏加判斷的目的是保證後續來的線程不用在進入同步代碼塊中,這個能夠提升後續程序效率 22 { 23 synchronized( obj ) 24 { 25 //先判斷有沒有對象 26 if( s == null ) //判斷最後線程進入同步以後到底有沒有對象,只有在沒有對象的狀況下才能建立對象 27 { 28 s = new Single(); 29 } 30 } 31 //t0 32 } 33 //返回對象 34 return s; 35 } 36 } 37 //多線程併發訪問單例類獲取對象 38 class Demo implements Runnable 39 { 40 public void run() 41 { 42 Single s = Single.getInstance(); 43 System.out.println("s="+s); 44 } 45 } 46 class SingleThreadDemo 47 { 48 public static void main(String[] args) 49 { 50 //建立線程的任務 51 Demo d = new Demo(); 52 53 //建立線程對象 54 Thread t = new Thread(d); 55 Thread t2 = new Thread(d); 56 57 t.start(); 58 t2.start(); 59 } 60 }
死鎖:
在多線程執行任務的時候,可能出現須要獲取到多把鎖才能去完成某個任務。
Thread-0 、Thread-1
它們要完成線程的任務,而它們都須要獲取不一樣的鎖才能完成。
Thread-0 須要先獲取A鎖 再獲取B才能去完成任務
而Thread-1線程須要先獲取B鎖,在獲取A鎖才能完成任務。
這時Thread-0 獲取到了A的時候,CPU切換到Thread-1上,這時Thread-1就獲取到了B鎖。
這時就出現了2個線程要執行任務都須要獲取對方線程上的那個鎖。
死鎖示例源代碼:
1 //死鎖程序演示 2 class Demo implements Runnable 3 { 4 int num = 100; 5 //定義一個鎖 6 Object obj = new Object(); 7 8 //定義標記,讓兩個線程去不一樣的代碼塊中實現售票 9 boolean flag = true; 10 11 public void run() 12 { 13 if( flag ) 14 { 15 while( true ) 16 { 17 synchronized( obj ) 18 { 19 show(); 20 } 21 } 22 } 23 else 24 { 25 while(true) 26 { 27 show(); 28 } 29 } 30 } 31 public synchronized void show() 32 { 33 34 synchronized( obj ) 35 { 36 if( num > 0 ) 37 { 38 System.out.println(Thread.currentThread().getName()+"............"+num ); 39 num--; 40 } 41 } 42 } 43 } 44 class DeadLockDemo 45 { 46 public static void main(String[] args) 47 { 48 Demo d = new Demo(); 49 50 Thread t = new Thread(d); 51 Thread t2 = new Thread(d); 52 53 t.start(); 54 try{Thread.sleep(1);}catch(Exception e){} 55 d.flag = false; 56 t2.start(); 57 58 } 59 }
面試的死鎖程序源代碼:
1 //死鎖程序書寫 2 class Demo implements Runnable 3 { 4 private Object objA = new Object(); 5 private Object objB = new Object(); 6 //標記實現線程能夠切換 7 boolean flag = true; 8 //實現run方法 9 public void run() 10 { 11 //經過判斷flag標記目標是讓不一樣的線程在if和else中執行 12 if( flag ) 13 { 14 while(true) 15 { 16 synchronized( objA ) 17 { 18 System.out.println(Thread.currentThread().getName()+"....objA"); 19 //t0 20 synchronized( objB ) 21 { 22 System.out.println(Thread.currentThread().getName()+"....objB"); 23 } 24 } 25 } 26 } 27 else 28 { 29 while(true) 30 { 31 synchronized( objB ) 32 { 33 System.out.println(Thread.currentThread().getName()+"....objB"); 34 //t1 35 synchronized( objA ) 36 { 37 System.out.println(Thread.currentThread().getName()+"....objA"); 38 } 39 } 40 } 41 } 42 } 43 } 44 45 class DeadLock 46 { 47 public static void main(String[] args) 48 { 49 Demo d = new Demo(); 50 51 Thread t = new Thread(d); 52 Thread t2 = new Thread(d); 53 54 t.start(); 55 try{Thread.sleep(1);}catch(Exception e){} 56 d.flag = false; 57 t2.start(); 58 } 59 }
生產者和消費者程序:
生產者主要負責生產商品,消費者主要負責消費商品。假如生產者把生產者的商品要存放的容器中,而消費者要從容器中取出商品進行消費。
當生產者正在生產的時候,消費者不能來消費。或者是消費者正在消費的時候生產者不要來生產。
當生產者把容器存放滿了,這時生產者就不能在繼續給容器中存放商品,消費者若是把容器中的商品消費完了,消費者就不能在繼續消費。
分析生產者線程和消費者線程:
生產者線程主要負責給容器中存放商品,而消費者線程主要負責從容器中取出商品進行消費。生產者線程和消費者線程它們的線程任務不同。
因爲生產者和消費者線程任務不一樣,就沒法寫同一個run方法中,就須要書寫兩個run方法來封裝生產者的現場那個任務和封裝消費者的線程任務,
那麼就須要定義2個類。這個2個類分別負責生產者和消費者線程任務。
分析容器:
消費者線程任務對象一旦建立就必須明確本身消費的是那個容器中的商品,而生產者線程任務對象一建立,就必須明確本身生產的商品存放到哪一個容器中。
這時咱們能夠單獨定義一個類,這個類專門負責封裝容器。而且這個類須要對外提供給容器中存放商品的功能,和從容器中取出商品的功能。
單生產單消費代碼簡單實現:
1 /* 2 生產者和消費者 3 爲了程序簡單化,先研究單生產和單消費。 4 */ 5 6 //一個用來封裝容器的類 7 class Resource 8 { 9 //這裏定義一個容器,主要負責用來存放商品和取出商品 10 //定義了一個數組容器,這個容器中只能存放一個商品 11 private Object[] objs = new Object[1]; 12 13 private Object obj = new Object(); 14 15 //對外提供給容器中存放商品的方法 16 public void put( Object obj ) 17 { 18 objs[0] = obj; 19 try{Thread.sleep(1);}catch( InterruptedException e ){} 20 System.out.println(Thread.currentThread().getName()+"..存入的商品是..."+objs[0]); 21 } 22 //對外提供給容器中取出商品的方法 23 public void get() 24 { 25 System.out.println(Thread.currentThread().getName()+"----取出的商品是--------"+objs[0]); 26 27 objs[0] = null; 28 } 29 } 30 31 //一個類用來描述生產者線程的任務 32 class Producer implements Runnable 33 { 34 //定義一個成員變量記錄當前傳遞進來的資源對象 35 private Resource r; 36 //在一建立生產者任務對象的時候,就必須明確當前的商品要存放到那個容器資源中 37 Producer( Resource r ) 38 { 39 this.r = r; 40 } 41 //實現run方法,在run方法中完成生產線程的存放商品的動做 42 public void run() 43 { 44 //定義變量記錄當前生產的商品編號 45 int num = 1; 46 //生產者線程一旦進入run方法以後就不斷的來生產 47 while( true ) 48 { 49 r.put( "麪包"+num ); 50 num++; 51 } 52 } 53 } 54 55 //一個類用來描述消費者線程的任務 56 class Consumer implements Runnable 57 { 58 private Resource r; 59 Consumer( Resource r ) 60 { 61 this.r = r; 62 } 63 //run方法中封裝的消費者消費商品的任務代碼 64 public void run() 65 { 66 while( true ) 67 { 68 r.get(); 69 } 70 } 71 } 72 73 class ThreadDemo4 74 { 75 public static void main(String[] args) 76 { 77 //建立生產者或者消費者要操做的資源對象 78 Resource r = new Resource(); 79 80 //建立生產者任務對象 81 Producer pro = new Producer( r ); 82 //建立消費者任務對象 83 Consumer con = new Consumer( r ); 84 85 86 //建立線程對象 87 Thread t = new Thread( pro ); 88 Thread t2 = new Thread( con ); 89 90 t.start(); 91 t2.start(); 92 93 } 94 }
單生產單消費 中的同步實現:
/* 加入同步解決的是在存入的時候不能消費 在消費的時候不能存入。 */ //一個用來封裝容器的類 class Resource { //這裏定義一個容器,主要負責用來存放商品和取出商品 //定義了一個數組容器,這個容器中只能存放一個商品 private Object[] objs = new Object[1]; private Object obj = new Object(); //對外提供給容器中存放商品的方法 public void put( Object obj ) { //加入同步目的是保證生產者在生產的時候,消費者不能消費 synchronized( obj ) { objs[0] = obj; System.out.println(Thread.currentThread().getName()+"..存入的商品是..."+objs[0]); } } //對外提供給容器中取出商品的方法 public void get() { //加入同步目的是在消費者消費的時候生產者不要來生產 synchronized( obj ) { System.out.println(Thread.currentThread().getName()+"----取出的商品是--------"+objs[0]); objs[0] = null; } } } //一個類用來描述生產者線程的任務 class Producer implements Runnable { //定義一個成員變量記錄當前傳遞進來的資源對象 private Resource r; //在一建立生產者任務對象的時候,就必須明確當前的商品要存放到那個容器資源中 Producer( Resource r ) { this.r = r; } //實現run方法,在run方法中完成生產線程的存放商品的動做 public void run() { //定義變量記錄當前生產的商品編號 int num = 1; //生產者線程一旦進入run方法以後就不斷的來生產 while( true ) { r.put( "麪包"+num ); num++; } } } //一個類用來描述消費者線程的任務 class Consumer implements Runnable { private Resource r; Consumer( Resource r ) { this.r = r; } //run方法中封裝的消費者消費商品的任務代碼 public void run() { while( true ) { r.get(); } } } class ThreadDemo5 { public static void main(String[] args) { //建立生產者或者消費者要操做的資源對象 Resource r = new Resource(); //建立生產者任務對象 Producer pro = new Producer( r ); //建立消費者任務對象 Consumer con = new Consumer( r ); //建立線程對象 Thread t = new Thread( pro ); Thread t2 = new Thread( con ); t.start(); t2.start(); } }
單生產單消費 中的等待喚醒機制:
/* 解決問題: 因爲程序中給的容易僅僅只能存放一個商品, 當生產者正好把商品存放到容器中以後,這時cpu依然在執行生產者線程,不少有可能出現繼續執行生產者往 容器中存放商品的現象。這時一旦生產者第二次或者屢次存放商品前面已經存放進去的商品就沒有被消費 而被後續的商品給覆蓋了。 當消費者正好把容器中的商品消費完了,cpu還在執行消費者線程時,就可能發生消費者再次來消費, 可是容器中並無商品,這時就會出現消費null 解決思路: 在生產者生產商品的時候,首先不該該馬上給容器中就存放商品,而應該先判斷當前容器中 有沒有商品,若是有就不能存放,應該等待消費者來消費這個商品,當消費者消費完這個商品以後 生產者就能夠來把商品存放到容器中。 消費者來消費商品的時候,也不能直接去消費,也須要判斷當前的容器中有沒有商品,若是有 才能消費,若是沒有就須要等待。等待生產者給容器中存放商品。 在解決生產者和消費者問題的時候,咱們須要線程進行等待。如何讓線程等待呢? 在查閱api的時候在Thread類中沒有找到等待的方法,而在Object類中找到等待的方法。 在Java中咱們要讓那個線程等待,這個線程必須位於同步代碼塊中,只要線程位於同步中 那麼這個線程確定會持有當前同步的鎖對象。 咱們若是要讓某個線程從執行狀態改變爲等待的狀態,這時須要使用當前同步上的鎖對象, 這時這個鎖對象就知道當前是那個線程持有本身。這時這個鎖對象就有能力讓線程處於等待狀態。 一個鎖能夠監視多個線程。就可使用這個鎖讓當前線程等待。 鎖讓線程等待,那麼這個等待的方法應該屬於鎖的。而在同步代碼塊中,鎖又是任意的。 任何一個對象均可以做爲鎖,那麼任何一個對象都應該具有讓線程處於等待的功能。 任何對象都具有的功能,就須要抽取到Object類中。 */ //一個用來封裝容器的類 class Resource { //這裏定義一個容器,主要負責用來存放商品和取出商品 //定義了一個數組容器,這個容器中只能存放一個商品 private Object[] objs = new Object[1]; private Object obj_lock = new Object(); //對外提供給容器中存放商品的方法 public void put( Object obj ) { //加入同步目的是保證生產者在生產的時候,消費者不能消費 synchronized( obj_lock ) { //判斷當前容器中是否有商品 if( objs[0] != null ) { //讓生產者線程等待 try{obj_lock.wait();}catch(InterruptedException e){} } objs[0] = obj; System.out.println(Thread.currentThread().getName()+"..存入的商品是..."+objs[0]); //生產者在生產完商品以後,要通知消費來消費 obj_lock.notify(); } } //對外提供給容器中取出商品的方法 public void get() { //加入同步目的是在消費者消費的時候生產者不要來生產 synchronized( obj_lock ) { //判斷當前是否有商品,若是沒有消費者線程要等待 if( objs[0] == null ) { //讓消費者線程等待 try{obj_lock.wait();}catch(InterruptedException e){} } System.out.println(Thread.currentThread().getName()+"----取出的商品是--------"+objs[0]); objs[0] = null; //消費者在消費完商品以後,要通知生產來生產 obj_lock.notify(); } } } //一個類用來描述生產者線程的任務 class Producer implements Runnable { //定義一個成員變量記錄當前傳遞進來的資源對象 private Resource r; //在一建立生產者任務對象的時候,就必須明確當前的商品要存放到那個容器資源中 Producer( Resource r ) { this.r = r; } //實現run方法,在run方法中完成生產線程的存放商品的動做 public void run() { //定義變量記錄當前生產的商品編號 int num = 1; //生產者線程一旦進入run方法以後就不斷的來生產 while( true ) { r.put( "麪包"+num ); num++; } } } //一個類用來描述消費者線程的任務 class Consumer implements Runnable { private Resource r; Consumer( Resource r ) { this.r = r; } //run方法中封裝的消費者消費商品的任務代碼 public void run() { while( true ) { r.get(); } } } class ThreadDemo6 { public static void main(String[] args) { //建立生產者或者消費者要操做的資源對象 Resource r = new Resource(); //建立生產者任務對象 Producer pro = new Producer( r ); //建立消費者任務對象 Consumer con = new Consumer( r ); //建立線程對象 Thread t = new Thread( pro ); Thread t2 = new Thread( con ); t.start(); t2.start(); } }
多生產多消費 中的等待喚醒機制 須要把if改爲while 須要把notify改成notifyAll:
1 /* 2 程序修改爲多個生產者和多個消費以後,有出現了商品的覆蓋現象, 3 或者消費者不斷的消費null現象 4 致使這個緣由的問題,是生產者在喚醒的時候把另外的生產者喚醒了,而被喚醒的 5 生產者沒有去判斷還有沒有商品,直接就開始生產了。這樣就會出現商品的覆蓋。 6 7 消費者消費null的緣由是在消費者喚醒其餘的消費者,被喚醒的消費者也不去判斷有沒有 8 商品,就直接開始消費 9 10 解決方案: 11 在喚醒以後,從新去判斷有沒有商品便可。 12 這時只須要把判斷有沒有商品的if結構改成while這樣就能夠在每次喚醒以後,繼續進行 13 while的條件進行判斷 14 15 把if改成while以後死鎖了,緣由是生產者喚醒時喚醒了生產者或者消費者消費時喚醒了消費者。 16 這時就在喚醒以後,又回到while中判斷,當前被喚醒的繼續等待。就會出現死鎖現象。 17 解決方案:在喚醒的時候,不只喚醒本方,也須要喚醒對象。咱們這時可使用notifyAll方法喚醒當前 18 鎖對象上等待的全部線程。 19 20 */ 21 22 //一個用來封裝容器的類 23 class Resource 24 { 25 //這裏定義一個容器,主要負責用來存放商品和取出商品 26 //定義了一個數組容器,這個容器中只能存放一個商品 27 private Object[] objs = new Object[1]; 28 29 private Object obj_lock = new Object(); 30 31 int num = 1; 32 33 //對外提供給容器中存放商品的方法 34 public void put( Object obj ) 35 { 36 //加入同步目的是保證生產者在生產的時候,消費者不能消費 37 synchronized( obj_lock ) 38 { 39 //判斷當前容器中是否有商品 40 while( objs[0] != null ) 41 { 42 //讓生產者線程等待 43 try{obj_lock.wait();}catch(InterruptedException e){} // t1 //t0 44 } 45 46 objs[0] = obj+""+num; 47 num++; 48 49 System.out.println(Thread.currentThread().getName()+"..存入的商品是..."+objs[0]); 50 51 //生產者在生產完商品以後,要通知消費來消費 52 obj_lock.notifyAll(); 53 54 } 55 } 56 57 58 //對外提供給容器中取出商品的方法 59 public void get() 60 { 61 //加入同步目的是在消費者消費的時候生產者不要來生產 62 synchronized( obj_lock ) 63 { 64 //判斷當前是否有商品,若是沒有消費者線程要等待 65 while( objs[0] == null ) 66 { 67 //讓消費者線程等待 68 try{obj_lock.wait();}catch(InterruptedException e){} // t3 69 } 70 System.out.println(Thread.currentThread().getName()+"----取出的商品是--------"+objs[0]); 71 72 objs[0] = null; 73 74 //消費者在消費完商品以後,要通知生產來生產 75 76 obj_lock.notifyAll(); //t2 77 } 78 } 79 } 80 81 //一個類用來描述生產者線程的任務 82 class Producer implements Runnable 83 { 84 //定義一個成員變量記錄當前傳遞進來的資源對象 85 private Resource r; 86 //在一建立生產者任務對象的時候,就必須明確當前的商品要存放到那個容器資源中 87 Producer( Resource r ) 88 { 89 this.r = r; 90 } 91 //實現run方法,在run方法中完成生產線程的存放商品的動做 92 public void run() 93 { 94 //生產者線程一旦進入run方法以後就不斷的來生產 95 while( true ) 96 { 97 r.put( "麪包" ); 98 } 99 } 100 } 101 102 //一個類用來描述消費者線程的任務 103 class Consumer implements Runnable 104 { 105 private Resource r; 106 Consumer( Resource r ) 107 { 108 this.r = r; 109 } 110 //run方法中封裝的消費者消費商品的任務代碼 111 public void run() 112 { 113 while( true ) 114 { 115 r.get(); 116 } 117 } 118 } 119 120 class ThreadDemo7 121 { 122 public static void main(String[] args) 123 { 124 //建立生產者或者消費者要操做的資源對象 125 Resource r = new Resource(); 126 127 //建立生產者任務對象 128 Producer pro = new Producer( r ); 129 //建立消費者任務對象 130 Consumer con = new Consumer( r ); 131 132 133 //建立線程對象 134 Thread t = new Thread( pro ); 135 Thread t2 = new Thread( pro ); 136 137 Thread t3 = new Thread( con ); 138 Thread t4 = new Thread( con ); 139 140 t.start(); 141 t2.start(); 142 t3.start(); 143 t4.start(); 144 145 } 146 }
在學習生產者和消費者的時候在使用等待和喚醒機制的時候,發生了本方喚醒本方的。這樣致使程序的效率下降。
在jdk5中對等待喚醒以及鎖的機制進行了升級。
鎖的升級:
在jdk5以前同步中獲取鎖和釋放鎖都是隱式的。把隱式的鎖變成了顯示由程序員本身手動的獲取和釋放。
jdk5對鎖的描述:採用的接口來描述。在java.util.concurrent.locks包下有接口Lock它是專門放負責描述鎖這個事物。
Lock鎖代替了同步代碼塊。在多線程中可以使用同步代碼塊的地方均可以使用Lock接口來代替。
當咱們使用Lock接口代替同步代碼塊的時候,就須要程序員手動的來獲取鎖和釋放鎖,若是在程序中出現了獲取鎖以後,沒有釋放鎖,致使其餘程序沒法進入這個同步語句中。須要使用try-finally語句在finally中書寫釋放鎖的代碼。
在Lock接口中lock方法和unLock方法分別是獲取鎖和釋放鎖。
1 //使用JDK5的鎖完成售票的例子 2 import java.util.concurrent.locks.*; 3 class Ticket implements Runnable 4 { 5 int num = 100; 6 7 //建立鎖對象 8 Lock loc = new ReentrantLock(); //多態了 9 10 public void run() 11 { 12 while( true ) 13 { 14 //獲取鎖 15 loc.lock(); 16 try 17 { 18 if( num > 0 ) 19 { 20 System.out.println(Thread.currentThread().getName()+"............."+num); 21 num--; 22 } 23 } 24 finally 25 { 26 //釋放鎖 27 loc.unlock(); 28 } 29 } 30 } 31 } 32 33 class LockDemo 34 { 35 public static void main(String[] args) 36 { 37 //建立線程任務 38 Ticket t = new Ticket(); 39 40 //建立線程對象 41 Thread tt = new Thread(t); 42 Thread tt2 = new Thread(t); 43 Thread tt3 = new Thread(t); 44 Thread tt4 = new Thread(t); 45 46 //開啓線程 47 tt.start(); 48 tt2.start(); 49 tt3.start(); 50 tt4.start(); 51 } 52 }
JDK5對同步的鎖進行了升級,同時還對等待喚醒機制也進行了升級。
在JDK5以前等待喚醒機制的方法和當前鎖綁定在一塊兒。在編寫生產者和消費者任務的時候,因爲消費者和生產者使用的同一個鎖,那麼就會致使使用當前這個鎖來喚醒在這個鎖上等待的線程的時,就可能發生生產者喚醒生產者或者消費者喚醒消費者。
到了JDK5以後,鎖被Lock接口來描述,等待喚醒的方法也被從新封裝成一個類。專門使用一個類負責等待喚醒。JDK5中使用Condtion接口來專門負責描述等待喚醒機制。Condition代替了jdk5以前的Object類中的wait、notify、notifyAll方法。await方法代替了Object中wait方法、signal代替的notify、signalAll代替notifyAll方法。
當咱們須要使用等待喚醒的時候,就須要獲取到Condition對象,而後把這個Condition對象和Lock接口綁定在一塊兒便可。這樣就可使用一個Lock下面綁定多個Condition。在Lock接口有個方法newCondition這個方法就可讓一個Condtion對象和Lock接口對象綁定在一塊兒了。
1 //使用Lock代替同步,使用Condition接口代替等待和喚醒 2 import java.util.concurrent.locks.*; 3 class Resource 4 { 5 Object[] objs = new Object[1]; 6 //記錄存入的商品編號 7 int num = 1; 8 9 //獲取Lock鎖對象 10 Lock loc = new ReentrantLock(); 11 //定義生產者的等待喚醒對象(監視器對象 Condition) 12 Condition pro_con = loc.newCondition(); 13 //定義消費者者的等待喚醒對象(監視器對象 Condition) 14 Condition con_con = loc.newCondition(); 15 16 public void put( Object obj ) 17 { 18 //獲取鎖 19 loc.lock(); 20 try 21 { 22 //判斷有沒有商品 23 if( objs[0] !=null ) 24 { 25 //讓生產者等待 26 try{pro_con.await();}catch(Exception e){} 27 } 28 objs[0] = obj +"" + num; 29 num++; 30 System.out.println(Thread.currentThread().getName()+"........"+objs[0]); 31 //喚醒消費者 32 con_con.signal(); 33 34 } 35 finally 36 { 37 loc.unlock(); 38 } 39 40 } 41 public void get() 42 { 43 //獲取鎖 44 loc.lock(); 45 try 46 { 47 //判斷有沒有商品 48 if( objs[0] == null ) 49 { 50 //讓消費者等待 51 try{con_con.await();}catch(Exception e){} 52 } 53 54 System.out.println(Thread.currentThread().getName()+"================="+objs[0]); 55 objs[0]=null; 56 //喚醒生產者 57 pro_con.signal(); 58 } 59 finally 60 { 61 loc.unlock(); 62 } 63 } 64 65 } 66 class Producer implements Runnable 67 { 68 private Resource r; 69 public Producer( Resource r ) 70 { 71 this.r = r; 72 } 73 public void run() 74 { 75 while( true ) 76 { 77 r.put("方便麪"); 78 } 79 } 80 } 81 82 class Consumer implements Runnable 83 { 84 private Resource r; 85 public Consumer( Resource r ) 86 { 87 this.r = r; 88 } 89 public void run() 90 { 91 while( true ) 92 { 93 r.get(); 94 } 95 } 96 } 97 98 99 class LockConditionTest 100 { 101 public static void main(String[] args) 102 { 103 Resource r = new Resource(); 104 105 Producer pro = new Producer(r); 106 Consumer con = new Consumer(r); 107 108 Thread t = new Thread(pro); 109 Thread t2 = new Thread(pro); 110 Thread t3 = new Thread(con); 111 Thread t4 = new Thread(con); 112 113 t.start(); 114 t2.start(); 115 t3.start(); 116 t4.start(); 117 } 118 }