java ReentrantLock结合条件队列 实现生产者-消费者模式 以及ReentratLock和Synchronized对比
package reentrantlock;
import java.util.ArrayList;
public class ProviderAndConsumerTest {
static ProviderAndConsumer providerAndConsumer = new ProviderAndConsumer();
public static void main(String[] args) throws InterruptedException {
// new Thread(new GetRunnable(), "消费者002").start();
// Thread.sleep(1000);
// new Thread(new PutRunnable(), "生产者001").start();
ArrayList<Thread> provider = new ArrayList<>();
for (int i = 0; i < 3; i++){
provider.add(new Thread(new PutRunnable(), "生产者00"+ (i+1)));
}
ArrayList<Thread> consumer = new ArrayList<>();
for (int i = 0; i < 3; i++){
consumer.add(new Thread(new GetRunnable(), " 消费者--99"+ (i+1)));
}
for (Thread i :
consumer) {
i.start();
}
// 先让消费者线程全部饥饿,进入消费者条件队列中
Thread.sleep(10000);
for (Thread i :
provider) {
i.start();
}
}
static class PutRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++){
providerAndConsumer.put(" (" +Thread.currentThread().getName() + "_data_" + i + ")");
try {
// 调整睡眠时间,等同于调整生产者生产数据的频率,但是不准,因为跟生产者内部逻辑执行时间有很大关系
Thread.sleep(500,1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class GetRunnable implements Runnable{
@Override
public void run() {
while(true){
providerAndConsumer.get();
try {
// 调整睡眠时间,等同于调整消费者消费数据的频率,但是不准,因为跟消费者内部执行时间有很大关系
Thread.sleep(10,1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
package reentrantlock; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; public class ProviderAndConsumer { ReentrantLock reentrantLock = new ReentrantLock();
Condition notEmpty = reentrantLock.newCondition();
Condition notFull = reentrantLock.newCondition();
int maxSize = 3;
int putIndex = 0;
int getIndex = 0;
int realDataCount = 0;
Object[] queue = new Object[maxSize]; boolean isConsumerWait = false;
boolean isProviderWait = false; public void put(Object data){
System.out.println(Thread.currentThread().getName() + "线程,-----尝试加锁-----");
reentrantLock.lock();
System.out.println(Thread.currentThread().getName() + "线程,-----加锁成功-----");
try { while (realDataCount == queue.length){
System.out.println(Thread.currentThread().getName() + "线程,-----数据满了,只好等待----" +
"哈哈要进生产者条件队列啦");
isProviderWait = true;
notFull.await();
} queue[putIndex] = data;
realDataCount++; putIndex++;
if (putIndex == queue.length) putIndex = 0;
System.out.println(Thread.currentThread().getName() + "线程,成功生产一个数据=" + data.toString()); if (isConsumerWait){
System.out.println(Thread.currentThread().getName() + "线程," +
" 未 激活消费者条件队列节点前,获取消费者队列长度"
+ reentrantLock.getWaitQueueLength(notEmpty));
}
notEmpty.signal();
if (isConsumerWait){
int length = reentrantLock.getWaitQueueLength(notEmpty);
if (length == 0){
isConsumerWait = false;
}
System.out.println(Thread.currentThread().getName() + "线程," +
" 已 激活消费者条件队列节点后,获取消费者队列长度"
+ reentrantLock.getWaitQueueLength(notEmpty));
}
// 调整睡眠时间,等同于调整生产者持有锁的时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "线程,成功解锁");
reentrantLock.unlock();
} } public void get(){
System.out.println("\t\t" + Thread.currentThread().getName() + "线程,尝试加锁");
reentrantLock.lock();
System.out.println("\t\t" + Thread.currentThread().getName() + "线程,-----加锁成功----"); try { while (realDataCount == 0){
System.out.println("\t\t" + Thread.currentThread().getName() + "线程,-----没有数据,只好等待----"+
"哈哈 要进消费者条件队列啦");
isConsumerWait = true;
notEmpty.await();
} System.out.println("\t\t" + Thread.currentThread().getName() + "线程,成功消费一个数据=" + queue[getIndex]);
realDataCount--; getIndex++;
if (getIndex == queue.length) getIndex = 0; if (isProviderWait){
System.out.println("\t\t" + Thread.currentThread().getName() + "线程," +
" 未 激活生产者条件队列节点前,获取生产者队列长度"
+ reentrantLock.getWaitQueueLength(notFull));
}
notFull.signal();
if (isProviderWait){
int length = reentrantLock.getWaitQueueLength(notFull);
if (length == 0){
isProviderWait = false;
}
System.out.println("\t\t" + Thread.currentThread().getName() + "线程," +
" 已 激活生产者条件队列节点后,获取生产者队列长度"
+ length);
} } catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("\t\t" + Thread.currentThread().getName() + "线程,成功解锁");
reentrantLock.unlock();
}
} }
跑通上面的例子可以得到一些总结:
1、在生产者-消费者模式下,消费者线程和生产者线程都在抢占cpu,谁抢到cpu谁就得到执行。抢不到的会进入AQS队列。
此时如果有多个线程在抢占不到cpu进入AQS队列时,进入AQS队列的顺序是不可预知的(比如恰好经历线程切换),
但可以保证的是: 一旦在AQS队列里,顺序就是绝对从前往后的。
换句话说,先尝试获取锁的线程在获取不到锁需要进入AQS队列时,不一定就比其他紧接着(也就是说几乎同时,但相对靠后)
尝试获取锁同样获取不到锁需要进入AQS队列要进入队列早。(比如恰好经历线程切换)
2、那什么时候会进入条件队列呢?什么时候出条件对列呢?
当消费者线程获取到cpu,但此时,没有任何数据可供消费,那么当前的这个消费者线程就会释放(完全释放,因为是可重入)锁并让出cpu资源(线程挂起)然后进入消费者条件队列
当接下来有生产者线程得到cpu执行并生产出数据后,生产者线程就会唤醒“消费者条件队列”中的第一个线程,并把第一个线程加入到AQS队列的尾部
对于“生产者条件对列”,跟上面是一个道理。
3、生产者-消费者模式中的数据缓存区,有一个生产者生产指针,指向下一个生产数据应该放的位置,有一个消费者消费指针,指向下一个要消费的数据的位置。
4、永远记住,锁只是一种资源控制方式,任何行为都不能控制cpu在何时切换,以及cpu切换到哪个线程。

ReentratLock和Synchronized对比:
相同点:
1、都是独占锁。synchronized属于隐式锁,编码简单。ReentrantLock需要手动控制加锁解锁,必须配对使用,控制灵活,但是相对技术要求高。
2、都具有可重入性。synchronized可重入简单,用在复杂场景中(比如递归)不需要考虑锁释放的问题。ReentrantLock手动控制,必须配对,在复杂场景中,对技术要求高,否则带来锁不能正确释放的问题。
不同点:
1、灵活性。synchronized属于非公平锁,cpu切换到哪个线程,哪个线程就获得执行。ReentrantLock可灵活配置公平锁、非公平锁。
2、是否可被中断。synchronized不可被中断,一直阻塞。ReentrantLock可被中断,更好的用于解决死锁问题。
3、ReentrantLock使用场景更多。可设置超时时间。可结合条件队列实现 等待-通知机制(例如生产者-消费者模式)
java ReentrantLock结合条件队列 实现生产者-消费者模式 以及ReentratLock和Synchronized对比的更多相关文章
- Java并发(基础知识)—— 阻塞队列和生产者消费者模式
1.阻塞队列 Blocki ...
- Java Thread系列(十)生产者消费者模式
Java Thread系列(十)生产者消费者模式 生产者消费者问题(producer-consumer problem),是一个多线程同步问题的经典案例.该问题描述了两个共亨固定大小缓冲区的线程-即所 ...
- Java并发编程()阻塞队列和生产者-消费者模式
阻塞队列提供了可阻塞的put和take方法,以及支持定时的offer和poll方法.如果队列已经满了,那么put方法将阻塞直到有空间可用:如果队列为空,那么take方法将会阻塞直到有元素可用.队列可以 ...
- Java多线程—阻塞队列和生产者-消费者模式
阻塞队列支持生产者-消费者这种设计模式.该模式将“找出需要完成的工作”与“执行工作”这两个过程分离开来,并把工作项放入一个“待完成“列表中以便在随后处理,而不是找出后立即处理.生产者-消费者模式能简化 ...
- Java的设计模式(7)— 生产者-消费者模式
生产者-消费者模式是一个经典的多线程设计模式,它为多线程间的协作提供了良好的解决方案.这个模式中,通常有两类线程,即若干个生产者线程和若干个消费者线程.生产者线程负责提交用户请求,消费者线程则负责具体 ...
- python进阶:Python进程、线程、队列、生产者/消费者模式、协程
一.进程和线程的基本理解 1.进程 程序是由指令和数据组成的,编译为二进制格式后在硬盘存储,程序启动的过程是将二进制数据加载进内存,这个启动了的程序就称作进程(可简单理解为进行中的程序).例如打开一个 ...
- Java设计模式—生产者消费者模式(阻塞队列实现)
生产者消费者模式是并发.多线程编程中经典的设计模式,生产者和消费者通过分离的执行工作解耦,简化了开发模式,生产者和消费者可以以不同的速度生产和消费数据.这篇文章我们来看看什么是生产者消费者模式,这个问 ...
- 10 阻塞队列 & 生产者-消费者模式
原文:http://www.cnblogs.com/dolphin0520/p/3932906.html 在前面我们接触的队列都是非阻塞队列,比如PriorityQueue.LinkedList(Li ...
- 【多线程】java多线程实现生产者消费者模式
思考问题: 1.为什么用wait()+notify()实现生产者消费者模式? wait()方法可以暂停线程,并释放对象锁 notify()方法可以唤醒需要该对象锁的其他线程,并在执行完后续步骤,到了s ...
随机推荐
- Git源码管理工具使用
注明:双击tap键为自动补全操作 1.视频地址:http://www.newbieol.com/course/index_102.html 2.sourcetree是一个拥有界面的git工具吧 下载官 ...
- miui 系统铃声
MIUI7-8系统铃声和通知铃声等,从miui system.img中提取出来的: 链接:http://pan.baidu.com/s/1bpH5N5P 密码:tz7p
- 如何解决Redis中的key过期问题
最近我们在Redis集群中发现了一个有趣的问题.在花费大量时间进行调试和测试后,通过更改key过期,我们可以将某些集群中的Redis内存使用量减少25%. Twitter内部运行着多个缓存服务.其中一 ...
- 【linux】工作中linux系统常用命令操作整理
1.Linux如何查看端口 使用lsof(list open files)命令,lsof -i:端口号 用于查看某一端口的占用情况,比如查看8000端口使用情况,lsof -i:8000. 或者使用n ...
- Kafka分区分配策略(Partition Assignment Strategy
问题 用过 Kafka 的同学用过都知道,每个 Topic 一般会有很多个 partitions.为了使得我们能够及时消费消息,我们也可能会启动多个 Consumer 去消费,而每个 Consumer ...
- Neo4j安装
一.Windows版本 1)下载java8,并配置环境变量 java下载请点击,提取码:f6ci 2)Neo4j下载 选windows版本 新建系统环境变量: 并配置Path环境变量,添加bin所在目 ...
- 人生苦短,Let's Go目录
目录 GO语言系列(一)- 初识go语言 GO语言系列(二)- 基本数据类型和操作符 Go语言系列(三)- 基础函数和流程控制 GO语言系列(四)- 内置函数.闭包与高级数据类型 GO语言系列(五)- ...
- 从线性模型(linear model)衍生出的机器学习分类器(classifier)
1. 线性模型简介 0x1:线性模型的现实意义 在一个理想的连续世界中,任何非线性的东西都可以被线性的东西来拟合(参考Taylor Expansion公式),所以理论上线性模型可以模拟物理世界中的绝大 ...
- BeanUtils 日期转换(本地格式yyyy-MM-dd)转换成date
1.BeanUtils工具的使用 1)beanUtils 可以便于对javaBean的属性进行赋值. 2)beanUtils 可以便于对javaBean的对象进行赋值. 3)beanUtils可以将一 ...
- python魔法方法之构造和析构
python的类实例化的时候会默认执行该类的构造方法_init_ class Rectangle: def __init__(self,x,y): self.x=x self.y=y def getA ...