上篇文章已經對多線程有個初步的認識了,此次咱們來看看Java的Lock鎖,主要有如下知識點:java
AQS
ReentrantLock
ReentrantReadWriteLock
Lock和synchronized的選擇
在學習Lock鎖以前,咱們先來看看什麼是AQS?算法
ReadWriteLock
。注意:ReentrantLock不是AQS的子類,其內部類Sync纔是AQS的子類。
AQS維護了一個volatile int
類型的state
變量,用來表示當前同步狀態。
volatile雖然不能保證操做的原子性,可是保證了當前變量state的可見性。多線程
compareAndSetState
compareAndSetState用來修改state狀態,它是一個原子操做,底層實際上是調用系統的CAS算法,有關CAS可移步:CAS框架
protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
acquire
acquire(int arg) 以獨佔方式獲取資源,若是獲取到資源,線程直接返回,不然進入等待隊列,直到獲取到資源爲止,且整個過程忽略中斷的影響。函數
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
若是tryAcquire(int)
方法返回true,則acquire直接返回,不然當前線程須要進入隊列進行排隊。addWaiter()
將該線程加入等待隊列的尾部,並標記爲獨佔模式;學習
學習ReentrantLock以前先來看看它實現的Lock接口ui
public interface Lock { void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); }
ReentrantLock
,意思是"可重入鎖
",線程能夠重複地得到已經持有的鎖。 ReentrantLock是惟一實現了Lock接口的類。接下來咱們來看看有關源碼:this
ReentrantLock實現了三個內部類,分別是Sync、NonfairSync和FairSync。spa
abstract static class Sync extends AbstractQueuedSynchronizer static final class NonfairSync extends Sync static final class FairSync extends Sync
這些內部類都是AQS的子類,這就印證了咱們以前所說的:AQS是ReentrantLock的基礎,AQS是構建鎖的框架.線程
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
默認實現的是非公平鎖,傳入true表示使用公平鎖。
lock
方法非公平鎖
的lock方法加鎖流程
首先會經過CAS
方法,嘗試將當前的AQS中的State
字段改爲從0改爲1,若是修改爲功的話,說明原來的狀態是0,並無線程佔用鎖,並且成功的獲取了鎖,只須要調用setExclusiveOwnerThread
函將當前線程設置成持有鎖的線程便可。不然,CAS
操做失敗以後,和普通鎖同樣,調用父類AQS的acquire(1)
函數嘗試獲取鎖。
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; final void lock() { if (compareAndSetState(0, 1))//嘗試獲取鎖 setExclusiveOwnerThread(Thread.currentThread()); else //獲取失敗則調用AQS的acquire方法 acquire(1); }
而在AQS的acquire(1)
函數中,會判斷tryAcquire(1)
以及acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
,若是嘗試獲取失敗而且添加隊列成功的話,那麼就會調用selfInterrupt
函數中斷線程執行,說明已經加入到了AQS的隊列中。
注意:AQS的tryAcquire(1)
是由子類Sync(也就是ReentrantLockd的靜態內部類)本身實現的,也就是用到了模板方法,接下來咱們去看看子類的實現。
tryAcquire
是在NonfairSync
類中實現的,其中調用了nonfairTryAcquire
函數。
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) {//獲取當前線程狀態 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) {//可重入鎖 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
在nonfairTryAcquire
函數中,會嘗試讓當前線程去獲取鎖:
CAS
操做,將當前的狀態設置成acquires
,若是設置成功了的話,那麼則將當前線程設置成鎖持有的線程,而且返回true,表示獲取成功。0
的話,說明已經有線程持有鎖,則判斷當前線程與持有鎖的線程是否相同,若是相同的話,則將當前的狀態加上acquires從新將狀態設置,而且返回true,這也就是重入鎖
的緣由。源碼參考:ReentrantLock中的NonfairSync加鎖流程
咱們知道synchronized內置鎖和ReentrantLock都是互斥鎖
(一次只能有一個線程進入到臨界區(被鎖定的區域))
而ReentrantReadWriteLock是一個讀寫鎖
:
通常來講:咱們大多數都是讀取數據得多,修改數據得少。因此這個讀寫鎖在這種場景下就頗有用了!
ReentrantReadWriteLock
實現了ReadWriteLock
接口.
接口只有兩個方法,一個用來獲取讀鎖,一個用來獲取寫鎖。也就是說將文件的讀寫操做分開,分紅2個鎖來分配給線程,從而使得多個線程能夠同時進行讀操做
public interface ReadWriteLock { Lock readLock(); Lock writeLock(); }
和ReentrantLock相比,ReentrantReadWriteLock多了ReadLock
和WriteLock
兩個內部類。
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { private final ReentrantReadWriteLock.ReadLock readerLock; private final ReentrantReadWriteLock.WriteLock writerLock;
abstract static class Sync extends AbstractQueuedSynchronizer { static final int SHARED_SHIFT = 16;// 高16位爲讀鎖,低16位爲寫鎖 static final int SHARED_UNIT = (1 << SHARED_SHIFT); static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; static int sharedCount(int c) { return c >>> SHARED_SHIFT; } static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
讀寫鎖對於同步狀態的實現是將變量切割成兩部分,高16位表示讀,低16位表示寫。
看個實際例子
class czy{ private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); ........ public void read(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(); } } }
Lock和synchronized的選擇
總結來講,Lock和synchronized有如下幾點不一樣:
1)Lock是一個接口,而synchronized
是Java中的關鍵字,synchronized是內置的語言實現;
2)synchronized在發生異常時,會自動釋放線程佔有的鎖,所以不會致使死鎖現象發生;
而Lock在發生異常時,若是沒有主動經過unLock()去釋放鎖,則極可能形成死鎖現象,所以使用Lock時須要在finally塊中釋放鎖;
3)Lock可讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不可以響應中斷;
4)經過Lock能夠知道有沒有成功獲取鎖,而synchronized卻沒法辦到。
5)Lock能夠提升多個線程進行讀操做的效率。
有關Lock鎖的知識點就到這裏,若是想了解更多請參考下面連接。