在JDK5的並發包中有一個子包為java.concurrent.locks,它下面定義了三個接口Lock,ReadWriteLock,Condition,分別為重入鎖,讀寫鎖,鎖條件判斷
LOCK:
Lock與java關鍵字(synchronized)具有相同的功能,但它更加靈活。ReentrantLock作為Lock接口的實現類,被作為Java語言中synchronized功能的替代,它們具有相同的內存意義,相同的鎖定,但在爭用條件下卻有更好的性能,此為它還有synchronized沒有提供的其它特性。但就實際應用來說,由於內部鎖是內置於Java虛擬機中的,它能夠進行優化,因此未來的性能改進可能更傾向於內部鎖,而不是重入鎖。綜上所述,除非你的應用程序需要發在Java 5.0上,或者需要使用重入鎖的可伸縮性,否則就應該選擇內部鎖。
總之,ReentrantLock鎖與Java內在鎖相比有下面的特點:
1)ReentrantLock必須在 finally 塊中釋放鎖,而使用synchronized同步,JVM 將確保鎖會獲得自動釋放。
2)與目前的 synchronized 實現相比,爭用下的 ReentrantLock 實現更具可伸縮性。
3)對於ReentrantLock ,可以有不止一個條件變量與它關聯。(Condition后面會說到)
4)允許選擇想要一個公平鎖,還是一個不公平鎖。(new ReentrantLock(false)非公平鎖,非公平鎖執行效率會更高)
5)除非你對 Lock 的某個高級特性有明確的需要,或者有明確的證據表明在特定情況下,同步已經成為可伸縮性的瓶頸,否則還是應當繼續使用synchronized。
6)Lock 類只是普通的類,JVM 不知道具體哪個線程擁有 Lock 對象。而且,幾乎每個開發人員都熟悉 synchronized,它可以在 JVM 的所有版本中工作。
Condition:
類似於java中原來線程交互所用的wait,notify和notifyAll方法在新的並發包中基於重入鎖機制引入了Condition接口,Condition
將 Object
監視器方法(wait
、notify
和 notifyAll
)分解成截然不同的對象,以便通過將這些對象與任意 Lock
實現組合使用,為每個對象提供多個等待 set(wait-set),就是多路等待。Condition 的方法與 wait 、notify 和 notifyAll 方法類似,分別命名為 await 、 signal和singalAll因為它們不能覆蓋Object上的對應方法。
下面是一個多路條件等待按順序執行的例子,給定一個0,按1,2,3,這樣的順序依次加一次,總共加10次,並輸出每次加后的結果,模擬讓多線程來做:
1 import java.util.concurrent.locks.Condition; 2 import java.util.concurrent.locks.Lock; 3 import java.util.concurrent.locks.ReentrantLock; 4 5 public class ConditionTest { 6 7 /** 8 * @param args 9 */ 10 public static void main(String[] args) { 11 for (int i = 0; i < 10; i++) { 12 new Thread(new Runnable() { 13 14 @Override 15 public void run() { 16 new Test1().sub1(); 17 18 } 19 }).start(); 20 21 new Thread(new Runnable() { 22 23 @Override 24 public void run() { 25 new Test1().sub2(); 26 27 } 28 }).start(); 29 30 new Thread(new Runnable() { 31 32 @Override 33 public void run() { 34 new Test1().sub3(); 35 36 } 37 }).start(); 38 } 39 } 40 41 } 42 43 class Test1 { 44 public static int i = 0; 45 public static int conNum = 1; 46 //此處注意鎖和condition也要定義成靜態的,不然就有多把鎖和condition了,相當於沒加一樣。 47 public static Lock lock = new ReentrantLock(false); 48 public static Condition con1 = lock.newCondition(); 49 public static Condition con2 = lock.newCondition(); 50 public static Condition con3 = lock.newCondition(); 51 52 // 方法1對i進行加1 53 public void sub1() { 54 lock.lock(); 55 try { 56 while (conNum != 1) { 57 try { 58 con1.await(); 59 } catch (InterruptedException e) { 60 e.printStackTrace(); 61 } 62 } 63 i++; 64 System.out.print(i + ","); 65 conNum = 2; 66 con2.signal(); 67 } finally { 68 lock.unlock(); 69 } 70 71 } 72 73 // 方法2對i加2 74 public void sub2() { 75 lock.lock(); 76 77 try { 78 while (conNum != 2) { 79 try { 80 con2.await(); 81 } catch (InterruptedException e) { 82 e.printStackTrace(); 83 } 84 } 85 i = i + 2; 86 System.out.print(i + ","); 87 conNum = 3; 88 con3.signal(); 89 } finally { 90 lock.unlock(); 91 } 92 93 } 94 95 // 方法3對i加3; 96 public void sub3() { 97 lock.lock(); 98 try { 99 while (conNum != 3) { 100 try { 101 con3.await(); 102 } catch (InterruptedException e) { 103 e.printStackTrace(); 104 } 105 } 106 i = i + 3; 107 System.out.print(i + ","); 108 conNum = 1; 109 con1.signal(); 110 } finally { 111 lock.unlock(); 112 } 113 114 } 115 116 }
ReadWriteLock:
ReadWriteLock 維護了一對相關的鎖,一個用於只讀操作,另一個用於寫入操作。只要沒有 writer,讀取鎖可以由多個 reader 線程同時保持。寫入鎖是獨占的。從理論上說,使用讀寫鎖所允許的並發性增強將帶來更大的性能提高。與互斥鎖相比,使用讀-寫鎖能否提升性能則取決於讀寫操作期間讀取數據相對於修改數據的頻率,以及數據的爭用——即在同一時間試圖對該數據執行讀取或寫入操作的線程數。
下面一個示例,模擬各有10個線程對數據源數據進行讀寫,從輸出結果可以看出實現了有序的讀寫
1 public class LockTest { 2 /** 3 * @param args 4 */ 5 public static void main(String[] args){ 6 //10個讀線程 7 for(int i=0;i<10;i++){ 8 new Thread(new Runnable() { 9 @Override 10 public void run() { 11 System.out.println(Thread.currentThread().getName()+"讀取到的數據為:"+new Data().get()); 12 } 13 }).start(); 14 } 15 //10個寫線程 16 for(int i=0;i<10;i++){ 17 final int num = i; 18 new Thread(new Runnable() { 19 @Override 20 public void run() { 21 new Data().set("world"+num); 22 } 23 }).start(); 24 } 25 } 26 27 28 29 } 30 //數據源 31 class Data{ 32 private static String name="hello"; 33 ReadWriteLock rwl = new ReentrantReadWriteLock(); 34 public String get(){ 35 rwl.readLock().lock(); 36 System.out.println(Thread.currentThread().getName()+"--讀取數據前:"); 37 38 try{try { 39 Thread.sleep(new Random().nextInt(3000)); 40 } catch (InterruptedException e) { 41 e.printStackTrace(); 42 } 43 return name; 44 }finally{ 45 System.out.println(Thread.currentThread().getName()+"--已經讀取完"); 46 rwl.readLock().unlock(); 47 } 48 49 } 50 51 public void set(String name){ 52 rwl.writeLock().lock(); 53 try{ 54 System.out.println(Thread.currentThread().getName()+">寫入數據前"); 55 try { 56 Thread.sleep(new Random().nextInt(3000)); 57 } catch (InterruptedException e) { 58 e.printStackTrace(); 59 } 60 this.name = name; 61 System.out.println(Thread.currentThread().getName()+">寫完數據"); 62 }finally{ 63 rwl.writeLock().unlock(); 64 } 65 66 } 67 }