1. 程式人生 > >(轉載)Java顯式鎖學習總結之一:概論

(轉載)Java顯式鎖學習總結之一:概論

=========================================================================================

我們都知道在java中,當多個執行緒需要併發訪問共享資源時需要使用同步,我們經常使用的同步方式就是synchronized關鍵字,事實上,在jdk1.5之前,只有synchronized一種同步方式。而在jdk1.5中提供了一種新的同步方式--顯示鎖(Lock)。顯示鎖是隨java.util.concurrent包一起釋出的,java.util.concurrent包是併發大神Doug Lea寫的一個併發工具包,裡面除了顯示鎖,還有許多其他的實用併發工具類。

什麼是顯示鎖

  什麼是顯示鎖?用一段程式碼來說明:

複製程式碼

package com.gome;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockPractice {
    private static int a=0;
    private static Lock lock=new ReentrantLock();
    
    public static void increateBySynchronized(){
        a=0;
        for (int i = 0; i < 1000; i++) {
            Thread t=new Thread(new Runnable() {
                public void run() {
                    for (int j = 0; j < 100; j++) {
                        synchronized (LockPractice.class) {
                            a++;
                        }
                    }
                }
            });
            t.start();
        }
        while (Thread.activeCount()>1) {
            Thread.yield();
        }
        System.out.println(a);
    }
    
    public static void increateByLock(){
        a=0;
        for (int i = 0; i < 1000; i++) {
            Thread t=new Thread(new Runnable() {
                public void run() {
                    for (int j = 0; j < 100; j++) {
                        lock.lock();
                        try {
                            a++;
                        } finally {
                            lock.unlock();
                        }
                    }
                }
            });
            t.start();
        }
        while (Thread.activeCount()>1) {
            Thread.yield();
        }
        System.out.println(a);
    }
    
    public static void main(String[] args) {
        increateBySynchronized();
        increateByLock();
    }
}

執行結果:

解釋:

類LockPractice 中有兩個方法increateBySynchronized()和increateByLock(),這兩個方法都成功地用多執行緒併發將a累加到100000而沒有出現競態條件(race condition)問題。

其中increateBySynchronized()的同步是我們熟悉的synchronized關鍵字實現的:

synchronized (LockPractice.class) {
  a++;
}

這句程式碼的含義是:當有一個執行緒A進入synchronized程式碼塊後,阻塞其他要進入該程式碼塊的執行緒直到A執行完程式碼塊。synchronized關鍵字會關聯一個鎖物件,這裡是LockPractice.class。synchronized關鍵字底層是由jvm來實現的,當一個執行緒進入synchronized塊時,會在關聯的鎖物件的物件頭(MarkWord)中記錄下執行緒資訊(可以簡單的理解為執行緒id),這樣這個鎖物件就被當前執行緒獨佔了,其他試圖獲取這個鎖物件的執行緒將被阻塞。

因此一個執行緒進入、退出synchronized程式碼塊的本質就是這個執行緒對鎖物件的獲取、釋放。

而increateByLock()的同步程式碼如下,其中 lock是全域性變數 private static Lock lock=new ReentrantLock();

lock.lock();
try {
    a++;
} finally {
    lock.unlock();
}

從程式碼上可以看出,顯示鎖Lock的使用和synchronized的本質很像,也是定義了一個鎖物件(new ReentrantLock()),然後在進入同步程式碼前加鎖,執行同步程式碼後釋放鎖。

但是顯示鎖的底層卻和synchronized完全不同,並沒有使用到物件頭(MarkWord)這樣底層的東西,顯示鎖只是表現出了和synchronized一樣的行為(第一個訪問同步程式碼的執行緒獲得鎖,阻塞後來的執行緒)。

顯示鎖的優點

從上面的描述來看,顯示鎖實現了和synchronized一樣的功能,但是寫起來更復雜(需要手動加鎖解鎖,還需要寫finally防止發生異常後鎖不能釋放),那為什麼還要加入顯示鎖呢?

我們可以從Lock介面提供的方法看出端倪:

方法名稱 描述
void lock() 獲取鎖
void lockInterruptibly() throws InterruptedException 可中斷地獲取鎖,線上程獲取鎖的過程中可以響應中斷
boolean tryLock()   嘗試非阻塞獲取鎖,呼叫方法後立即返回,成功返回true,失敗返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException 在超時時間內獲取鎖,到達超時時間將返回false,也可以響應中斷
void unlock(); 釋放鎖
Condition newCondition(); 獲取等待元件,等待元件實現類似於Object.wait()方法的功能

從Lock提供的介面可以看出來,顯示鎖至少比synchronized多了以下功能:

  1. 可中斷獲取鎖:使用synchronized關鍵字獲取鎖的時候,如果執行緒沒有獲取到被阻塞了,那麼這個時候該執行緒是不響應中斷(interrupt)的,而使用Lock.lockInterruptibly()獲取鎖時被中斷,執行緒將丟擲中斷異常。
  2. 可非阻塞獲取鎖:使用sync關鍵字獲取鎖時,如果沒有成功獲取,只有被阻塞,而使用Lock.tryLock()獲取鎖時,如果沒有獲取成功也不會阻塞而是直接返回false。
  3. 可限定獲取鎖的超時時間:使用Lock.tryLock(long time, TimeUnit unit)。
  4. 其實顯示鎖還有其他的優勢,比如同一鎖物件上可以有多個等待佇列(相當於Object.wait()),我們後面會講。

其實除了更多的功能,顯示鎖還有一個很大的優勢:synchronized的同步是jvm底層實現的,對一般程式設計師來說程式遇到出乎意料的行為的時候,除了查官方文件幾乎沒有別的辦法;而顯示鎖除了個別操作用了底層的Unsafe類之外,幾乎都是用java語言實現的,我們可以通過學習顯示鎖的原始碼,來更加得心應手的使用顯示鎖。

顯示鎖的缺點 

當然顯示鎖也不是完美的,否則java就不會保留著synchronized關鍵字了,顯示鎖的缺點主要有兩個:

  1. 使用比較複雜,這點之前提到了,需要手動加鎖,解鎖,而且還必須保證在異常狀態下也要能夠解鎖。而synchronized的使用就簡單多了。
  2. 效率較低,synchronized關鍵字畢竟是jvm底層實現的,因此用了很多優化措施來優化速度(偏向鎖、輕量鎖等),而顯示鎖的效率相對低一些。

因此當需要進行同步時,優先考慮使用synchronized關鍵字,只有synchronized關鍵字不能滿足需求時,才考慮使用顯示鎖。

總結

這篇文章介紹了顯示鎖是什麼,顯示鎖的優點與缺點,在什麼情況下會用到顯示鎖。

後文將重點學習顯示鎖的底層實現:佇列同步器(AbstractQueuedSynchronizer)的實現、重入鎖(ReentrantLock)的實現、讀寫鎖(ReadWriteLock)的實現、等待/通知(Condition)的實現。