Java高并发Lock接口讲解,精准通知线程间的执行顺序
题目:两个线程操作一个变量,实现两个线程对同一个资源一个进行加1操作,另外一个进行减1操作,且需要交替实现,变量的初始值为0。即两个线程对同一个资源进行加一减一交替操作。
Lock接口与Condition接口

JUC指的是上述三个api包,lock接口位于Java.util.concurrent.locks下
既然我们标题上写的是Lock,那我们自然用JUC来解决这个问题:先阅读API:

Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition。- 锁是用于通过多个线程控制对共享资源的访问的工具。 通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,一些锁可能允许并发访问共享资源,如
ReadWriteLock的读锁。 - 使用
synchronized方法或语句提供对与每个对象相关联的隐式监视器锁的访问,但是强制所有锁获取和释放以块结构的方式发生:当获取多个锁时,它们必须以相反的顺序被释放,并且所有的锁都必须被释放在与它们相同的词汇范围内。 - 虽然
synchronized方法和语句的范围机制使得使用监视器锁更容易编程,并且有助于避免涉及锁的许多常见编程错误,但是有时您需要以更灵活的方式处理锁。 例如,用于遍历并发访问的数据结构的一些算法需要使用“手动”或“链锁定”:您获取节点A的锁定,然后获取节点B,然后释放A并获取C,然后释放B并获得D等。 所述的实施方式中Lock接口通过允许获得并在不同的范围释放的锁,并允许获得并以任何顺序释放多个锁使得能够使用这样的技术。

Condition因素出Object监视器方法(wait,notify和notifyAll)成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock个实现。Lock替换synchronized方法和语句的使用,Condition取代了对象监视器方法的使用。- 条件(也称为条件队列或条件变量 )为一个线程暂停执行(“等待”)提供了一种方法,直到另一个线程通知某些状态现在可能为真。 因为访问此共享状态信息发生在不同的线程中,所以它必须被保护,因此某种形式的锁与该条件相关联。 等待条件的关键属性是它原子地释放相关的锁并挂起当前线程,就像
Object.wait。 - 一个
Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。 - 例如,假设我们有一个有限的缓冲区,它支持
put和take方法。 如果在一个空的缓冲区尝试一个take,则线程将阻塞直到一个项目可用; 如果put试图在一个完整的缓冲区,那么线程将阻塞,直到空间变得可用。 我们希望在单独的等待集中等待put线程和take线程,以便我们可以在缓冲区中的项目或空间可用的时候使用仅通知单个线程的优化。 这可以使用两个Condition实例来实现。
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock(); try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally { lock.unlock(); }
}
public Object take() throws InterruptedException {
lock.lock(); try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally { lock.unlock(); }
}
}

即就是原先我们使用synchronized ,wait,notify,现在使用JUC的lock(ReentrantLock),condition,condition.await(), condition.signalAll();

