题目:两个线程操作一个变量,实现两个线程对同一个资源一个进行加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. Redis源码安装(Linux环境)

    下载源码: wget https://download.redis.io/redis-stable.tar.gz 解压: tar -xzvf redis-stable.tar.gz 编译&安装 ...

  2. 【导师招募】Apache DolphinScheduler 社区又又又入选开源之夏啦!

    很高兴和大家宣布,Apache DolphinScheduler 社区今年再次成功入选入选由中国科学院软件研究所开源软件供应链点亮计划发起的"开源之夏"活动. 入选公示链接:htt ...

  3. 《重学Java设计模式》笔记——建造者模式

    1. 建造者模式可以解决什么问题 我家里有各种形状的瓷器,盘子或者碗.虽然形状不同,但是所用的材料基本上是一样的,比如土.水.釉.彩这些基本的东西. 但是做不同款式的瓷器,方法是不同的.假如说我现在已 ...

  4. 推荐7款美观且功能强大的WPF UI库

    前言 经常看到有小伙伴在DotNetGuide技术社区交流群里提问:WPF有什么好用或者好看的UI组件库推荐的?,今天大姚给大家分享7款开源.美观.功能强大.简单易用的WPF UI组件库. WPF介绍 ...

  5. 虚拟文件系统VFS-片段一

    文件系统类型 基于磁盘的文件系统 如FAT.EXT4 虚拟文件系统 如proc 网络文件系统 顾名思义,网络文件系统还将网络通信封装起来,这意味可以直接通过通信访问另一台设备的文件系统. man fs ...

  6. shiro的rememberMe各种漏洞一刀切解决

    rememberMe的低版本AES固定密码导致的漏洞,高版本仍然有被爆破,穷举的风险等.这种东西总是在安全检测的时候被拿出来说事儿,然而项目中并未开启rememberMe,也就是说压根不需要这个功能. ...

  7. Unrecognized SSL message, plaintext connection?

    报错:Unrecognized SSL message, plaintext connection? 修改:把 requestContext.setScheme(Scheme.HTTPS);修改为 r ...

  8. Go实现常用的排序算法

    一.插入排序 1.从第一个元素开始,该元素可以认为已经被排序 2.取出下一个元素,在已经排序的元素序列中从后向前扫描 3.如果该元素(已排序)大于新元素,将该元素移到下一位置 4.重复步骤3,直到找到 ...

  9. CSS & JS Effect – 画三角形 Triangle

    前言 画三角形有什么用? 可以做这样的 Design 参考 5 Ways To Create A Triangle With CSS Border Triangle 用 border 做 三角形应该是 ...

  10. CSS – Width & Height Auto and Percentage

    前言 这个非常基础, 我在学 W3Schools 之前就已经会了, 所以之前一直没用记入起来. 但偶尔遇到一些场景时还是会被坑到. 所以特别写这一篇, 提升记忆. width / height aut ...