一、协调生产 / 生产的需要
本文内容次要想向大家介绍一下 Lock 联合 Condition 的应用办法,为了更好的了解 Lock 锁与 Condition 锁信号,咱们来手写一个 ArrayBlockingQueue。JDK 实际上曾经有这个类,基于 Lock 锁与 Condition 锁信号实现的,当然 JDK 实现代码很简单蕴含了更谨严的逻辑校验,以及从性能优化的角度做了更多的工作。本文中咱们只是来简略实现一下其外围逻辑:
- ArrayBlockingQueue 初始化结构时指定容量下限最大值
- 提供 put 办法,当达到 Queue 队列容量下限最大值,阻塞生产数据的线程。
- put 办法生产数据之后,队列必定是不为空,告诉消费者线程进行生产。
- 提供 take 办法,当 Queue 队列容量为 0 时候,阻塞生产数据的线程。
- take 办法执行之后,队列必定不是满的,告诉生产者线程进行生产。
- 一条数据只能被 take 一次,take 之后数据从 queue 中删除
置信实现实现下面的逻辑之后,java 并发编程之 Lock 锁与 Condition 锁信号,你必定是把握了!其实这个逻辑基本上就是 kafka 生产者客户端缓冲队列,批量进行数据发送的实现逻辑。区别是 take 办法一次取出缓冲区所有数据,本文 take 办法一次取出一条数据。
二、构造方法
结构队列的办法很简略,应用一个 List 作为数据存储队列,并指定其容量。到此咱们还没有实现容量判断,以及阻塞线程的性能。
// 类成员变量 - 存储数据的队列
private List<Object> queue;
// 类成员变量 - 存储队列的容量下限
private int queueSize;
public MyBlockingQueue(int queueSize) {
this.queueSize = queueSize;
queue = new ArrayList<>(queueSize);// 存储音讯的汇合
}
三、Lock& Condition 逻辑设计
首先咱们要有一把锁,保证数据 put 与 take 操作的同步性,即:一条数据只能被 take 一次,take 之后数据从 queue 中删除;以及创立 Condition 逻辑都须要 Lock 锁。学过 java 根底并发编程的同学,能够把 Lock 锁了解为 Synchronized 同步代码块性能是一样的。我写过一个专栏《java 并发编程》中介绍了二者的区别,欢送关注。
private Lock lock = new ReentrantLock();// 锁
Condition 逻辑大家能够了解为传统 JDK 多线程编程中的 wait 与 notify,然而 Condition 的语义更容易被了解。如下文代码所示:
private Condition notFull = lock.newCondition(); // 队列不为满
notFull.signal(); // 告诉生产者队列不为满,能够持续生产数据(通常在消费者拿走数据之后,调用)
notFull.await(); // 队列已满,阻塞生产线程(await 对 condition 逻辑取反)
private Condition notEmpty = lock.newCondition(); // 队列不为空
notEmpty.signal(); // 告诉消费者线程队列不为空,能够持续生产数据(通常在生产者生产数据之后,调用)
notEmpty.await(); // 队列曾经空了,阻塞生产线程(await 对 condition 逻辑取反)
大家在应用 Lock& Condition 进行线程同步协调的时候,肯定像我一样先把 condition 的逻辑语义设计好
- 将当 xxxx 时候的表白,设计为 condition。
- 当状况满足 condition 的时候发出信号 signal()告诉其余线程;
- 当状况与 condtion 正好相同的的时候,应用 await 阻塞以后线程。
四、put 放入数据
其实最重要的就是实现 Lock& Condition 逻辑设计,剩下的就是填空了,模板如下
通过 while 循环判断队列以后容量是否达到容量下限,如果达到下限就示意队列满了。队列满了(notFull 取反应用 await),await 阻塞生产线程向队列中持续放入数据。在这里,有小伙伴已经问过我一个奇葩的问题:多线程持有同一个 lock 锁,你怎么晓得阻塞的是生产线程,而不是生产线程呢? 答:一个线程是生产线程还是生产线程,取决于它的动作(调用什么办法),并没有一个标签给它定义死,调用 put 办法放入数据的就是生产数据的线程。while/await 组合是规范写法,请不要随便翻新改成 if,否则你会遇到很多诡异的 bug。
// 队列满了,await 阻塞生产线程
while (queue.size() >= queueSize) {System.out.println(Thread.currentThread().getName() + "期待, 因为队列满了");
notFull.await();}
向队列中增加一条数据,此时咱们能够确定队列是 notEmpty,所以应用 notEmpty.signal()向生产者发送信号。这里问题又来了:多线程持有同一个 lock 锁,你怎么晓得告诉的是消费者线程,而不是生产者线程呢? 答案是我的确不晓得 ,所以在上文中的while (queue.size() >= queueSize)
采纳的是 while,而不是 if。即便生产者线程被唤醒了,while 判断也会把它 await 拦住。
// 向队列增加一条音讯,同时告诉消费者有新音讯了
queue.add(message);
System.out.println(Thread.currentThread().getName() + "生产" + message);
notEmpty.signal();// 告诉消费者线程
五、take 生产数据
take 从队列中取出数据,取出数据之后,队列必定是 notFull,所以收回 notFull.signal 信号。当队列空了(notEmpty 应用 await 取反),await 同时阻塞消费者线程。
public Object take() throws InterruptedException {
Object retVal = null;
lock.lock();// 操作队列先加锁
try {
// 队列空了,告诉生产线程,生产线程阻塞
while (queue.size() == 0) {System.out.println("队列曾经空了,进行生产!");
notEmpty.await();}
// 队列删除一条音讯,同时告诉生产者队列有地位了
retVal = queue.get(0);
queue.remove(0);
notFull.signal(); // 同时告诉生产者队列} finally {lock.unlock();
}
return retVal;
}
我置信有了下面 put 办法的根底,了解 take 办法中的代码,就非常容易了,这里我就不做过多的阐明了。
六、生产生产测试
public static void main(String[] args) {
// 为了不便查看测试后果,咱们的队列容量设置小一些
MyBlockingQueue queue = new MyBlockingQueue(2);
// 生产者线程
new Thread(()->{for(int i = 0;i < 5;i++){
try {queue.put("msg" + i); // 放入 5 条数据
} catch (InterruptedException e) {e.printStackTrace();
}
}
}).start();
// 消费者线程
new Thread(()->{while(true){ // 始终生产
try {System.out.println(Thread.currentThread().getName()
+ "生产数据" + queue.take());
} catch (InterruptedException e) {e.printStackTrace();
}
}
}).start();}
输入后果如下,满足咱们的需要。队列满了,生产者线程 Thread- 0 期待;生产生产相互协调告诉,最终数据生产实现,队列空了,消费者线程阻塞。
Thread- 0 生产 msg0
Thread- 0 生产 msg1
Thread- 0 期待, 因为队列满了
Thread- 1 生产数据 msg0
Thread- 0 生产 msg2
Thread- 0 期待, 因为队列满了
Thread- 1 生产数据 msg1
Thread- 0 生产 msg3
Thread- 0 期待, 因为队列满了
Thread- 1 生产数据 msg2
Thread- 0 生产 msg4
Thread- 1 生产数据 msg3
Thread- 1 生产数据 msg4
队列曾经空了,进行生产!
欢送关注我的博客,更多精品常识合集
本文转载注明出处(必须带连贯,不能只转文字):字母哥博客 – zimug.com
感觉对您有帮忙的话,帮我点赞、分享!您的反对是我不竭的创作能源!。另外,笔者最近一段时间输入了如下的精品内容,期待您的关注。
- 《kafka 修炼之道》
- 《手摸手教你学 Spring Boot2.0》
- 《Spring Security-JWT-OAuth2 一本通》
- 《实战前后端拆散 RBAC 权限管理系统》
- 《实战 SpringCloud 微服务从青铜到王者》