实现Lock接口后的代码如下
class Resource {
private int number = 0;
private Lock lock = new ReentrantLock(); //可重入锁
private Condition condition = lock.newCondition();
public void up() throws InterruptedException{
lock.lock();
try {
while (number != 0) {
condition.await(); //相当于wait
}
number++;
System.out.println(Thread.currentThread().getName()+"\t"+number);
condition.signalAll(); //相当于notifyAll
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void down() throws InterruptedException{
lock.lock();
try {
while (number == 0) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"\t"+number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
结果如下:

那么问题来了,为什么我们要用JUC的方式来替代呢?
因为原本的notify只能唤醒三个里面任意一个(注意这样唤醒并不精准),notifyall唤醒全部。
精准通知线程间的执行顺序
接着看看JUC的优势。这次我们的题目升级:多个线程之间的调用顺序,实现A->B->C:俗称风火轮
要求三个线程的启动顺序如下:
AA打印5次,BB打印10次,CC打印15次,…持续10轮。
代码如下,主方法:
public class ThreadOrderAccess {
public static void main(String[] args) {
ShareResource resource = new ShareResource();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.printf5();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.printf10();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
resource.printf15();
}
}, "C").start();
}
}
资源类:注意标志位的修改和定位
class ShareResource {
private int number = 1;// 1:A 2:B 3:C
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
//注意标志位的修改和定位
//A
public void printf5() {
lock.lock();
try {
//判断
while (number != 1) {
condition1.await();
}
//干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
//通知
number = 2; //此时B、C一直在wait状态中
condition2.signal(); //精确打击 唤醒B
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//B
public void printf10() {
lock.lock();
try {
while (number != 2) {
condition2.await();
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//C
public void printf15() {
lock.lock();
try {
while (number != 3) {
condition3.await();
}
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
}
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
注意这里之后的wait等方法都换成JUC的不然后报错java.lang.IllegalMonitorStateException
部分结果如下:


所以这Lock锁就是对老技术synchronized的优化,精准通知,精准唤醒,通过对指定的condition唤醒来达到对指定的线程的唤醒。
这就是本次的讲解,如果有需要改进的地方,欢迎在下方留言!
Java高并发Lock接口讲解,精准通知线程间的执行顺序的更多相关文章
- Java高并发与多线程(二)-----线程的实现方式
今天,我们开始Java高并发与多线程的第二篇,线程的实现方式. 通常来讲,线程有三种基础实现方式,一种是继承Thread类,一种是实现Runnable接口,还有一种是实现Callable接口,当然,如 ...
- Java高并发与多线程(三)-----线程的基本属性和主要方法
今天,我们开始Java高并发与多线程的第三篇,线程的基本属性和主要方法. [属性] 编号(ID) 类型long 用于标识不同的线程,编号唯一,只存在java虚拟机的一次运行 名称(Name) 类型St ...
- java高并发系列 - 第9天:用户线程和守护线程
守护线程是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程.JIT线程都是守护线程.与之对应的是用户线程,用户线程可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作.如果 ...
- java高并发系列 - 第27天:实战篇,接口性能成倍提升,让同事刮目相看,现学现用
这是java高并发系列第27篇文章. 开发环境:jdk1.8. 案例讲解 电商app都有用过吧,商品详情页,需要给他们提供一个接口获取商品相关信息: 商品基本信息(名称.价格.库存.会员价格等) 商品 ...
- 如何解决java高并发详细讲解
对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了.而并发问题是绝大部分的程序员头疼的问题, 但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研 ...
- Java高并发系列——检视阅读
Java高并发系列--检视阅读 参考 java高并发系列 liaoxuefeng Java教程 CompletableFuture AQS原理没讲,需要找资料补充. JUC中常见的集合原来没讲,比如C ...
- [ 高并发]Java高并发编程系列第二篇--线程同步
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
- Java多线程(五) Lock接口,ReentranctLock,ReentrantReadWriteLock
在JDK5里面,提供了一个Lock接口.该接口通过底层框架的形式为设计更面向对象.可更加细粒度控制线程代码.更灵活控制线程通信提供了基础.实现Lock接口且使用得比较多的是可重入锁(Reentrant ...
- 《实战Java高并发程序设计》读书笔记
文章目录 第二章 Java并行程序基础 2.1 线程的基本操作 2.1.1 线程中断 2.1.2 等待(wait)和通知(notify) 2.1.3 等待线程结束(join)和谦让(yield) 2. ...
- java高并发系列 - 第6天:线程的基本操作
新建线程 新建线程很简单.只需要使用new关键字创建一个线程对象,然后调用它的start()启动线程即可. Thread thread1 = new Thread1(); t1.start(); 那么 ...
随机推荐
- 在WSL Ubuntu中设置sshd自启动
参考: https://blog.csdn.net/android_cai_niao/article/details/128490566 ------------------------------- ...
- 【转载】 tensorflow变量默认是如何进行初始化的?
版权声明:本文为CSDN博主「TahoeWang」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明.原文链接:https://blog.csdn.net/sinat_3 ...
- Pytorch学习率更新
如需了解示例完整代码及其后续内容请访问: https://www.emperinter.info/2020/08/01/learning-rate-in-pytorch/ 缘由 自己在尝试了官方的代码 ...
- 【干货】Apache DolphinScheduler2.0升级3.0版本方案
升级背景 因项目需要使用数据质量模块功能,可以为数仓提供良好的数据质量监控功能.故要对已有2.0版本升级到3.0版本以上,此次选择测试了3.0.1 和 3.1.1 两个版本,对进行同数据等任务调度暂停 ...
- 存储过程中调用EXECUTE IMMEDIATE的“权限不足”
EXECUTE IMMEDIATE是Oracle中使用动态SQL的一种方法,可以直接执行,也可以在存储过程中调用.然而在存储过程中调用可能会遇到权限不足的问题,如在存储过程中执行重建索引语句: sql ...
- JavaScript设计模式样例十四 —— 观察者模式
观察者模式(Observer Pattern) 定义:当一个对象被修改时,则会自动通知它的依赖对象.目的:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被 ...
- Figma 替代品 Penpot 安装和使用教程
在设计领域,Figma 无疑是一个巨人.它彻底改变了设计流程,将协作带到了一个全新的高度.然而,随着 Adobe 收购 Figma 的消息传出,许多设计师和开发者开始担心:Figma 未来会如何演变? ...
- Java多线程并发编程中并发容器第二篇之List的并发类讲解
Java多线程并发编程中并发容器第二篇之List的并发类讲解 概述 本文我们将详细讲解list对应的并发容器以及用代码来测试ArrayList.vector以及CopyOnWriteArrayList ...
- ICMAN触摸滑条滚轮方案
ICMAN触摸滑条滚轮调光是一种利用触摸技术实现的调光控制方式,是一种更简单.直观且节能的调光方式,有效改善了用户的照明体验,并在智能家居和节能照明领域发挥着重要作用. 基于厦门晶尊微电子(ICMAN ...
- CoST: 时间序列预测中分离季节趋势特征的对比学习《CoST: CONTRASTIVE LEARNING OF DISENTANGLED SEASONAL-TREND REPRESENTATIONS FOR TIME SERIES FORECASTING》(时序预测、表征学习、对比学习、因果关系、分离趋势季节特征)
2022/6/18 11:32,简单记录一下随笔(因为不写点东西,根本注意力不集中,看5分钟可能要摸鱼10分钟,还是要写点,突然发现,草稿箱里最早的一篇没写完的博客是去年的7月2日,救命啊,我拖了一年 ...