在Java 1.5之後,併發包中新增了Lock接口用來實現鎖功能,它提供了Synchronized關鍵字類似的功能,只是在使用時需要顯式地獲取鎖和釋放鎖。雖然它缺少了隱式獲取鎖釋放鎖的便捷性,但是卻擁有了鎖釋放和獲取的可操作性、可中斷地獲取鎖以及超時獲取鎖等多種選擇。
1 Lock接口
Lock接口的主要api如下:
1)void lock():獲取鎖,調用該方法的當前線程或獲取鎖,並從該方法返回,沒有獲取鎖的將會自旋等待。
2)void lockInterruptibly () throws InterruptedException:可中斷的獲取鎖,該方法會響應中斷,即在獲取鎖的自旋等待中,可以中斷當前線程。
3)boolean tryLock():嘗試非阻塞的獲取鎖,調用該方法會立刻返回,獲取則返回true。
4)Condition newCondition():返回等待通知組件,類似於Object.wait與Object.notify方法,可以使線程進入waiting狀態。
5)相對應的unlock()方法。
在Lock的具體實現中,基本都是通過聚合了一個隊列同步器(AbstractQueuedSynchronizer,AQS)的子類來完成線程訪問控制的。下面我們將詳細介紹AQS的使用和實現。
2 隊列同步器AQS
2.1 同步器的接口
上述的幾個方法將被模板方法引用,我們在實現自定義方法時,將調用這些模板方法。模板方法如下:
1)void acquire(ing arg):獨佔式獲取同步鎖(調用tryAcquire()),成功的話,則同步器的鎖被該線程擁有(同步器記錄下該線程),方法返回。否則進入同步隊列等待。
2)void acquireInterruptibly(int arg):與1)相同,但是能響應中斷,在同步隊列等待時,可以響應中斷,拋出異常並返回。
3)boolean AcquireShared(int arg):共享式的獲取鎖。
4)Collection<Thread> getQueuedThreads():獲取等待在同步隊列上的線程。
5)其他獨佔、共享、中斷的組合獲取鎖和釋放鎖的方法。
2.2 模板方法的實現
1.同步隊列
2.獨佔式同步狀態獲取與釋放
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
public boolean tryAcquire(int acquires){
if(this.compareAndSetState(0, 1)){ //如果鎖未被佔用,則設置爲1
this.setExclusiveOwnerThread(Thread.currentThread()); //將此線程設爲鎖的擁有者
return true;
}
return false;
}
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) {
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;
}
可以看到,當狀態爲0時,會同上面的不可重入鎖一樣進行加鎖並取得鎖。但如果不爲0,則對線程進行判斷,如果當前線程是擁有鎖的線程,則可以正確返回,執行同步方法。否則,將加入同步隊列等待。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor(); //找到前驅節點
if (p == head && tryAcquire(arg)) { //如果前驅節點是頭結點並且當前節點能夠獲取狀態
setHead(node); //設爲頭結點
p.next = null; // help GC //之前的節點出隊
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //會調用LockSupport.park()方法阻塞自己,等待前繼節點喚醒自己
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
自旋獲取鎖的過程:
判斷當前節點的前驅節點。
需要獲取當前節點的前驅節點的狀態,當前驅節點是頭結點並且當前節點(線程)能夠獲取狀態(tryAcquire方法成功),
代表該當前節點佔有鎖,如果滿足上述條件,那麼代表能夠佔有鎖,根據節點對鎖佔有的含義,設置頭結點爲當前節點(setHead)。
如果沒有滿足上述條件,判斷前一個節點的狀態,並調用parkAndCheckInterrupt方法使得當前線程阻塞,直到unpark調用(前一個節點釋放鎖的時候會通知後繼節點),Thread的interrupt調用,然後重新輪訓去嘗試上述操作。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
3 共享式同步狀態的獲取與釋放
共享式獲取與獨佔式獲取最主要的區別在於同一時刻能否有多個線程同時獲取到同步狀態。其獲取鎖的代碼如下:
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在acquireShared()放法中,先會tryAcquireShared(arg),如果大於0成功,則獲取同步狀態。否則,加入同步隊列,阻塞,等待前繼節點執行完同步方法後喚醒,重新嘗試獲取同步狀態。共享鎖的釋放同獨佔鎖,需要喚醒後繼節點。
4 獨佔式超時獲取同步狀態
可以在指定時間內獲取同步狀態,如果獲取成功,則返回true,否則,返回false。在acquireSharedInterruptibly()的基礎是增加了時間限制。不再詳述。