2 線程死鎖
死鎖(dead lock)是指兩個或多個線程都有權限訪問兩個或多個對象,並且每個線程都在已經獲得某個對象鎖的情況下等待其它線程已經得到的鎖。假設線程A持有對象X的鎖,並且正在試圖獲得對象Y的鎖,同時,線程B已經擁有對象Y的鎖,並在試圖獲得對象X的鎖。此時因為線程互相等待釋放鎖而彼此都無法繼續操作,死鎖就產生了。一下是個死鎖的例子:
public class Dummy { // private long value; public Dummy(long value){ this.value = value; } public synchronized long getValue(){ return this.value; } public synchronized void setValue(long value){ this.value = value; } public synchronized void swap(Dummy other){ long t = this.getValue(); long v = other.getValue(); this.setValue(v); other.setValue(t); } public static void main(String[] args) throws Exception{ final Dummy d1 = new Dummy(100); final Dummy d2 = new Dummy(200); Thread t1 = new Thread(){ public void run(){ long count = 0; try { while(true){ d1.swap(d2); count++; if(count % 100 == 0){ System.out.println("current thread " + Thread.currentThread().getName() + " process " + count); } } } catch (Exception e) { e.printStackTrace(); } } }; t1.setName("thread1"); Thread t2 = new Thread(){ public void run(){ long count = 0; try { while(true){ d2.swap(d1); count++; if(count % 100 == 0){ System.out.println("current thread " + Thread.currentThread().getName() + " process " + count); } } } catch (Exception e) { e.printStackTrace(); } } }; t2.setName("thread2"); // t1.start(); t2.start(); // wait thread to die t1.join(); t2.join(); } }以上程序按照下面的時序執行,就會產生死鎖:
線程A線程B進入d1.swap(d2)時獲得d1的鎖在執行t = this.getValue()時,順利獲得d1的鎖(因為已經持有)進入d2.swap(d1)時獲得d2的鎖執行v = other.getValue()時,由於需要d2的鎖而處於等待的狀態在執行t = this.getValue()時,順利獲得d2的鎖執行v = other.getValue()時,由於需要d1的鎖而處於等待狀態為了避免死鎖的發生,在一個被同步的區域內,不要調用一個可被改寫的共有的或受保護的方法。 另外一種比較簡單的避免死鎖的獨占技術是順序化資源。它的思想就是把一個嵌套的synchronized方法或塊中使用的對象和一個數字標簽關聯起來。如果同步操作是根據對象標簽的最小優先(least first)的原則,那麼剛才介紹的例子的情況就不會發生。也就是說,如果線程A和線程B都按照相同的順序獲得鎖,就可以避免死鎖的發生。對於數字標簽的選擇,可以使用System.identityHashCode的返回值,盡管沒有什麼機制可以保證identityHashCode的惟一性,但是在實際運行的系統中,這個方法的惟一性在很大程度上得到了保證。swap的一個更好的實現如下:
為了避免死鎖的發生,在一個被同步的區域內,不要調用一個可被改寫的共有的或受保護的方法。
另外一種比較簡單的避免死鎖的獨占技術是順序化資源。它的思想就是把一個嵌套的synchronized方法或塊中使用的對象和一個數字標簽關聯起來。如果同步操作是根據對象標簽的最小優先(least first)的原則,那麼剛才介紹的例子的情況就不會發生。也就是說,如果線程A和線程B都按照相同的順序獲得鎖,就可以避免死鎖的發生。對於數字標簽的選擇,可以使用System.identityHashCode的返回值,盡管沒有什麼機制可以保證identityHashCode的惟一性,但是在實際運行的系統中,這個方法的惟一性在很大程度上得到了保證。swap的一個更好的實現如下:
public void swap(Dummy other) { if(this == other) return; // Alias check else if(System.identityHashCode(this){ this.doSwap(other); } else { other.doSwap(this); } } private synchronized void doSwap(Dummy Other) { long t = getValue(); long v = other.getValue(); setValue(v); other.setValue(t); }2.1 生產者和消費者
生產者與消費者模型中,要保證以下幾點:
1) 同一時間內只能有一個生產者生產
2) 同一時間內只能有一個消費者消費
3) 生產者生產的同時消費者不能消費
4) 消息隊列滿時生產者不能繼續生產
5) 消息隊列空時消費者不能繼續消費
以下是一個經典的生產者消費者例子:
class Producer implements Runnable{ // private Message msg = null; public Producer(Message msg){ this.msg = msg; } public void run(){ for (int x = 0; x < 100; x++) { this.msg.set("message " + x); } } } class Consumer implements Runnable{ // private Message msg = null; public Consumer(Message msg){ this.msg = msg; } public void run(){ for (int x = 0; x < 100; x++) { this.msg.get(); } } } class Message { // private String message = "unknow"; private boolean flag = false; // if false get, true set public synchronized void get() { if (flag == true) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("message is " + this.message); flag = true; notify(); } public synchronized void set(String message) { if (flag == false) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.message = message; flag = false; notify(); } } public class Test { public static void main(String[] args) { Message msg = new Message(); Producer p = new Producer(msg); Consumer c = new Consumer(msg); new Thread(p).start(); new Thread(c).start(); } }2.2 synchronized關鍵字與volatile關鍵字
把代碼塊聲明為 synchronized,通常是指該代碼具有原子性(atomicity)和可見性(visibility)。原子性,即鎖的互斥性,意味著一個線程一次只能執行由一個指定監控對象(lock)保護的代碼,從而防止多個線程在更新共享狀態時相互沖突。可見性指某一線程對變量所做的更新,當進入由同一監控器(lock)保護的另一個 synchronized 塊時,將立刻可以看到這些對變量所做的更新。
volatile 變量具有 synchronized 的可見性特性,但是不具備原子特性。這就是說線程能夠自動發現 volatile 變量的最新值。volatile變量可以被看作是一種“輕量級的synchronized“,與synchronized塊相比,volatile所需的編碼較少,並且運行時的開銷也小,但是其所能實現的功能僅是synchronized的一部分。volatile變量可用於提供線程安全,但是必須同時滿足下面兩個條件:
1)對變量的寫操作不依賴於當前值。
2)該變量沒有包含在具有其他變量的不變式中。
然而,大多數編程情形都會與這兩個條件的其中之一沖突,使得 volatile 變量不能像 synchronized 那樣普遍適用於實現線程安全。
2.3 Java內存模型
Java Memory Model分為主內存(main memory)和工作內存(working memory)。Java中所有變量都保存在主內存中,供所有線程共享。每個線程都有自己的工作內存,工作內存中保存的是主內存中某些變量的拷貝。線程對所有變量的操作都是在工作內存中進行,線程之間無法相互直接訪問,變量傳遞均需要通過主存完成。內存模型有兩個特征:
1)可見性
2)有序性
在JMM中,可見性:通過並發線程修改變量值, 必須將線程變量同步回主存後, 其他線程才能訪問到.
在JMM中,有序性:通過Java提供的同步機制或volatile關鍵字, 來保證內存的訪問順序.
對synchronized:
當線程要進入synchronized時,如果工作存儲器在有未映像到主存儲器的工作拷貝,該內容就會被強制寫入主存儲器,因此之前的計算結果就會被全部的寫入主存儲器內,成為其他線程可以看得見(visible)的狀態。當線程欲退出synchronized時,會執行相同與進入synchronized時強制寫入主內存儲器的處理。
對volatile:
當線程欲引用volatile字段的值時,通常都會發生從主存儲器到工作存儲器的拷貝操作,而相反,將值指定給寫著volatile的字段後,工作存儲器的內容通常便會映像到主存儲器。