synchronized和lock比較
一、synchronized的實現方案
1.synchronized能夠把任何一個非null對象當成鎖,實現由兩種方式:
a.當synchronized作用於非靜態方法時,鎖住的是當前對象的事例,當synchronized作用於靜態方法時,鎖住的是class實例,又因為Class的相關數據存儲在永久帶,因此靜態方法鎖相當於類的一個全局鎖。
b.當synchronized作用於一個對象實例時,鎖住的是對應的代碼塊。
2.synchronized鎖又稱為對象監視器(object)。
3.當多個線程一起訪問某個對象監視器的時候,對象監視器會將這些請求存儲在不同的容器中。
>Contention List:競爭隊列,所有請求鎖的線程首先被放在這個競爭隊列中
>Entry List:Contention List中那些有資格成為候選資源的線程被移動到Entry List中
>Wait Set:哪些調用wait方法被阻塞的線程被放置在這裏
>OnDeck:任意時刻,最多只有一個線程正在競爭鎖資源,該線程被成為OnDeck
>Owner:當前已經獲取到所資源的線程被稱為Owner
> !Owner:當前釋放鎖的線程
下圖展示了他們之前的關系
二、lock的實現方案
與synchronized不同的是lock是純java手寫的,與底層的JVM無關。在java.util.concurrent.locks包中有很多Lock的實現類,常用的有ReenTrantLock、ReadWriteLock(實現類有ReenTrantReadWriteLock)
,其實現都依賴java.util.concurrent.AbstractQueuedSynchronizer類(簡稱AQS),實現思路都大同小異,因此我們以ReentrantLock作為講解切入點。
分析之前我們先來花點時間看下AQS。AQS是我們後面將要提到的CountDownLatch/FutureTask/ReentrantLock/RenntrantReadWriteLock/Semaphore的基礎,因此AQS也是Lock和Excutor實現的基礎。它的基本思想就是一個同步器,支持獲取鎖和釋放鎖兩個操作。
要支持上面鎖獲取、釋放鎖就必須滿足下面的條件:
1、 狀態位必須是原子操作的
2、 阻塞和喚醒線程
3、 一個有序的隊列,用於支持鎖的公平性
場景:可定時的、可輪詢的與可中斷的鎖獲取操作,公平隊列,或者非塊結構的鎖。
使用用法介紹
public class Test { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); private Lock lock = new ReentrantLock(); //註意這個地方 public static void main(String[] args) { final Test test = new Test(); new Thread(){ public void run() { test.insert(Thread.currentThread()); }; }.start(); new Thread(){ public void run() { test.insert(Thread.currentThread()); }; }.start(); } public void insert(Thread thread) { lock.lock(); try { System.out.println(thread.getName()+"得到了鎖"); for(int i=0;i<5;i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception }finally { System.out.println(thread.getName()+"釋放了鎖"); lock.unlock(); } } }
tryLock()的使用方法
public class Test { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); private Lock lock = new ReentrantLock(); //註意這個地方 public static void main(String[] args) { final Test test = new Test(); new Thread(){ public void run() { test.insert(Thread.currentThread()); }; }.start(); new Thread(){ public void run() { test.insert(Thread.currentThread()); }; }.start(); } public void insert(Thread thread) { if(lock.tryLock()) { try { System.out.println(thread.getName()+"得到了鎖"); for(int i=0;i<5;i++) { arrayList.add(i); } } catch (Exception e) { // TODO: handle exception }finally { System.out.println(thread.getName()+"釋放了鎖"); lock.unlock(); } } else { System.out.println(thread.getName()+"獲取鎖失敗"); } } }
lockInterruptibly()
public class Test { private Lock lock = new ReentrantLock(); public static void main(String[] args) { Test test = new Test(); MyThread thread1 = new MyThread(test); MyThread thread2 = new MyThread(test); thread1.start(); thread2.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } thread2.interrupt(); } public void insert(Thread thread) throws InterruptedException{ lock.lockInterruptibly(); //註意,如果需要正確中斷等待鎖的線程,必須將獲取鎖放在外面,然後將InterruptedException拋出 try { System.out.println(thread.getName()+"得到了鎖"); long startTime = System.currentTimeMillis(); for( ; ;) { if(System.currentTimeMillis() - startTime >= Integer.MAX_VALUE) break; //插入數據 } } finally { System.out.println(Thread.currentThread().getName()+"執行finally"); lock.unlock(); System.out.println(thread.getName()+"釋放了鎖"); } } } class MyThread extends Thread { private Test test = null; public MyThread(Test test) { this.test = test; } @Override public void run() { try { test.insert(Thread.currentThread()); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+"被中斷"); } } }
ReentrantReadWriteLock
public class Test { private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public static void main(String[] args) { final Test test = new Test(); new Thread(){ public void run() { test.get(Thread.currentThread()); }; }.start(); new Thread(){ public void run() { test.get(Thread.currentThread()); }; }.start(); } public void get(Thread thread) { rwl.readLock().lock(); try { long start = System.currentTimeMillis(); while(System.currentTimeMillis() - start <= 1) { System.out.println(thread.getName()+"正在進行讀操作"); } System.out.println(thread.getName()+"讀操作完畢"); } finally { rwl.readLock().unlock(); } } }
另外在ReentrantLock類中定義了很多方法,比如:
isFair() //判斷鎖是否是公平鎖
isLocked() //判斷鎖是否被任何線程獲取了
isHeldByCurrentThread() //判斷鎖是否被當前線程獲取了
hasQueuedThreads() //判斷是否有線程在等待該鎖
在ReentrantReadWriteLock中也有類似的方法,同樣也可以設置為公平鎖和非公平鎖。不過要記住,ReentrantReadWriteLock並未實現Lock接口,它實現的是ReadWriteLock接口。
主要從以下幾個特點介紹:
1.可重入鎖
如果鎖具備可重入性,則稱作為可重入鎖。像synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明了鎖的分配機制:基於線程的分配,而不是基於方法調用的分配。
2.可中斷鎖
可中斷鎖:顧名思義,就是可以相應中斷的鎖。
在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。
如果某一線程A正在執行鎖中的代碼,另一線程B正在等待獲取該鎖,可能由於等待時間過長,線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,這種就是可中斷鎖。
3.公平鎖和非公平鎖
公平鎖以請求鎖的順序來獲取鎖,非公平鎖則是無法保證按照請求的順序執行。synchronized就是非公平鎖,它無法保證等待的線程獲取鎖的順序。而對於ReentrantLock和ReentrantReadWriteLock,它默認情況下是非公平鎖,但是可以設置為公平鎖。
參數為true時表示公平鎖,不傳或者false都是為非公平鎖。
ReentrantLock lock = new ReentrantLock(true);
4.讀寫鎖
讀寫鎖將對一個資源(比如文件)的訪問分成了2個鎖,一個讀鎖和一個寫鎖。
正因為有了讀寫鎖,才使得多個線程之間的讀操作不會發生沖突。
ReadWriteLock就是讀寫鎖,它是一個接口,ReentrantReadWriteLock實現了這個接口。
可以通過readLock()獲取讀鎖,通過writeLock()獲取寫鎖。
三、總結
1.synchronized
優點:實現簡單,語義清晰,便於JVM堆棧跟蹤,加鎖解鎖過程由JVM自動控制,提供了多種優化方案,使用更廣泛
缺點:悲觀的排他鎖,不能進行高級功能
2.lock
優點:可定時的、可輪詢的與可中斷的鎖獲取操作,提供了讀寫鎖、公平鎖和非公平鎖
缺點:需手動釋放鎖unlock,不適合JVM進行堆棧跟蹤
3.相同點
都是可重入鎖
ReenTrantLock獨有的能力:
1. ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先獲得鎖。
2. ReenTrantLock提供了一個Condition(條件)類,用來實現分組喚醒需要喚醒的線程們,而不是像synchronized要麽隨機喚醒一個線程要麽喚醒全部線程。
3. ReenTrantLock提供了一種能夠中斷等待鎖的線程的機制,通過lock.lockInterruptibly()來實現這個機制。
什麽情況下使用ReenTrantLock:
答案是,如果你需要實現ReenTrantLock的三個獨有功能時。
參考:
https://www.cnblogs.com/jiangds/p/6476293.html
https://www.cnblogs.com/handsomeye/p/5999362.html
https://www.cnblogs.com/baizhanshi/p/7211802.html
synchronized和lock比較