執行緒基礎(二十一)-併發容器-ArrayBlockingQueue(上)

weixin_34007291發表於2018-12-05

本文作者:王一飛,叩丁狼高階講師。原創文章,轉載請註明出處。

在正式講解ArrayBlockingQueue類前,先來科普一下執行緒中各類鎖,只有瞭解這些鎖之後,理解ArrayBlockingQueue那就更輕鬆了。

可重入鎖

一種遞迴無阻塞的同步機制,也叫做遞迴鎖。簡單講一個執行緒獲取到鎖物件之後,還是可以再次獲取該鎖物件時,不會發生阻塞。

java中 synchronized 跟ReentrantLock 都是可重入鎖, synchronized 為隱性, 而ReentrantLock 為顯示。 下面以synchronized 為例:

public class ThreadDemo {
    public synchronized void method1(){
        System.out.println(Thread.currentThread().getName() + "進入method1....");
        try {
            Thread.sleep(1000);
            method2();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void method2(){
        System.out.println(Thread.currentThread().getName() + "進入method2....");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class App {
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadDemo.method1();
            }
        }, "t1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadDemo.method2();
            }
        }, "t2").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadDemo.method2();
            }
        }, "t3").start();
    }
}
11401799-4292a6c3f4b7ef05.png
執行結果

上面程式碼, t1執行緒先進入method1, 1s之後進入method2, 因為使用synchronized 加鎖,t2, t3執行緒無法進入method2,必須等,而t1執行緒執行完method1後,可以直接進入method2, 無需要重複獲取鎖的操作。
改進: 可以再多開幾個執行緒訪問method2方法,會發現結果是一致的。

不可重入鎖

不可重入鎖是跟重入鎖是對立的,表示一執行緒獲取到鎖物件後,想再次獲取該鎖物件時,必須先釋放之前獲取鎖物件,否則阻塞等待。
java中沒有執行緒類實現不可重入鎖,更多時候,需要我們程式設計實現。

//自定義鎖物件模擬不可重入鎖
public class MyLock {
    private boolean isLock = false;
    //模擬獲取鎖
    public synchronized void lock() throws InterruptedException {
        //自旋排除一些硬體執行干擾
        while(isLock){
            wait();
        }
        isLock = true;
    }
    //模擬釋放鎖
    public synchronized  void unLock(){
        isLock = false;
        notify();
    }
}
public class ThreadDemo {
    private MyLock lock = new MyLock();

    public void method1() throws InterruptedException {
        lock.lock();
        System.out.println("method1...in");
        method2();
        System.out.println("method1...out");
        lock.unLock();
    }

    public void method2() throws InterruptedException {
        lock.lock();
        System.out.println("method2......");
        lock.unLock();
    }
}
public class App {
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo demo = new ThreadDemo();
        demo.method1();
    }
}

執行之後, 列印只有method1...in, 在執行method1時,呼叫lock.lock()方法, isLock標記量改為true,表示鎖被持有,跳過迴圈。 執行method2時,再次執行lock.lock(),isLock標籤為true, 進入迴圈,執行緒等待。模擬拉當鎖已經被持有,同一個執行緒第二次申請同一把鎖,需要等待。

互斥鎖

同一個時刻,只允許獲取到所物件的執行緒執行。synchronized ReentrantLock 本身就是互斥鎖。

public class ThreadDemo {
    //同一個時刻只允許一個執行緒進入
    public synchronized void method1(){
        System.out.println(Thread.currentThread().getName() + "進入....");
    }
}
public class ThreadDemo {
    private ReentrantLock lock = new ReentrantLock();
    //同一個時刻只允許一個執行緒進入
    public  void method1(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "進入...."); 
        }finally {
            lock.unlock();
        }
    }
}
自旋鎖

一種非阻塞鎖,也就是說,如果某執行緒需要獲取鎖,但鎖已經被執行緒佔用時,執行緒不阻塞等待,而是通過空迴圈來消耗CPU時間片,等待其他執行緒釋放鎖。注意,自旋鎖中的迴圈也不是瞎迴圈, 一般會設定一定迴圈次數或者迴圈跳出條件。
自旋鎖運用非常廣泛, jdk中的juc包原子操作類中都是, 比如: AtomicInteger

public class AtomicInteger extends Number implements java.io.Serializable {
   public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }
}

Unsafe類

public final int getAndSetInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));

        return var5;
 }
偏向鎖 / 輕量級鎖 / 重量級鎖

