Java中的可重入鎖

在前面ConcurrentHashMap的實現(xiàn)原理與使用(二)中提到了可重入鎖ReentrantLock,說有時間再聊,這幾天下大雨,《變5》也沒有看成,就來和大家一起聊聊Java中的可重入鎖。

synchronized與ReentrantLock

Java官方API中粘過來說明:A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized methods and statements, but with extended capabilities.在這里翻一下(英文不好,強行使用百度翻譯加上自己組織):一個可重入的互斥鎖,和關鍵詞synchronized隱式鎖修飾的方法與語句(可能翻譯錯了,就理解為和synchronized具有相同作用吧)具有相同的功能和語義,但具有擴展功能,翻譯完畢。

通俗來講可重入鎖是一個線程在獲取到一個鎖以后,再次獲取該鎖線程不會被阻塞,synchronized是隱式的進行加鎖,而ReentrantLock不是隱式的,但是,他們兩的功能和語義基本相同,都是可重入鎖,下面舉個栗子來證明synchronized、ReentrantLock是可重入鎖吧。

先來個synchronized可重入的栗子
package edu.thread.reentrantLock;

/**
 * @Description: .
 * @Author: ZhaoWeiNan .
 * @CreatedTime: 2017/6/25 .
 * @Version: 1.0 .
 */
public class SynchronizedTest extends Thread {

    private String type;

    public SynchronizedTest(String type,String name){
        super(name);
        this.type = type;
    }

    @Override
    public void run() {
        if ("死鎖".equals(type)){
            //死鎖栗子
            DeadLock();
        }else if ("可重入".equals(type)){
            //可重入栗子
            ReentrantLock();
        }
    }

