Java 多執行緒之內建鎖與顯示鎖
Java中具有通過Synchronized實現的內建鎖,和ReentrantLock實現的顯示鎖,這兩種鎖各有各的好處,算是互有補充,今天就來做一個總結。
Synchronized
內建鎖獲得鎖和釋放鎖是隱式的,進入synchronized修飾的程式碼就獲得鎖,走出相應的程式碼就釋放鎖。
synchronized(list){ //獲得鎖 list.append(); list.count(); }//釋放鎖
通訊
與Synchronized配套使用的通訊方法通常有wait(),notify()。
wait()方法會立即釋放當前鎖,並進入等待狀態,等待到相應的notify並重新獲得鎖過後才能繼續執行;notify()不會立刻立刻釋放鎖,必須要等notify()所線上程執行完synchronized塊中的所有程式碼才會釋放。用如下程式碼來進行驗證:
public static void main(String[] args){ List list = new LinkedList(); Thread r = new Thread(new ReadList(list)); Thread w = new Thread(new WriteList(list)); r.start(); w.start(); } class ReadList implements Runnable{ private List list; public ReadList(List list){ this.list = list; } @Override public void run(){ System.out.println("ReadList begin at "+System.currentTimeMillis()); synchronized (list){ try { Thread.sleep(1000); System.out.println("list.wait() begin at "+System.currentTimeMillis()); list.wait(); System.out.println("list.wait() end at "+System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("ReadList end at "+System.currentTimeMillis()); } } class WriteList implements Runnable{ private List list; public WriteList(List list){ this.list = list; } @Override public void run(){ System.out.println("WriteList begin at "+System.currentTimeMillis()); synchronized (list){ System.out.println("get lock at "+System.currentTimeMillis()); list.notify(); System.out.println("list.notify() at "+System.currentTimeMillis()); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("get out of block at "+System.currentTimeMillis()); } System.out.println("WriteList end at "+System.currentTimeMillis()); } }
執行結果
ReadList begin at 1493650526582 WriteList begin at 1493650526582 list.wait() begin at 1493650527584 get lock at 1493650527584 list.notify() at 1493650527584 get out of block at 1493650529584 WriteList end at 1493650529584 list.wait() end at 1493650529584 ReadList end at 1493650529584
可見讀執行緒開始執行,開始wait過後,寫執行緒才獲得鎖;寫執行緒走出同步塊而不是notify過後,讀執行緒才wait結束,亦即獲得鎖。所以notify不會釋放鎖,wait會釋放鎖。值得一提的是,notifyall()會通知等待佇列中的所有執行緒。
編碼
編碼模式比較簡單,單一,不必顯示的獲得鎖,釋放鎖,能降低因粗心忘記釋放鎖的錯誤。使用模式如下:
synchronized(object){ }
靈活性
- 內建鎖在進入同步塊時,採取的是無限等待的策略,一旦開始等待,就既不能中斷也不能取消,容易產生飢餓與死鎖的問題
- 線上程呼叫notify方法時,會隨機選擇相應物件的等待佇列的一個執行緒將其喚醒,而不是按照FIFO的方式,如果有強烈的公平性要求,比如FIFO就無法滿足
效能
Synchronized在JDK1.5及之前效能(主要指吞吐率)比較差,擴充套件性也不如ReentrantLock。但是JDK1.6以後,修改了管理內建鎖的演算法,使得Synchronized和標準的ReentrantLock效能差別不大。
ReentrantLock
ReentrantLock是顯示鎖,需要顯示進行 lock 以及 unlock 操作。
通訊
與ReentrantLock搭配的通行方式是Condition,如下:
private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); condition.await();//this.wait(); condition.signal();//this.notify(); condition.signalAll();//this.notifyAll();
Condition是被繫結到Lock上的,必須使用lock.newCondition()才能建立一個Condition。從上面的程式碼可以看出,Synchronized能實現的通訊方式,Condition都可以實現,功能類似的程式碼寫在同一行中。而Condition的優秀之處在於它可以為多個執行緒間建立不同的Condition,比如物件的讀/寫Condition,佇列的空/滿Condition,在JDK原始碼中的ArrayBlockingQueue中就使用了這個特性:
public ArrayBlockingQueue(int capacity, boolean fair) { if (capacity <= 0) throw new IllegalArgumentException(); this.items = new Object[capacity]; lock = new ReentrantLock(fair); notEmpty = lock.newCondition(); notFull = lock.newCondition(); } public void put(E e) throws InterruptedException { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == items.length) notFull.await(); enqueue(e); } finally { lock.unlock(); } } public E take() throws InterruptedException { final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { while (count == 0) notEmpty.await(); return dequeue(); } finally { lock.unlock(); } } private void enqueue(E x) { // assert lock.getHoldCount() == 1; // assert items[putIndex] == null; final Object[] items = this.items; items[putIndex] = x; if (++putIndex == items.length) putIndex = 0; count++; notEmpty.signal(); } private E dequeue() { // assert lock.getHoldCount() == 1; // assert items[takeIndex] != null; final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); notFull.signal(); return x; }
編碼
Lock lock = new ReentrantLock(); lock.lock(); try{ }finally{ lock.unlock(); }
相比於Synchronized要複雜一些,而且一定要記得在finally中釋放鎖而不是其他地方,這樣才能保證即使出了異常也能釋放鎖。
靈活性
- lock.lockInterruptibly() 可以使得執行緒在等待鎖是支援響應中斷;lock.tryLock() 可以使得執行緒在等待一段時間過後如果還未獲得鎖就停止等待而非一直等待。有了這兩種機制就可以更好的制定獲得鎖的重試機制,而非盲目一直等待,可以更好的避免飢餓和死鎖問題
- ReentrantLock可以成為公平鎖(非預設的),所謂公平鎖就是鎖的等待佇列的FIFO,不過公平鎖會帶來效能消耗,如果不是必須的不建議使用。這和CPU對指令進行重排序的理由是相似的,如果強行的按照程式碼的書寫順序來執行指令,就會浪費許多時鐘週期,達不到最大利用率
效能
雖然Synchronized和標準的ReentrantLock效能差別不大,但是ReentrantLock還提供了一種非互斥的讀寫鎖,
也就是不強制每次最多隻有一個執行緒能持有鎖,它會避免“讀/寫”衝突,“寫/寫”衝突,但是不會排除“讀/讀”衝突,
因為“讀/讀”並不影響資料的完整性,所以可以多個讀執行緒同時持有鎖,這樣在讀寫比較高的情況下,效能會有很大的提升。
下面用兩種鎖分別實現的執行緒安全的linkedlist:
class RWLockList {//讀寫鎖 private List list; private final ReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); public RWLockList(List list){this.list = list;} public int get(int k) { readLock.lock(); try { return (int)list.get(k); } finally { readLock.unlock(); } } public void put(int value) { writeLock.lock(); try { list.add(value); } finally { writeLock.unlock(); } } } class SyncList { private List list; public SyncList(List list){this.list = list;} public synchronized int get(int k){ return (int)list.get(k); } public synchronized void put(int value){ list.add(value); } }
讀寫鎖測試程式碼:
List list = new LinkedList(); for (int i=0;i<10000;i++){ list.add(i); } RWLockList rwLockList = new RWLockList(list);//初始化資料 Thread writer = new Thread(new Runnable() { @Override public void run() { for (int i=0;i<10000;i++){ rwLockList.put(i); } } }); Thread reader1 = new Thread(new Runnable() { @Override public void run() { for (int i=0;i<10000;i++){ rwLockList.get(i); } } }); Thread reader2 = new Thread(new Runnable() { @Override public void run() { for (int i=0;i<10000;i++){ rwLockList.get(i); } } }); long begin = System.currentTimeMillis(); writer.start();reader1.start();reader2.start(); try { writer.join(); reader1.join(); reader2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("RWLockList take "+(System.currentTimeMillis()-begin) + "ms");
同步鎖測試程式碼:
List list = new LinkedList(); for (int i=0;i<10000;i++){ list.add(i); } SyncList syncList = new SyncList(list);//初始化資料 Thread writerS = new Thread(new Runnable() { @Override public void run() { for (int i=0;i<10000;i++){ syncList.put(i); } } }); Thread reader1S = new Thread(new Runnable() { @Override public void run() { for (int i=0;i<10000;i++){ syncList.get(i); } } }); Thread reader2S = new Thread(new Runnable() { @Override public void run() { for (int i=0;i<10000;i++){ syncList.get(i); } } }); long begin1 = System.currentTimeMillis(); writerS.start();reader1S.start();reader2S.start(); try { writerS.join(); reader1S.join(); reader2S.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("SyncList take "+(System.currentTimeMillis()-begin1) + "ms");
結果:
RWLockList take 248ms RWLockList take 255ms RWLockList take 249ms RWLockList take 224ms SyncList take 351ms SyncList take 367ms SyncList take 315ms SyncList take 323ms
可見讀寫鎖的確是優於純碎的互斥鎖
總結
內建鎖最大優點是簡潔易用,顯示鎖最大優點是功能豐富,所以能用內建鎖就用內建鎖,在內建鎖功能不能滿足之時在考慮顯示鎖。
關於兩種鎖,目前接觸到的就是這麼多,總結不到位之處,歡迎拍磚。
相關文章
- 多執行緒(二)、內建鎖 synchronized執行緒synchronized
- JAVA多執行緒與鎖機制Java執行緒
- Java多執行緒(2)執行緒鎖Java執行緒
- Java多執行緒中執行緒安全與鎖問題Java執行緒
- Java多執行緒 -- 公平鎖和非公平鎖Java執行緒
- Java 執行緒安全 與 鎖Java執行緒
- Java多執行緒(五):死鎖Java執行緒
- java多執行緒(5)死鎖Java執行緒
- Java多執行緒-鎖的區別與使用Java執行緒
- 多執行緒_鎖執行緒
- Python多執行緒與GIL鎖Python執行緒
- 多執行緒之8鎖問題執行緒
- JAVA多執行緒詳解(3)執行緒同步和鎖Java執行緒
- python之執行緒鎖Python執行緒
- 多執行緒與併發----讀寫鎖執行緒
- Java多執行緒程式設計—鎖優化Java執行緒程式設計優化
- 畫江湖之 PHP 多執行緒開發 【執行緒安全 互斥鎖】PHP執行緒
- 畫江湖之 PHP 多執行緒開發 [執行緒安全 互斥鎖]PHP執行緒
- 多執行緒(2)-執行緒同步互斥鎖Mutex執行緒Mutex
- iOS多執行緒安全-13種執行緒鎖?iOS執行緒
- Java多執行緒的悲觀鎖與樂觀鎖及各自適用場景Java執行緒
- Linux之執行緒互斥鎖Linux執行緒
- 多執行緒與併發-----Lock鎖技術執行緒
- java多執行緒中的死鎖、活鎖、飢餓、無鎖都是什麼鬼?Java執行緒
- java多執行緒:執行緒同步synchronized(不同步的問題、佇列與鎖),死鎖的產生和解決Java執行緒synchronized佇列
- 多執行緒安全性和Java中的鎖執行緒Java
- Java多執行緒學習(六)Lock鎖的使用Java執行緒
- JAVA CPU100%與執行緒死鎖定位Java執行緒
- threading多執行緒資源加鎖thread執行緒
- 執行緒和鎖,鎖升級執行緒
- 執行緒同步與互斥:互斥鎖執行緒
- 執行緒鎖(四)執行緒
- iOS底層原理 多執行緒之安全鎖以及常用的讀寫鎖 --(11)iOS執行緒
- Java併發(十六)----執行緒八鎖Java執行緒
- Java執行緒狀態及同步鎖Java執行緒
- java執行緒的狀態+鎖分析Java執行緒
- 【多執行緒與高併發3】常用鎖例項執行緒
- Java多執行緒之執行緒中止Java執行緒
- Linux多執行緒的使用一:互斥鎖Linux執行緒