程序员小天地程序员代码改变世界

Java中的可重入锁

2017-06-25  本文已影响0人  小草莓子桑

在前面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中的可重入锁就为大家介绍到这里,欢迎大家来交流,指出文中一些说错的地方,让我加深认识。
谢谢大家!

上一篇下一篇

猜你喜欢

热点阅读