    /**
     *  一個阻塞的栗子。
     *  1.老公線程,先用西瓜加鎖,拿到鎖以后,再去用西瓜刀加鎖。
     *  2.此時老婆線程已經(jīng)獲取到西瓜刀的鎖,然后sleep了。
     *  3.老公線程獲取不到西瓜刀的鎖,所以被阻塞。
     */
    private void DeadLock(){
        if (Thread.currentThread().getName().equals("老公")){
            synchronized ("西瓜"){
                System.out.println("老公買了西瓜,準備去拿西瓜刀。");
                synchronized("西瓜刀"){
                    System.out.println("老公拿了西瓜刀,準備吃西瓜。");
                }

            }
        }else if (Thread.currentThread().getName().equals("老婆")){
            synchronized ("西瓜刀"){
                System.out.println("老婆把西瓜刀藏起來了,不讓老公吃西瓜。");
                try {
                    Thread.sleep(1000000000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 可重入的栗子。
     * 1.老公線程獲取了徒手吃西瓜的鎖,注意此時,徒手吃西瓜的鎖并沒有被釋放。
     * 2.同時start了一個老婆線程了,徒手吃西瓜的鎖并沒有被釋放,所以老婆線程沒有獲取到徒手吃西瓜鎖。
     * 3.老公線程再次獲取徒手吃西瓜的鎖,沒有被阻塞,證明了synchronized的可重入。
     */
    private void ReentrantLock(){
        if (Thread.currentThread().getName().equals("老公")){
            synchronized ("徒手吃西瓜"){
                System.out.println("老公買了西瓜,徒手掰西瓜。");
                synchronized("徒手吃西瓜"){
                    System.out.println("老公掰開了西瓜,準備吃西瓜。");
                    System.out.println("老公把老婆鎖在外面,吃完西瓜才讓她進來。");
                    try {
                        Thread.sleep(1000000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }else if (Thread.currentThread().getName().equals("老婆")){
            System.out.println("老婆沖進家。");
            synchronized ("徒手吃西瓜"){
                System.out.println("老婆抓住老公的手,不讓老公吃西瓜。");
            }
        }
    }
}

class Demo{
    public static void main(String[] args){
        //先運行阻塞的栗子
        /*SynchronizedTest lg = new SynchronizedTest("死鎖","老公");
        SynchronizedTest lp = new SynchronizedTest("死鎖","老婆");

        lg.start();
        lp.start();*/

        //在運行可重入的栗子
        SynchronizedTest lg = new SynchronizedTest("可重入","老公");
        SynchronizedTest lp = new SynchronizedTest("可重入","老婆");
        lg.start();
        lp.start();
    }
}

先運行了一個死鎖的栗子,其中老婆線程獲取了西瓜刀鎖,把老公線程阻塞了,老公線程沒有遲到西瓜:


老婆線程獲取了西瓜刀鎖,阻塞了老公線程

在運行了一個可重入的栗子,其中老公線程獲取了徒手吃西瓜鎖,此時沒有釋放,所以阻塞了老婆線程獲取徒手吃西瓜鎖,當徒手吃西瓜鎖釋放的情況下,老公再次獲取該鎖的時候,沒有被阻塞:


老公線程獲取了徒手吃西瓜鎖,再次獲取該鎖時,沒有被阻塞
再來個ReentrantLock可重入的栗子

改寫一下上面那個栗子,使用ReentrantLock實現(xiàn)鎖:

package edu.thread.reentrantLock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Description: .
 * @Author: ZhaoWeiNan .
 * @CreatedTime: 2017/6/25 .
 * @Version: 1.0 .
 */
public class ReentrantLockTest extends Thread {

    private String type;

    //西瓜鎖
    private ReentrantLock lock1;
    //西瓜刀鎖
    private ReentrantLock lock2;
    //徒手吃西瓜鎖
    private ReentrantLock lock3;

    public ReentrantLockTest(String type,String name,ReentrantLock lock1,ReentrantLock lock2){
        super(name);
        this.type = type;
        this.lock1 = lock1;
        this.lock2 = lock2;
    }

    public ReentrantLockTest(String type,String name,ReentrantLock lock3){
        super(name);
        this.type = type;
        this.lock3 = lock3;
    }

    @Override
    public void run() {
        if ("死鎖".equals(type)){
            //死鎖栗子
            DeadLock();
        }else if ("可重入".equals(type)){
            //可重入栗子
            ReentrantLock();
        }
    }

    /**
     *  一個阻塞的栗子。
     *  1.老公線程,先用西瓜加鎖,拿到鎖以后,再去用西瓜刀加鎖。
     *  2.此時老婆線程已經(jīng)獲取到西瓜刀的鎖,然后sleep了。
     *  3.老公線程獲取不到西瓜刀的鎖,所以被阻塞。
     */
    private void DeadLock(){
        if (Thread.currentThread().getName().equals("老公")){
            lock1.lock();
            System.out.println("老公買了西瓜,準備去拿西瓜刀。");
            lock2.lock();
            System.out.println("老公拿了西瓜刀,準備吃西瓜。");
            lock2.unlock();
            lock1.unlock();
        } else if (Thread.currentThread().getName().equals("老婆")){
            lock2.lock();
            System.out.println("老婆把西瓜刀藏起來了,不讓老公吃西瓜。");
            try {
                Thread.sleep(1000000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock2.unlock();
        }
    }

    /**
     * 可重入的栗子。
     * 1.老公線程獲取了徒手吃西瓜的鎖,注意此時,徒手吃西瓜的鎖并沒有被釋放。
     * 2.同時start了一個老婆線程了,徒手吃西瓜的鎖并沒有被釋放,所以老婆線程沒有獲取到徒手吃西瓜鎖。
     * 3.老公線程再次獲取徒手吃西瓜的鎖,沒有被阻塞,證明了ReentrantLock的可重入。
     */
    private void ReentrantLock(){
        if (Thread.currentThread().getName().equals("老公")){
            lock3.lock();
            System.out.println("老公買了西瓜,徒手掰西瓜。");
            lock3.lock();
            System.out.println("老公掰開了西瓜,準備吃西瓜。");
            System.out.println("老公把老婆鎖在外面,吃完西瓜才讓她進來。");
            try {
                Thread.sleep(1000000000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock3.unlock();
        }else if (Thread.currentThread().getName().equals("老婆")){
            System.out.println("老婆沖進家。");
            lock3.lock();
            System.out.println("老婆抓住老公的手,不讓老公吃西瓜。");
            lock3.unlock();
        }


    }
}

class Demo1 {
    public static void main(String[] args){
        //先運行阻塞的栗子
       /* //西瓜鎖
        ReentrantLock lock1 = new ReentrantLock();
        //西瓜刀鎖
        ReentrantLock lock2 = new ReentrantLock();
        ReentrantLockTest lg = new ReentrantLockTest("死鎖","老公",lock1,lock2);
        ReentrantLockTest lp = new ReentrantLockTest("死鎖","老婆",lock1,lock2);

        lg.start();
        lp.start();*/

        //在運行可重入的栗子
        //徒手吃西瓜鎖
        ReentrantLock lock3 = new ReentrantLock();
        ReentrantLockTest lg = new ReentrantLockTest("可重入","老公",lock3);
        ReentrantLockTest lp = new ReentrantLockTest("可重入","老婆",lock3);
        lg.start();
        lp.start();
    }
}

先運行了一個死鎖的栗子,其中老婆線程獲取了西瓜刀鎖,把老公線程阻塞了,老公線程沒有遲到西瓜:


老婆線程獲取了西瓜刀鎖,阻塞了老公線程

在運行了一個可重入的栗子,其中老公線程獲取了徒手吃西瓜鎖,此時沒有釋放,所以阻塞了老婆線程獲取徒手吃西瓜鎖,當徒手吃西瓜鎖釋放的情況下,老公再次獲取該鎖的時候,沒有被阻塞:


老公線程獲取了徒手吃西瓜鎖,再次獲取該鎖時,沒有被阻塞

利用ReentrantLock實現(xiàn)消費者生產(chǎn)者模式

package edu.thread.reentrantLock;

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

/**
 * @Description: .
 * @Author: ZhaoWeiNan .
 * @CreatedTime: 2017/6/25 .
 * @Version: 1.0 .
 */
public class Demo2 {

    public static void main(String[] args){
        Product product = new Product(new ReentrantLock());
        Producer producer = new Producer(product);
        Customer customer = new Customer(product);
        producer.start();
        customer.start();
    }
}

/**
 * 產(chǎn)品
 */
class Product{
    //名稱
    String name;
    //價格
    int price;
    //可以生產(chǎn)的標識
    boolean flag = false;
    //鎖
    ReentrantLock lock;
    //消費條件
    Condition customerCondition;
    //生產(chǎn)條件
    Condition producerCondition;

    public Product(ReentrantLock lock) {
        this.lock = lock;
        customerCondition = lock.newCondition();
        producerCondition = lock.newCondition();
    }
}

/**
 * 消費者
 */
class Customer extends Thread{
    private Product product;

    public Customer(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        while (true){
            try {
                product.lock.lock();
                //先判斷產(chǎn)品的標識是否可以消費
                if (product.flag == true){
                    //消費
                    System.out.println("消費了產(chǎn)品");
                    System.out.println("產(chǎn)品為:" + product.name);
                    System.out.println("價格為:" + product.price);

                    //消費了產(chǎn)品,把標注改為false
                    product.flag = false;

                    //通知在producerCondition上等待的生產(chǎn)者線程進行生產(chǎn)
                    product.producerCondition.signal();
                }else {
                    //消費者線程在customerCondition上等待
                    product.customerCondition.await();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                product.lock.unlock();
            }
        }
    }
}

/**
 * 生產(chǎn)者
 */
class Producer extends Thread{

    private Product product;

    public Producer(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        int i = 0;
        while (true){

            try {
                product.lock.lock();
                //產(chǎn)品標識是false沒有生產(chǎn)
                if (product.flag == false){
                    if (i % 2 == 0){
                        //偶數(shù)的時候生產(chǎn)cpu
                        product.name = "CPU";
                        product.price = 2000;
                    }else {
                        //奇數(shù)生產(chǎn)內存條
                        product.name = "內存條";
                        product.price = 300;
                    }
                    i ++;
                    System.out.println("生產(chǎn)了產(chǎn)品");
                    System.out.println("產(chǎn)品為:" + product.name);
                    System.out.println("價格為:" + product.price);
                    //把產(chǎn)品標識改為true,可以消費
                    product.flag = true;
                    //通知在customerCondition等待的消費者線程進行消費
                    product.customerCondition.signal();
                }else {
                    //已經(jīng)生產(chǎn)了
                    //生產(chǎn)者線程在producerCondition上等待
                    product.producerCondition.await();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                product.lock.unlock();
            }
        }
    }
}
運行結果

說說ReentrantLock中通知機制的使用方法,在ReentrantLock中通知是用Condition來實現(xiàn)的,Condition對象中的signal方法相當于,Obejct對象中的wait方法,Condition對象中的signal方法相當于Object對象中的notify方法,同理notifyAll、signalAll方法,wait(long timeout)、await(long time, TimeUnit unit)方法作用相同。需要注意的一點是,線程是在Condition對象中等待,也是在Condition對象中被喚醒,拿上面的栗子來說:

//通知在producerCondition上等待的生產(chǎn)者線程進行生產(chǎn)
product.producerCondition.signal();

//消費者線程在customerCondition上等待
product.customerCondition.await();

 //生產(chǎn)者線程在producerCondition上等待
 product.producerCondition.await();

 //通知在customerCondition等待的消費者線程進行消費
 product.customerCondition.signal();

如果讓生產(chǎn)者線程在producerCondition等待后,如果,如果調用product.customerCondition.signal(),不會喚醒生產(chǎn)者線程,因為生產(chǎn)者線程是在
producerCondition對象中等待的,使用的時候需要注意這一點。
文本中的代碼已經(jīng)上傳到開源中國了,有興趣的小伙伴可以拿去,https://git.oschina.net/zhaoweinan/reentrantlockdemo。

Java中的可重入鎖就為大家介紹到這里,歡迎大家來交流,指出文中一些說錯的地方,讓我加深認識。
謝謝大家!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容