题目:两个线程操作一个变量,实现两个线程对同一个资源一个进行加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 , notifynotifyAll )成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock实现。 Lock替换synchronized方法和语句的使用, Condition取代了对象监视器方法的使用。
  • 条件(也称为条件队列条件变量 )为一个线程暂停执行(“等待”)提供了一种方法,直到另一个线程通知某些状态现在可能为真。 因为访问此共享状态信息发生在不同的线程中,所以它必须被保护,因此某种形式的锁与该条件相关联。 等待条件的关键属性是它原子地释放相关的锁并挂起当前线程,就像Object.wait 。
  • 一个Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。
  • 例如,假设我们有一个有限的缓冲区,它支持puttake方法。 如果在一个空的缓冲区尝试一个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接口讲解,精准通知线程间的执行顺序的更多相关文章

  1. Java高并发与多线程(二)-----线程的实现方式

    今天,我们开始Java高并发与多线程的第二篇,线程的实现方式. 通常来讲,线程有三种基础实现方式,一种是继承Thread类,一种是实现Runnable接口,还有一种是实现Callable接口,当然,如 ...

  2. Java高并发与多线程(三)-----线程的基本属性和主要方法

    今天,我们开始Java高并发与多线程的第三篇,线程的基本属性和主要方法. [属性] 编号(ID) 类型long 用于标识不同的线程,编号唯一,只存在java虚拟机的一次运行 名称(Name) 类型St ...

  3. java高并发系列 - 第9天:用户线程和守护线程

    守护线程是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程.JIT线程都是守护线程.与之对应的是用户线程,用户线程可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作.如果 ...

  4. java高并发系列 - 第27天:实战篇,接口性能成倍提升,让同事刮目相看,现学现用

    这是java高并发系列第27篇文章. 开发环境:jdk1.8. 案例讲解 电商app都有用过吧,商品详情页,需要给他们提供一个接口获取商品相关信息: 商品基本信息(名称.价格.库存.会员价格等) 商品 ...

  5. 如何解决java高并发详细讲解

    对于我们开发的网站,如果网站的访问量非常大的话,那么我们就需要考虑相关的并发访问问题了.而并发问题是绝大部分的程序员头疼的问题, 但话又说回来了,既然逃避不掉,那我们就坦然面对吧~今天就让我们一起来研 ...

  6. Java高并发系列——检视阅读

    Java高并发系列--检视阅读 参考 java高并发系列 liaoxuefeng Java教程 CompletableFuture AQS原理没讲,需要找资料补充. JUC中常见的集合原来没讲,比如C ...

  7. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  8. Java多线程(五) Lock接口,ReentranctLock,ReentrantReadWriteLock

    在JDK5里面,提供了一个Lock接口.该接口通过底层框架的形式为设计更面向对象.可更加细粒度控制线程代码.更灵活控制线程通信提供了基础.实现Lock接口且使用得比较多的是可重入锁(Reentrant ...

  9. 《实战Java高并发程序设计》读书笔记

    文章目录 第二章 Java并行程序基础 2.1 线程的基本操作 2.1.1 线程中断 2.1.2 等待(wait)和通知(notify) 2.1.3 等待线程结束(join)和谦让(yield) 2. ...

  10. java高并发系列 - 第6天:线程的基本操作

    新建线程 新建线程很简单.只需要使用new关键字创建一个线程对象,然后调用它的start()启动线程即可. Thread thread1 = new Thread1(); t1.start(); 那么 ...

随机推荐

  1. Linux环境下配置vscode的C/C++ 的make编译环境(编写makefile方式)代码Demo版

    以前写过同样话题下的图文版的,这里给出一个代码Demo版本,上一个图文版本参见: Linux环境下配置vscode的C/C++ 的make编译环境(编写makefile方式) ============ ...

  2. 读论文《IMPALA: Scalable Distributed Deep-RL with Importance Weighted Actor-Learner Architectures》

    论文地址: https://arxiv.org/pdf/1802.01561v2.pdf 论文<IMPALA: Scalable Distributed Deep-RL with Importa ...

  3. 获取客户端真实IP备忘

    出于安全考虑,近期在处理一个记录用户真实IP的需求.本来以为很简单,后来发现没有本来以为的简单.这里主要备忘下,如果服务器处于端口回流(hairpin NAT),keepalived,nginx之后, ...

  4. JavaFx加载本地工程图片报错

    原因有两点: 1.png或者其他格式的图片文件没有放在src的直接目录下 2.路径填错,多了个符号等 正确姿势应该是 package com.fubao.tools.fx.Stage; import ...

  5. 高级工程师面试大全- java基础篇

    1.什么是java虚拟机 JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功 ...

  6. jxls导入excel

    我们在开发中经常用jxls实现导出功能,殊不知jxls也有导入功能,下面来介绍下如何使用jxls导入excel. 首先在maven的pom中添加相关依赖,如下: <dependency> ...

  7. 用whl文件安装Anaconda中的GDAL

      本文介绍在Anaconda环境下,基于.whl文件安装Python中高级地理数据处理库GDAL的方法.   在之前的文章中,我们介绍了基于conda install命令直接联网安装GDAL库的方法 ...

  8. 【Python】之操作鼠标键盘,上传文件,并支持中文

    Mac系统实现操作键盘 Python中模拟键盘和鼠标最著名的模块是:pymouse和pykeyboard.一次安装两个模块比较麻烦,而有一个库整合了这两个模块,而且能支持跨平台操作,这个库叫PyUse ...

  9. 【GitHub】上传代码通用操作等(附下载单个文件夹或文件)

    一.创建GitHub账号以及配置 参考我的另一篇文章:<[Mac系统 + Git]之上传项目代码到github上以及删除某个文件夹> 二.创建新的个人知识库 前面配置完之后,下面讲的再新建 ...

  10. WiFi基础(四):WiFi工作原理及WiFi接入过程

    liwen01 2024.09.16 前言 802.11 无线 WiFi 网有三类帧:数据帧.管理帧.控制帧.与有线网相比,无线 WiFi 网会复杂很多.大部分应用软件开发对 WiFi 的控制帧和管理 ...