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

原文鏈接:https://www.cnblogs.com/sheeva/p/6439335.html

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

我們都知道在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)的實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章