Java中juc并发包下的Condition接口与ReentrantLock对象锁实现线程通信

Posted 小鹏说

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java中juc并发包下的Condition接口与ReentrantLock对象锁实现线程通信相关的知识,希望对你有一定的参考价值。

前言

在传统的Java开发中,大多数程序员都是使用synchronized关键字配合Object类中的wait()、notify()方法和notifyAll()方法来实现线程通信,不过随着jdk版本的不断升级与维护,在jdk1.5开始,JavaAPI中出现了一个叫ReentrantLock对象锁,它是一种可重入的、递归的非公平锁,下面我们就来简单介绍一下ReentrantLock锁。

 

ReentrantLock继承结构体系图

synchronized关键字与ReentrantLock重入锁的对比

Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的。与synchronized相比,重入锁有着显式的操作过程,开发人员必须手动的指定何时加锁(调用lock方法),何时释放锁(调用unlock方法)。因此,重入锁对逻辑的控制性要好于synchronized。重入锁,顾名思义,对于同一个线程,这种锁是可以反复进入的。如果一个线程多次获得锁,那么在释放锁的时候,也必须释放相同的次数。

构造器

 常用方法

 1 lock()
 2 
 3 获得锁,如果锁已经被占用,则等待
 4 
 5 lockInterruptibly()
 6 
 7 获得锁,但优先响应中断。如果当前线程未被中断,则获取锁
 8 
 9 tryLock()
10 
11 尝试获得锁,如果成功放回true,失败返回false,该方法不等待,立即返回
12 
13 unlock()
14 
15 释放锁
16 
17 newCondition()
18 
19 返回与lock实例一起使用的Condition对象

案例演示

需求:

  现有三个线程A、B、C,要求这三个线程以A -> B -> C 这种顺序来轮流干活(10个轮回)

具体实现:

通过实现Condition对象来实现多线程之间通信

通过一个变量num来标识当前该哪个线程干活

规定:

num = 1时,线程A干活

num = 2时,线程B干活

num = 3时,线程C干活

测试代码:

  1 package com.lzp.lock.condition;
  2 
  3 import java.util.concurrent.locks.Condition;
  4 import java.util.concurrent.locks.Lock;
  5 import java.util.concurrent.locks.ReentrantLock;
  6 
  7 /**
  8  * @Author LZP
  9  * @Date 2021/7/1 20:41
 10  * @Version 1.0
 11  *
 12  * 需求:
 13  *  现有三个线程A、B、C,要求这三个线程以A -> B -> C 这种顺序来轮流干活(10个轮回)
 14  * 通过实现Condition对象来实现多线程之间的通信
 15  * 通过一个变量num来标识当前该哪个线程干活
 16  * 规定:
 17  * num = 1时,线程A干活
 18  * num = 2时,线程B干活
 19  * num = 3时,线程C干活
 20  */
 21 public class ConditionDemo01 {
 22 
 23     private int num = 1;
 24 
 25     private final Lock lock = new ReentrantLock();
 26     private final Condition cA = lock.newCondition();
 27     private final Condition cB = lock.newCondition();
 28     private final Condition cC = lock.newCondition();
 29 
 30     public void doA() {
 31         // 上锁
 32         lock.lock();
 33         try {
 34             while (num != 1) {
 35                 // 等待
 36                 try {
 37                     cA.await();
 38                 } catch (InterruptedException e) {
 39                     e.printStackTrace();
 40                 }
 41             }
 42             // 干活
 43             for (int i = 0; i < 10; i++) {
 44                 System.out.println(Thread.currentThread().getName() + "\\t" + i);
 45             }
 46             // 修改标志位
 47             num = 2;
 48             // 通知线程B
 49             cB.signal();
 50         } finally {
 51             lock.unlock();
 52         }
 53     }
 54 
 55     public void doB() {
 56         // 上锁
 57         lock.lock();
 58         try {
 59             while (num != 2) {
 60                 // 等待
 61                 try {
 62                     cB.await();
 63                 } catch (InterruptedException e) {
 64                     e.printStackTrace();
 65                 }
 66             }
 67             // 干活
 68             for (int i = 0; i < 10; i++) {
 69                 System.out.println(Thread.currentThread().getName() + "\\t" + i);
 70             }
 71             // 修改标志位
 72             num = 3;
 73             // 通知线程C
 74             cC.signal();
 75         } finally {
 76             lock.unlock();
 77         }
 78     }
 79 
 80     public void doC() {
 81         // 上锁
 82         lock.lock();
 83         try {
 84             while (num != 3) {
 85                 // 等待
 86                 try {
 87                     cC.await();
 88                 } catch (InterruptedException e) {
 89                     e.printStackTrace();
 90                 }
 91             }
 92             // 干活
 93             for (int i = 0; i < 10; i++) {
 94                 System.out.println(Thread.currentThread().getName() + "\\t" + i);
 95             }
 96             // 修改标志位
 97             num = 1;
 98             // 通知线程A
 99             cA.signal();
100         } finally {
101             lock.unlock();
102         }
103     }
104 
105     public static void main(String[] args) {
106         ConditionDemo01 source = new ConditionDemo01();
107 
108         new Thread(source::doA, "A").start();
109         new Thread(source::doB, "B").start();
110         new Thread(source::doC, "C").start();
111     }
112 }

运行结果:

ReentrantLock的特点

1. ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。

2. ReentrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

3. ReentrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。应用场景:需要使用以上三个特点时,使用ReentrantLock。

应用场景:需要使用以上三个特点时,使用ReentrantLock

Condition的使用

1. 使用ReentrantLock类的newCondition()方法可以获取Condition对象

2. 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法

3. 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了

以上是关于Java中juc并发包下的Condition接口与ReentrantLock对象锁实现线程通信的主要内容,如果未能解决你的问题,请参考以下文章

Java并发包4--可重入锁ReentrantLock的实现原理

Java-JUC:使用Lock替换synchronized,使用Condition的await,singal,singalall替换object的wait,notify,notifyall实现线(代码

JUC-Condition线程通信

Java多线程06——JUC并发包02

Day833.Lock和Condition(上):隐藏在并发包中的管程 -Java 并发编程实战

Day833.Lock和Condition(上):隐藏在并发包中的管程 -Java 并发编程实战