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实现线(代码