Java中的可重入锁

在前面ConcurrentHashMap的实现原理与使用(二)中提到了可重入锁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.此时老婆线程已经获取到西瓜刀的锁,然后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实现锁:

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.此时老婆线程已经获取到西瓜刀的锁,然后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实现消费者生产者模式

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();
    }
}

/**
 * 产品
 */
class Product{
    //名称
    String name;
    //价格
    int price;
    //可以生产的标识
    boolean flag = false;
    //锁
    ReentrantLock lock;
    //消费条件
    Condition customerCondition;
    //生产条件
    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();
                //先判断产品的标识是否可以消费
                if (product.flag == true){
                    //消费
                    System.out.println("消费了产品");
                    System.out.println("产品为:" + product.name);
                    System.out.println("价格为:" + product.price);

                    //消费了产品,把标注改为false
                    product.flag = false;

                    //通知在producerCondition上等待的生产者线程进行生产
                    product.producerCondition.signal();
                }else {
                    //消费者线程在customerCondition上等待
                    product.customerCondition.await();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                product.lock.unlock();
            }
        }
    }
}

/**
 * 生产者
 */
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();
                //产品标识是false没有生产
                if (product.flag == false){
                    if (i % 2 == 0){
                        //偶数的时候生产cpu
                        product.name = "CPU";
                        product.price = 2000;
                    }else {
                        //奇数生产内存条
                        product.name = "内存条";
                        product.price = 300;
                    }
                    i ++;
                    System.out.println("生产了产品");
                    System.out.println("产品为:" + product.name);
                    System.out.println("价格为:" + product.price);
                    //把产品标识改为true,可以消费
                    product.flag = true;
                    //通知在customerCondition等待的消费者线程进行消费
                    product.customerCondition.signal();
                }else {
                    //已经生产了
                    //生产者线程在producerCondition上等待
                    product.producerCondition.await();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                product.lock.unlock();
            }
        }
    }
}
运行结果

说说ReentrantLock中通知机制的使用方法,在ReentrantLock中通知是用Condition来实现的,Condition对象中的signal方法相当于,Obejct对象中的wait方法,Condition对象中的signal方法相当于Object对象中的notify方法,同理notifyAll、signalAll方法,wait(long timeout)、await(long time, TimeUnit unit)方法作用相同。需要注意的一点是,线程是在Condition对象中等待,也是在Condition对象中被唤醒,拿上面的栗子来说:

//通知在producerCondition上等待的生产者线程进行生产
product.producerCondition.signal();

//消费者线程在customerCondition上等待
product.customerCondition.await();

 //生产者线程在producerCondition上等待
 product.producerCondition.await();

 //通知在customerCondition等待的消费者线程进行消费
 product.customerCondition.signal();

如果让生产者线程在producerCondition等待后,如果,如果调用product.customerCondition.signal(),不会唤醒生产者线程,因为生产者线程是在
producerCondition对象中等待的,使用的时候需要注意这一点。
文本中的代码已经上传到开源中国了,有兴趣的小伙伴可以拿去,https://git.oschina.net/zhaoweinan/reentrantlockdemo

Java中的可重入锁就为大家介绍到这里,欢迎大家来交流,指出文中一些说错的地方,让我加深认识。
谢谢大家!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容