使用ReentrantLock和Condition来代替内置锁和wait(),notify(),notifyAll()
使用ReentrantLock可以替代内置锁,当使用内置锁的时候,我们可以使用wait() nitify()和notifyAll()来控制线程之间的协作,那么,当我们使用ReentrantLock的时候,我们怎么来处理线程之间的写作呢?
JDK5.0为我们提供了Condition对象来替代内置锁的 wait(),notify()和notifyAll()方法
内置锁的话,就只能有一个等待队列,所有的在某个对象上执行wait()方法的线程都会被加入到该对象的等待队列中去(线程会被挂起),需要其他的线程在同一个对象上调用notify()或者是notifyAll()方法来唤醒等待队列中的线程
而使用Condition的话,可以使用不同的等待队列,只需要使用lock.newCondition()即可定义一个Condition对象,每一个Condition对象上都会有一个等待队列(底层使用AQS),调用某个Condition对象的await()方法,就可以把当前线程加入到这个Condition对象的等待队列上
其他的线程调用同一个Condition对象的sinal()或者是signalAll()方法则会唤醒等待队列上的线程,使其能够继续执行
我们以一个现实中的例子来说明若何使用ReentrantLock和Condition如何替代synchronized和wait(),notify(),notifyAll():
我们模拟两个线程,一个线程执行登录操作,该登录操作会阻塞,然后等待另外一个线程将其唤醒(类似扫描登录的场景,页面会阻塞,等待扫码和确认,然后页面才会跳转)
首先是使用内置锁的例子:
package com.jiaoyiping.baseproject.condition;
import java.io.Serializable;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
/**
* Created with Intellij IDEA
*
* @author: jiaoyiping
* Mail: jiaoyiping@gmail.com
* Date: 2019/04/12
* Time: 15:29
* To change this template use File | Settings | Editor | File and Code Templates
*/
//使用内置锁来实现的等待/通知模型
public class LoginServiceUseInnerLock {
private ConcurrentHashMap<String, Result> loginMap = new ConcurrentHashMap<>();
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
LoginServiceUseInnerLock loginService = new LoginServiceUseInnerLock();
String uuid = UUID.randomUUID().toString();
System.out.println("[" + Thread.currentThread().getName() + "] 使用的UUID是: " + uuid);
new Thread(() -> {
loginService.login(uuid, 20_000);
countDownLatch.countDown();
}, "登录线程").start();
Thread.sleep(2_000);
new Thread(() -> {
loginService.confirm(uuid);
countDownLatch.countDown();
}, "确认线程").start();
countDownLatch.await();
System.out.println("[" + Thread.currentThread().getName() + "] 两个线程都执行完毕了");
}
public void login(String code, int timeout) {
Result result = new Result();
result.setMessage("超时");
loginMap.put(code, result);
synchronized (result) {
try {
//超时的话,会自动返回,程序继续
System.out.println("[" + Thread.currentThread().getName() + "] 登录线程挂起");
result.wait(timeout);
System.out.println("[" + Thread.currentThread().getName() + "] 登录线程继续执行,得到的结果是:" + result.getMessage());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
loginMap.remove(code);
}
}
}
public void confirm(String code) {
assert code != null;
Result result = loginMap.get(code);
if (result == null) {
System.out.println("[" + Thread.currentThread().getName() + "] 请求不存在或者已经过期");
return;
}
result.setMessage("成功");
synchronized (result) {
//唤醒等待队列上的线程
System.out.println("[" + Thread.currentThread().getName() + "] 确认线程开始唤醒阻塞的线程");
result.notify();
}
}
class Result implements Serializable {
private static final long serialVersionUID = -4279280559711939661L;
String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Result() {
}
public Result(String message) {
this.message = message;
}
}
使用内置锁的时候,我们把random生成的key和一个自己定义的Result对象放置到ConcurrentHashMap中去,登录线程调用 Result对象的wait(timeout) 方法将当前线程挂起,并加入到Result对象的等待队列上去
确认线程根据key值,找到对应的Result对象,设置好message,然后调用Result对象的notify()方法唤醒等待队列上的线程,登录线程得以继续执行
那我们如何使用ReentrantLock和Condition来重写这个例子:
package com.jiaoyiping.baseproject.condition;
import java.io.Serializable;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created with Intellij IDEA
*
* @author: jiaoyiping
* Mail: jiaoyiping@gmail.com
* Date: 2019/04/12
* Time: 14:56
* To change this template use File | Settings | Editor | File and Code Templates
*/
//使用ReentrantLock和Condition来实现的等待/通知模型
public class LoginServiceUseCondition {
private ReentrantLock lock = new ReentrantLock();
ConcurrentHashMap<String, Result> conditions = new ConcurrentHashMap<>();
public static void main(String[] args) throws InterruptedException {
LoginServiceUseCondition loginService = new LoginServiceUseCondition();
String uuid = UUID.randomUUID().toString();
System.out.println("[" + Thread.currentThread().getName() + "] 使用的UUID是:" + uuid);
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(() -> {
loginService.login(uuid, 30_000);
countDownLatch.countDown();
}, "登录线程").start();
Thread.sleep(5_000);
new Thread(() -> {
try {
Thread.sleep(3_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
loginService.confirm(uuid);
countDownLatch.countDown();
}, "确认线程").start();
countDownLatch.await();
System.out.println("[" + Thread.currentThread().getName() + "] 两个线程都执行完毕了,退出");
}
/**
* 过了超时时间之后,锁会自动释放
*
* @param code
* @param timeout
*/
public void login(String code, int timeout) {
assert code != null;
try {
lock.tryLock(timeout, TimeUnit.MILLISECONDS);
Condition condition = lock.newCondition();
Result result = new Result("超时", condition);
conditions.put(code, result);
System.out.println("[" + Thread.currentThread().getName() + "] login()的请求开始阻塞");
condition.await();
System.out.println("[" + Thread.currentThread().getName() + "] 结束等待,继续执行,拿到的结果是" + result.getMessage());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//确认线程(拿这个UUID,去找到对应的Condition,唤醒上边的等待队列,并把Condition对象移除掉)
public void confirm(String code) {
assert code != null;
Result result = conditions.get(code);
Condition condition = result.getCondition();
if (condition != null) {
try {
System.out.println("[" + Thread.currentThread().getName() + "] 找到对应的Condition对象,将其等待队列中的线程唤醒");
lock.lock();
result.setMessage("成功");
condition.signal();
conditions.remove(code);
} finally {
lock.unlock();
}
}
}
class Result implements Serializable {
String message;
final Condition condition;
public Result(String message, Condition condition) {
this.message = message;
this.condition = condition;
}
public Result(Condition condition) {
this.condition = condition;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Condition getCondition() {
return condition;
}
}
}
上边的例子说明了怎么使用ReentrantLock和Condition来代替内置锁和wait(),notify(),notifyAll()
下边的一个来自jdk中的例子,演示了如何使用同一个ReentrantLock上的多个等待队列的情况
来自JDK文档中的示例(我稍加改造,加上了main方法和一些日志):
package com.jiaoyiping.baseproject.condition;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.IntStream;
/**
* Created with Intellij IDEA
*
* @author: jiaoyiping
* Mail: jiaoyiping@gmail.com
* Date: 2019/04/12
* Time: 21:17
* To change this template use File | Settings | Editor | File and Code Templates
*/
public class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[10];
int putptr, takeptr, count;
public static void main(String[] args) throws InterruptedException {
BoundedBuffer boundedBuffer = new BoundedBuffer();
CountDownLatch countDownLatch = new CountDownLatch(40);
//分别启动20个put线程和20个take线程
IntStream.rangeClosed(1, 20).forEach(i -> {
new Thread(() -> {
try {
boundedBuffer.put(new Object());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}, "put线程 - " + i).start();
});
IntStream.rangeClosed(1, 20).forEach(i -> {
new Thread(() -> {
try {
boundedBuffer.take();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}, "take线程-" + i).start();
});
countDownLatch.await();
System.out.println("[" + Thread.currentThread().getName() + "] 所有线程都执行完毕,退出");
}
public void put(Object x) throws InterruptedException {
lock.lock();
try {
//put的线程,当队列满的时候挂起
while (count == items.length) {
System.out.println("[" + Thread.currentThread().getName() + "] 线程挂起");
notFull.await();
}
Thread.sleep(1_000);
items[putptr] = x;
if (++putptr == items.length) {
putptr = 0;
}
++count;
System.out.println("[" + Thread.currentThread().getName() + "] 执行完毕写操作,唤醒take线程");
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
//take的线程,当队列为空的时候,挂起
while (count == 0) {
System.out.println("[" + Thread.currentThread().getName() + "] 线程挂起");
notEmpty.await();
}
Thread.sleep(1_000);
Object x = items[takeptr];
if (++takeptr == items.length) {
takeptr = 0;
}
--count;
System.out.println("[" + Thread.currentThread().getName() + "] 执行完毕读操作,唤醒put线程");
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
我们看到,以上代码中,使用到了两个Condition:notFull和notEmpty,都是通过lock对象的newCondition()方法得来的
items被放满之后,put的线程会在notFull的等待队列上进行等待(执行了notFull.await()方法) put线程执行完操作之后,会调用 notEmpty.signal()来试图唤醒在notEmpty上等待的线程(也就是给take线程发了一个信号,告诉它,items不是空的了,你可以过来take了)
当item空了之后,take线程会在notEmpty的等待队列上进行等待(执行了notEmpty的await()方法) 当take线程执行完操作之后,会调用notFull.signal()来唤醒在notFull上等待的线程(也就是给put线程发一个信号,告诉它,items不满了,你可以进行put操作了)
和内置方法类似,在调用await(),signal(),signalAll()等方法的时候,也必须要获得锁,也就是必须在 lock.lock()和lock.unlock()代码块儿之间才能调用这些方法,否则就会抛出IllegalMonitorStateException
使用ReentrantLock和Condition来代替内置锁和wait(),notify(),notifyAll()的更多相关文章
- 深入理解java内置锁(synchronized)和显式锁(ReentrantLock)
多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两种同步方式.显式锁是JDK1.5引入的,这两种锁有什么异同呢? ...
- 七 内置锁 wait notify notifyall; 显示锁 ReentrantLock
Object中对内置锁进行操作的一些方法: Java内置锁通过synchronized关键字使用,使用其修饰方法或者代码块,就能保证方法或者代码块以同步方式执行. 内置锁使用起来非常方便,不需要显式的 ...
- 深入理解Java内置锁和显式锁
synchronized and Reentrantlock 多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两 ...
- 深刨显式锁ReentrantLock原理及其与内置锁的区别,以及读写锁ReentrantReadWriteLock使用场景
13.显示锁 在Java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile.Java5.0增加了一种新的机制:ReentrantLock.与之前提到过的机 ...
- JDK内置锁深入探究
一.序言 本文讲述仅针对 JVM 层次的内置锁,不涉及分布式锁. 锁有多种分类形式,比如公平锁与非公平锁.可重入锁与非重入锁.独享锁与共享锁.乐观锁与悲观锁.互斥锁与读写锁.自旋锁.分段锁和偏向锁/轻 ...
- java并发编程(一)可重入内置锁
每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块 ...
- synchronized内置锁
synchronized内置锁,如果发生阻塞,无法被中断,除非关闭jvm.因此不能从死锁中恢复.
- java synchronized内置锁的可重入性和分析总结
最近在读<<Java并发编程实践>>,在第二章中线程安全中降到线程锁的重进入(Reentrancy) 当一个线程请求其它的线程已经占有的锁时,请求线程将被阻塞.然而内部锁是可重 ...
- 转:【Java并发编程】之一:可重入内置锁
每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块 ...
随机推荐
- asp.net mvc ViewData 和 ViewBag区别,TempData
ViewData 和 ViewBag都是页面级别的生命周期,TempData--Passing data between the current and next HTTP requests Temp ...
- Hibernate(11)_基于外键的双向1对1
一.基于外键的双向1对1 对于基于外键的1-1关联,其外键可以存放在任意一边,在需要存放外键一端,增加many-to-one元素.为many-to-one元素增加unique="true&q ...
- 安装bootcamp时提示“找不到$winpedriver$文件夹,请验证该文件夹是否和bootcamp处于同一文件夹内?”
问题:我苹果系统是10.8.3的 装的win7 64位的! 这个bootcamp是我在别人那里拷贝的,我装的时候就这样了,但是别人装是好好的,还有我在MAC系统下载bootcamp的时候我的U盘格式是 ...
- ASP.NET微信公众号获取AccessToken
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token.开发者需要进行妥善保存.access_token的存储至少要保留512个字符空间.acces ...
- SpringBoot之整合Redis分析和实现-基于Spring Boot2.0.2版本
背景介绍 公司最近的新项目在进行技术框架升级,基于的Spring Boot的版本是2.0.2,整合Redis数据库.网上基于2.X版本的整个Redis少之又少,中间踩了不少坑,特此把整合过程记录,以供 ...
- 有钱人都用iphone?
身边的朋友用iphone, 大家都会调侃真有钱. 大数据显示, 人家是真的有钱. 看看来自腾讯移动分析数据 你玩过抖音就知道, 拍视频, 传视频的过的看着都挺滋润的. 反观用安卓的用户, 前20个应用 ...
- Xtrabackup简介
Xtrabackup是由 Percona 开发的一个开源软件,可实现对 InnoDB 的数据备份,支持在线热备份(备份时不影响数据读写),特点如下: 备份过程快速.可靠: 备份过程不会打断正在执行的事 ...
- P Invoke struct结构
一.获取Struct CHCNetSDK.NET_DVR_PTZPOS pos = new CameraTest.CHCNetSDK.NET_DVR_PTZPOS(); int size = Mars ...
- celery --分布式任务队列
一.介绍 celery是一个基于python开发的分布式异步消息任务队列,用于处理大量消息,同时为操作提供维护此类系统所需的工具. 它是一个任务队列,专注于实时处理,同时还支持任务调度.如果你的业务场 ...
- 【ML入门系列】(三)监督学习和无监督学习
概述 在机器学习领域,主要有三类不同的学习方法: 监督学习(Supervised learning) 非监督学习(Unsupervised learning) 半监督学习(Semi-supervise ...