之前對於synchronized的實現和運用有所瞭解,但是也發現了一些缺陷,由於synchronized是在JVM層面上實現同步互斥,是以關鍵字形式加鎖,導致其顆粒度過大,有的時候不需要這麼大範圍的加鎖,在中斷和線程阻塞處理上也有所欠缺,在JDK1.5之後,Java引入了Lock,作為對於synch ...
之前對於synchronized的實現和運用有所瞭解,但是也發現了一些缺陷,由於synchronized是在JVM層面上實現同步互斥,是以關鍵字形式加鎖,導致其顆粒度過大,有的時候不需要這麼大範圍的加鎖,在中斷和線程阻塞處理上也有所欠缺,在JDK1.5之後,Java引入了Lock,作為對於synchronized的補充。
實現原理
Lock是Java的一個介面類,而繼承它實現的也是一個Java類,因此Lock的實現不是基於JVM的,而是在應用層,介面源碼如下
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
這幾個方法就是Lock的核心實現。而Lock的實現類是ReentrantLock,這裡需要講一下ReentrantLock的結構,ReentrantLock把對Lock的實現交給了繼承AbstractQueuedSynchronizer抽象類的Sync類,而為了實現公平鎖和和非公平鎖,又加入了FairSync和NonfairSync兩個繼承Sync的類來重寫lock()方法。
- abstract static class Sync extends AbstractQueuedSynchronizer
- static final class NonfairSync extends Sync
- static final class FairSync extends Sync
而ReentrantLock的加鎖解鎖原理則是利用AbstractQueuedSynchronizer類的方法,把所有的請求線程構成一個CLH隊列(虛擬隊列,將每個線程內容包裝成一個Node類),運行線程不包括在內,如果有新進程加入,則阻塞並加入到隊列尾部,如果運行線程結束,就從隊列中獲取下個線程。
在瞭解加鎖和解鎖過程之前首先需要瞭解Lock怎麼阻塞線程,源碼中顯示調用了LockSupport.park(),在深入結果是調用sun.misc.Unsafe.park()本地方法,從而調用系統互斥鎖。下麵就是加鎖和解鎖的過程
加鎖
對於加鎖過程,非公平鎖和公平鎖機制不同,這裡分開描述。
非公平鎖
- 在開始就先嘗試當前線程獲取鎖,如果成功,那麼直接將鎖鎖定,不成功再進入隊列調用的流程。
- 進入隊列調用的第一步是獲取當前鎖的狀態,如果鎖狀態值為0,嘗試獲取鎖,如果成功的話,將鎖賦給當前線程,失敗就阻塞後進入隊列尾部排隊。
如果鎖狀態值為1,先看是否為存在重入鎖的情況,即線程和運行線程相同,如果是,那麼鎖狀態加1,如果不是,那麼阻塞後進入隊列尾部排隊。
//ReentrantLock.java文件 final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread();//獲取當前線程 int c = getState();//獲取鎖狀態值 if (c == 0) { //如果鎖狀態為0,那麼沒有線程占用鎖 if (compareAndSetState(0, acquires)) {//CAS嘗試獲得鎖 setExclusiveOwnerThread(current); return true; } } //如果鎖狀態不為0 else if (current == getExclusiveOwnerThread()) {//如果當前線程就是鎖的線程,進入重入鎖狀態 int nextc = c + acquires;//鎖狀態值加1 if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } //AbstractQueuedSynchronizer.java文件 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
公平鎖
- 不嘗試獲取鎖,直接進入隊列流程
- 在隊列流程中,同樣獲取鎖的狀態值,如果狀態值為0,並且隊列中沒有排隊的線程,那麼嘗試獲取鎖,如果有線程,那麼就阻塞線程並放在隊列尾部排隊
如果狀態值不為0,先看是否為存在重入鎖的情況,即線程和運行線程相同,如果是,那麼鎖狀態加1,如果不是,那麼阻塞後進入隊列尾部排隊。
//ReentrantLock.java文件 final void lock() { acquire(1); } /** * Fair version of tryAcquire. Don't grant access unless * recursive call or no waiters or is first. */ protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //如果鎖狀態值為0和隊列中沒有線程,獲取鎖 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //重入鎖情況 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } //AbstractQueuedSynchronizer.java文件 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
解鎖
- 獲取當前線程的狀態值,然後減1
如果結果為0,那麼釋放這個鎖,如果不為0,那麼存在重入鎖情況,不釋放鎖
//ReentrantLock.java文件 public void unlock() { sync.release(1); } protected final boolean tryRelease(int releases) { //狀態值-1 int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //判斷重入鎖情況 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } //AbstractQueuedSynchronizer.java文件 public final boolean release(int arg) { if (tryRelease(arg)) { //獲取排隊線程的頭線程,運行線程 Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
Lock應用
Lock介面有lock()、unlock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()這些方法進行加鎖和解鎖,這裡逐個分析(Condition()用於線程間通信,這裡暫時不解釋)
lock()和unlock()
這兩個方法基本都是一起出現的,因為這裡放在一起講,lock()就是用來對代碼塊進行加鎖,而unlock()是對鎖的釋放,而在它們之間就是加鎖的代碼,一般會加上try...catch方式,而unlock()一般會出現在finally中,基本格式如下
Lock lock = new Lock();
lock.lock();
try{
//加鎖代碼塊
...
}catch(Exception e){
}finally{
lock.unlock(); //釋放鎖
}
tryLock()和tryLock(long time, TimeUnit unit)
tryLock()是有返回值的,如果當前鎖未被占用,那麼就嘗試獲取鎖,這裡和lock()一樣,並返回true,但是如果鎖被占用,那麼直接返回false,不阻塞在那裡,這是和synchronized區別部分之一,是對加鎖的靈活運用。
tryLock(long time, TimeUnit unit)是加上了等待時間,就是在tryLock()基礎上加上了嘗試獲取的等待時間,如果鎖獲取失敗,會等待一段時間,如果等待過程中獲取到鎖,那麼返回true,如果還是沒有,那麼返回false。一般格式如下
Lock lock = new Lock();
if(lock.tryLock()) {
try{
//加鎖代碼塊
...
}catch(Exception e){
}finally{
lock.unlock();
}
}else {
//不加鎖的處理
...
}
lockInterruptibly()
lockInterruptibly()和lock()的機制是一樣的,如果沒有特殊情況,就是一般的加鎖,但是如果在加鎖後使用Thread.interrupt中斷線程,就會拋異常InterruptedException,因為lock優先考慮獲取鎖,在等待獲取鎖的過程中,忽略中斷請求,只有成功獲得鎖之後才響應中斷。lockInterruptibly允許在等待獲取鎖的過程中由其它線程調用等待線程的Thread.interrupt方法來中斷等待線程的等待而直接返回。
Lock lock = new Lock();
lock.lockInterruptibly();
try{
//加鎖代碼塊
...
}catch(InterruptedException e){
}finally{
lock.unlock(); //釋放鎖
}