[Java并發系列] 3.Java中的鎖

討論J.U.C包中locks下面的類(包括接口)

鎖主要是用來控制多個線程訪問共享資源的一種方式,通常情況下,一個鎖可以防止在同一時間內多個線程同時訪問共享資源(讀寫鎖除外,讀寫鎖在同一時間內,可以允許有多個讀鎖同時讀共享資源)。

1. Lock接口

Lock接口同synchronized關鍵字的作用類似,都是提供了同步的功能。但是Lock在使用的時候,需要顯式的去獲取鎖。與synchronized相比,Lock失去了隱式獲取鎖的便捷性,但是可以控制鎖的獲取和釋放,可中斷鎖和超時鎖。

2. Lock接口主要API

  • void lock(); 獲取鎖
  • void lockInterruptibly(); 可中斷的獲取鎖,此方法和lock()方法的區別在于: 當使用lockInterruptibly獲取鎖時可以中斷當前線程;
  • boolean tryLock(); 嘗試非阻塞的獲取鎖,當調用此方法之后,如果能獲取則返回true,如果不能獲取則直接返回false;
  • boolean tryLock(long time, TimeUnit unit); 超時獲取鎖
  • void unlock(); 釋放鎖
  • Condition newCondition(); 獲取等待通知組件,該組件和當前的鎖綁定在一起,只有當前線程獲的了鎖,才能調用該組件的wait()方法,調用wait()方法之后,當前線程將釋放鎖;

3. Lock接口的實現類

1. ReentrantLock(重入鎖)

重入鎖,就是支持重進入的鎖,表示該鎖支持一個線程對資源的重復加鎖。

重進入: 指的是任意線程在獲取鎖能夠再次獲取該所而不會被阻塞。
公平與非公平獲取鎖:公平指的是在絕對時間上,先對鎖進行請求的線程(等待時間最長的線程優先獲取鎖)首先獲取鎖,那么這個鎖是公平的,反之,則是非公平的。

①. 鎖的重進入

如果要實現鎖的重進入,那么就就緒解決兩個問題:

  • 鎖的獲取:要獲取鎖,那么鎖就需要去檢查獲取該鎖的線程是否是已獲取此鎖的線程(也就是是否是當前線程占有此鎖),如果是,那么獲取成功;如下代碼是非公平獲取鎖的方式
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;
        }

在此方法中,首先判斷此鎖是否已被占有,如果沒有則使用CAS的方式設置同步狀態;如果鎖已被占有,則判斷當前線程是否是占有此鎖的線程,然后再來決定獲取操作是否成功,如果獲取鎖的線程再次請求獲取鎖,則將同步狀態值進行增加并且返回true。
所以重入鎖的獲取就是當線程重入成功,增加鎖的同步狀態值即可。

  • 鎖的釋放:線程重復N此獲取鎖,那么就需要釋放N次,其他的線程才可以獲取該鎖。如下代碼是釋放鎖的代碼:
protected final boolean tryRelease(int releases) {
            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;
        }

如果一個鎖被某個線程獲取了N次,那么前(N-1)次都會返回false,而當同步狀態完成被釋放時(c=0),將占有線程設置為null,才會返回true。

②. 公平與非公平的獲取鎖

如下是公平獲取鎖的代碼:

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 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;
        }
    }

與上面非公平獲取鎖的代碼相比,在這段代碼中,僅僅在if條件中多了一個hasQueuedPredecessors()方法,此方法就是判斷在同步隊列中,當前節點是否有前驅節點(即有比當前線程更早的獲取鎖的線程),因此當hasQueuedPredecessors()返回true時,就需要等待前驅線程獲取并釋放鎖之后才能繼續獲取鎖。

2. ReadWriteLock(讀寫鎖)

排他鎖:指的是在同一時刻只允許一個線程進行訪問
讀寫鎖:在同一時間,允許有多個讀線程進行訪問,而在寫線程進行訪問時,讀線程和其他寫線程均會被阻塞。讀寫鎖維護了一個讀鎖和一個寫鎖,通過讀寫分離,來提升并發性能(至少比排他鎖性能好多了)。

//todo 讀寫鎖內容較多,留待以后來寫

4. LockSupport類

LockSupport類位于在J.U.C.locks包中,它主要是定義了一些公共靜態方法,這些方法提供了最基本的線程阻塞和喚醒功能。如下表是LockSupport中提供的一些方法及描述:

方法 描述
public static void park() 阻塞當前線程,當其他線程調用unpark()或者中斷當前線程時,才能從park()方法返回
fipublic static void parkNanos(long nanos) 阻塞當前線程,超過nanos納秒之后,自動返回
public static void parkUntil(long deadline) 阻塞當前線程,直到deadline時間
public static void unpark(Thread thread) 喚醒處于阻塞的線程
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容