ReentrantLock使用Condition实现线程的等待和唤醒

Posted java叶新东老师

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReentrantLock使用Condition实现线程的等待和唤醒相关的知识,希望对你有一定的参考价值。

ReentrantLock

    ReentrantLock的出现是为了替代synchronized,因为使用lock锁之后代码会更加简洁,增加易读性,但是在jdk1.6之后,synchronized增加了一个锁升级的概念,所以从jdk1.6开始,都优先使用synchronized,这不代表着ReentrantLock就要废弃了,synchronized 是一个非公平锁,若要实现公平锁就得使用ReentrantLock;所以啊,没有万能的工具,但可以通过场景的不同而选择最适合的工具来使用;

Condition

Condition是为了控制线程的挂起和唤醒而发明的;也就是用来实现线程之间通讯使用的接口,每个condition里面维护了一个等待队列,与synchronized不同的是,synchronized锁对象关联的监视器对象仅有一个,所以等待队列也只有一个。

而一个ReentrantLock可以有多个Condition,这样可以根据不同的业务需求,在使用同一个lock锁对象的基础上使用多个等待队列,让不同性质的线程加入到不同的等待队列当中。

使用

众所周知,Object有wait()notify()方法,用于线程间的通信。并且这两个方法只能在synchronized同步块内才可以调用,

synchronized (flag){
             
    flag.notify(); // 唤醒其中一个线程
    flag.wait();//将当前线程挂起
            
}

和synchronized一样,condition也是只能在lock()方法unlock()方法的中间使用

lock.lock();

condition.signal(); // 唤醒等待的线程
condition.await();// 让线程进入等待状态

lock.unlock();

condition的常用方法如下

为什么要使用多个Condition

如果你只有生产者和消费的2个线程在交替执行的话, 只用一个condition就够了, 但是我们在实际开发过程中,线程的数量可不都只有2个,有可能是几十上百个的线程;假如有100个线程,包含了生产者和消费者的线程,你每次唤醒线程就得使用signalAll()方法了,你想想有100个线程那么多,你每次只唤醒一个线程,就会导致其他的线程一直在等待,会出现饿死的情况;但是你调用signalAll()方法之后虽然唤醒了其他的99个线程, 但是因为加了锁,所以只有一个线程会抢到锁,但是你有那么多线程竞争抢锁,在高并发情况下,这个上下文切换的竞争锁开销是非常大的;

并且,这99个争抢的线程中包含了生产者和消费者,如果每次都让生产者抢到了锁资源,就会导致生产了过多的产品无法消费的情况,就像工厂里面生产了超量了产品,这些产品都积压在仓库里面,卖不出去;这是一个很大的问题;

所以在这边引入了多个Condition 可以使得线程的控制粒度更精细;

实战

     上面说到使用多个Condition 可以使得线程的控制粒度更精细;那么是怎么个精细法呢?接下来我们来模拟一下,使用 condition 实现三个(或者三个以上)线程打印ABC ABC.....;这样保证了每个线程都能执行到;不会出现饿死的情况,因为让线程按照自己想要的顺序执行, 所以也不会出现一直被生产者抢到的情况;

实现方式的思路是这样的:

1、启动的时候A线程先执行,

2、然后A线程挂起,启动B线程,

3、B线程挂起,启动C线程

4、C线程挂起,在启动A线程;

以上的方式就相当于启动了一个轮询,每个线程执行完成后调用下一个线程;

ConditionTest.java

package com.Lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 开启三个线程 ABC,按顺序打印ABC ABC....一直循环,使用Condition实现
 */
public class ConditionTest {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();
        ConditionTest test = new ConditionTest();
        // 开启线程A-----------------------------
        test.newThread(lock,"A",condition2,condition1);
        // 开启线程B-----------------------------
        test.newThread(lock,"B",condition3,condition2);
        // 开启线程C-----------------------------
        test.newThread(lock,"C",condition1,condition3);
    }
    /**
     * 开启新线程
     * @param name 线程名称
     * @param signalCondition  唤醒的线程
     * @param awaitCondition  等待的线程
     */
    public void newThread(Lock lock,String name,Condition signalCondition,Condition awaitCondition){
        new Thread(() -> {
            for (; ; ) {
                lock.lock();
                System.out.println(Thread.currentThread().getName());
                try {
                    signalCondition.signal(); // 唤醒等待的线程
                    awaitCondition.await();// 让线程进入等待状态
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.unlock();
            }
        }, name).start();
    }
}

另一个实现方式(与Condition无关)

当然,不用condition也可以实现,使用 ReentrantLock 的公平锁即可

package com.Lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 开启三个线程 ABC,按顺序打印ABC ABC....一直循环,使用公平锁实现
 */
public class ConditionTest_1 {
    public static void main(String[] args) {
        // 构造函数的参数设为true表示公平锁
        Lock lock = new ReentrantLock(true);

        ConditionTest_1 test = new ConditionTest_1();
        // 开启线程A-----------------------------
        test.newThread(lock,"A");
        // 开启线程B-----------------------------
        test.newThread(lock,"B");
        // 开启线程C-----------------------------
        test.newThread(lock,"C");
    }
    /**
     * 开启新线程
     * @param name 线程名称
     */
    public void newThread(Lock lock,String name){
        new Thread(() -> {
            for (; ; ) {
                lock.lock();
                System.out.println(Thread.currentThread().getName());
                lock.unlock();
            }
        }, name).start();
    }
}

以上是关于ReentrantLock使用Condition实现线程的等待和唤醒的主要内容,如果未能解决你的问题,请参考以下文章

从使用角度看 ReentrantLock 和 Condition

ReentrantLock及Condition原理解析

synchronizedLock接口Condition接口读写锁及ReentrantLock(重入锁) 特性及使用

Java多线程11:ReentrantLock的使用和Condition

ReentrantLock与Condition构造有界缓存队列与数据栈

Condition的await-signal流程详解