上一篇文章中講述了信號量和互斥量,其中互斥量一般用于保證對于資源的互斥訪問,和鎖的本質一樣。本文講述簡單鎖的實現和可重入鎖的基本原理。
簡單鎖
在講述簡單鎖的實現之前,我們先來看一個鎖的應用例子:
public class Counter{
private Lock lock = new Lock();
private int count = 0;
public int inc(){
lock.lock();
this.count++;
lock.unlock();
return count;
}
}
上面的程序中,由于this.count++
這一操作分多步執行,在多線程環境中可能出現結果不符合預期的情況,這段代碼稱之為 臨界區 ,所以需要使用lock來保證其原子性。
Lock的實現:
public class Lock{
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException{
while(isLocked){ //不用if,而用while,是為了防止假喚醒
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify();
}
}
說明:當isLocked為true時,調用lock()的線程在wait()阻塞。 為防止該線程虛假喚醒,程序會重新去檢查isLocked條件。 如果isLocked為false,當前線程會退出while(isLocked)循環,并將isLocked設回true,讓其它正在調用lock()方法的線程能夠在Lock實例上加鎖。當線程完成了臨界區中的代碼,就會調用unlock()。執行unlock()會重新將isLocked設置為false,并且喚醒 其中一個 處于等待狀態的線程。
鎖的可重入性
同樣,先舉例來說明鎖的可重入性:
public class UnReentrant{
Lock lock = new Lock();
public void outer(){
lock.lock();
inner();
lock.unlock();
}
public void inner(){
lock.lock();
//do something
lock.unlock();
}
}
outer中調用了inner,outer先鎖住了lock,這樣inner就不能再獲取lock。其實調用outer的線程已經獲取了lock鎖,但是不能在inner中重復利用已經獲取的鎖資源,這種鎖即稱之為 不可重入 。通常也稱為 自旋鎖 。相對來說,可重入就意味著:線程可以進入任何一個它已經擁有的鎖所同步著的代碼塊。
可重入鎖的基本原理
我們在上面的Lock基礎上作一些修改:
public class Lock{
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock()
throws InterruptedException{
Thread callingThread = Thread.currentThread();
while(isLocked && lockedBy != callingThread){
wait();
}
isLocked = true;
lockedCount++;
lockedBy = callingThread;
}
public synchronized void unlock(){
if(Thread.curentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
}
- lockBy:保存已經獲得鎖實例的線程,在lock()判斷調用lock的線程是否已經獲得當前鎖實例,如果已經獲得鎖,則直接跳過while,無需等待。
- lockCount:記錄同一個線程重復對一個鎖對象加鎖的次數。否則,一次unlock就會解除所有鎖,即使這個鎖實例已經加鎖多次了。
Java中常用的鎖的屬性
synchronized:可重入鎖;
java.util.concurrent.locks.ReentrantLock:可重入鎖;