偏向鎖,輕量鎖,重量鎖並不是對執行緒加鎖機制,而是jdk1.6之後提出的對執行緒加鎖的優化策略。
偏向鎖:當執行緒沒有競爭的環境下,需要重複獲取某個鎖物件時,jvm為減少開銷,讓執行緒進入偏向模式,再次獲取鎖物件時,取消之前已經獲取鎖同步操作(即一系列的cas判斷),直接取得鎖物件。如果期間有其他執行緒參與競爭,則退出偏向模式。

當執行緒退出偏向模式最後,進入輕量級鎖模式。此時,執行緒嘗試使用自旋方式來獲取鎖,如果獲取成功,繼續邏輯執行, 如果獲取失敗,表示當前鎖物件存在競爭,鎖就會膨脹成重量級鎖。

當執行緒進入重量級鎖模式, 所有操作就跟我們所認知那樣,爭奪CUP,在操作過程中,爭奪失敗執行緒會被作業系統掛起,阻塞等待。那麼執行緒間的切換和呼叫成本就會大大提高,效能也就對應下降了。

樂觀鎖

顧名思義,就是很樂觀,在獲取資料時認為別人不會對資料做修改,所以不上鎖,但是在更新的時候會先判斷別人有沒有更新了此資料,最通用的實現是使用版本號判斷方式,java的juc併發包中的原子操作類使用的CAS機制,其實也是一種樂觀鎖實現。

悲觀鎖

與樂觀鎖是相對的,操作前都假設最壞的情況,在獲取資料的認為別人會對資料做修改,所以每次操作前都會上鎖。而別人想操作此資料就會阻塞直到它拿到鎖。Java中synchronized關鍵字ReentrantLock的實現便是悲觀鎖。

公平鎖

公平鎖,講究公平,當鎖物件被佔用時,參與鎖物件爭奪的執行緒按照FIFO的順序排序等待鎖釋放,人人有機會,不爭不搶。

public class Resource implements Runnable {

    private ReentrantLock lock = new ReentrantLock(true);  //公平鎖
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 進入了.....");
        lock.lock(); //爭鎖
        try {
            System.out.println(Thread.currentThread().getName() + " 獲取鎖並執行了.....");
        }finally {
            lock.unlock();
        }
    }
}
public class App {
    public static void main(String[] args) throws InterruptedException {

        Resource resource = new Resource(); //共享資源
        for (int i = 0; i <10; i++) {
            new Thread(resource, "t_" + i).start();
        }
    }
}
t_4 進入了.....
t_2 進入了.....
t_4 獲取鎖並執行了.....
t_7 進入了.....
t_6 進入了.....
t_3 進入了.....
t_5 進入了.....
t_2 獲取鎖並執行了.....
t_7 獲取鎖並執行了.....
t_6 獲取鎖並執行了.....
t_3 獲取鎖並執行了.....
t_5 獲取鎖並執行了.....
t_0 進入了.....
t_0 獲取鎖並執行了.....
t_1 進入了.....
t_1 獲取鎖並執行了.....
t_8 進入了.....
t_8 獲取鎖並執行了.....
t_9 進入了.....
t_9 獲取鎖並執行了.....

觀察執行結果,當鎖是公平鎖時(new ReentrantLock(true))會發現進入順序是4,2,7,6,3,5,0,1,8,9 而執行的順序是4,2,7,6,3,5,0,1,8,9。兩者順序一樣,這就是公平的體現,誰先來,誰先執行。

非公平鎖

與公平鎖相對,當鎖物件被釋放時,所有參與爭奪鎖物件的執行緒各憑本事,撐死膽大的,餓死膽小的。

其他程式碼不變,僅僅將引數改為false或者去掉
private ReentrantLock lock = new ReentrantLock(false);  //非公平鎖
t_4 進入了.....
t_2 進入了.....
t_5 進入了.....
t_3 進入了.....
t_4 獲取鎖並執行了.....
t_7 進入了.....
t_7 獲取鎖並執行了.....
t_0 進入了.....
t_1 進入了.....
t_0 獲取鎖並執行了.....
t_8 進入了.....
t_2 獲取鎖並執行了.....
t_9 進入了.....
t_9 獲取鎖並執行了.....
t_6 進入了.....
t_5 獲取鎖並執行了.....
t_3 獲取鎖並執行了.....
t_1 獲取鎖並執行了.....
t_8 獲取鎖並執行了.....
t_6 獲取鎖並執行了.....

當鎖是非公平鎖時(new ReentrantLock(false))進入的順序與執行順序不一樣啦,這就是非公平鎖,爭奪CPU各憑本事。

好的,到這執行緒中的鎖知識普及就結束了。

想獲取更多技術乾貨,請前往叩丁狼官網:http://www.wolfcode.cn/all_article.html

相關文章