java线程系列之三(线程协作)
本文来自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/7433673,转载请注明。
上一篇讲述了线程的互斥(同步),但是在很多情况下,仅仅同步是不够的,还需要线程与线程协作(通信),生产者/消费者问题是一个经典的线程同步以及通信的案例。该问题描述了两个共享固定大小缓冲区的线程,即所谓的“生产者”和“消费者”在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者,通常采用线程间通信的方法解决该问题。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。本文讲述了JDK5之前传统线程的通信方式,更高级的通信方式可参见Java线程(九):Condition-线程通信更高效的方式和Java线程(篇外篇):阻塞队列BlockingQueue。
假设有这样一种情况,有一个盘子,盘子里只能放一个鸡蛋,A线程专门往盘子里放鸡蛋,如果盘子里有鸡蛋,则一直等到盘子里没鸡蛋,B线程专门从盘子里取鸡蛋,如果盘子里没鸡蛋,则一直等到盘子里有鸡蛋。这里盘子是一个互斥区,每次放鸡蛋是互斥的,每次取鸡蛋也是互斥的,A线程放鸡蛋,如果这时B线程要取鸡蛋,由于A没有释放锁,B线程处于等待状态,进入阻塞队列,放鸡蛋之后,要通知B线程取鸡蛋,B线程进入就绪队列,反过来,B线程取鸡蛋,如果A线程要放鸡蛋,由于B线程没有释放锁,A线程处于等待状态,进入阻塞队列,取鸡蛋之后,要通知A线程放鸡蛋,A线程进入就绪队列。我们希望当盘子里有鸡蛋时,A线程阻塞,B线程就绪,盘子里没鸡蛋时,A线程就绪,B线程阻塞,代码如下:
import java.util.ArrayList;
import java.util.List;
/** 定义一个盘子类,可以放鸡蛋和取鸡蛋 */
public class Plate {
/** 装鸡蛋的盘子 */
List<Object> eggs = new ArrayList<Object>();
/** 取鸡蛋 */
public synchronized Object getEgg() {
while (eggs.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object egg = eggs.get(0);
eggs.clear();// 清空盘子
notify();// 唤醒阻塞队列的某线程到就绪队列
System.out.println("拿到鸡蛋");
return egg;
}
/** 放鸡蛋 */
public synchronized void putEgg(Object egg) {
while (eggs.size() > 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
eggs.add(egg);// 往盘子里放鸡蛋
notify();// 唤醒阻塞队列的某线程到就绪队列
System.out.println("放入鸡蛋");
}
static class AddThread implements Runnable {
private Plate plate;
private Object egg = new Object();
public AddThread(Plate plate) {
this.plate = plate;
}
public void run() {
plate.putEgg(egg);
}
}
static class GetThread implements Runnable {
private Plate plate;
public GetThread(Plate plate) {
this.plate = plate;
}
public void run() {
plate.getEgg();
}
}
public static void main(String args[]) {
Plate plate = new Plate();
for(int i = 0; i < 10; i++) {
new Thread(new AddThread(plate)).start();
new Thread(new GetThread(plate)).start();
}
}
}
输出结果:
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
- 放入鸡蛋
- 拿到鸡蛋
程序开始,A线程判断盘子是否为空,放入一个鸡蛋,并且唤醒在阻塞队列的一个线程,阻塞队列为空;假设CPU又调度了一个A线程,盘子非空,执行等待,这个A线程进入阻塞队列;然后一个B线程执行,盘子非空,取走鸡蛋,并唤醒阻塞队列的A线程,A线程进入就绪队列,此时就绪队列就一个A线程,马上执行,放入鸡蛋;如果再来A线程重复第一步,在来B线程重复第二步,整个过程就是生产者(A线程)生产鸡蛋,消费者(B线程)消费鸡蛋。
前段时间看了张孝祥老师线程的视频,讲述了一个其学员的面试题,也是线程通信的,在此也分享一下。
题目:子线程循环10次,主线程循环100次,如此循环100次,好像是空中网的笔试题。
大家注意到没有,在调用wait方法时,都是用while判断条件的,而不是if,在wait方法说明中,也推荐使用while,因为在某些特定的情况下,线程有可能被假唤醒,使用while会循环检测更稳妥。wait和notify方法必须工作于synchronized内部,且这两个方法只能由锁对象来调用。
java线程系列之三(线程协作)的更多相关文章
- Java多线程系列--“JUC线程池”06之 Callable和Future
概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...
- Java多线程系列--“JUC线程池”02之 线程池原理(一)
概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...
- Java多线程系列--“JUC线程池”03之 线程池原理(二)
概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...
- Java多线程系列--“JUC线程池”04之 线程池原理(三)
转载请注明出处:http://www.cnblogs.com/skywang12345/p/3509960.html 本章介绍线程池的生命周期.在"Java多线程系列--“基础篇”01之 基 ...
- Java多线程系列--“JUC线程池”05之 线程池原理(四)
概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...
- 死磕 java线程系列之线程池深入解析——普通任务执行流程
(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本. 注:线程池源码部分如无特殊说明均指ThreadPoolExecutor类. 简介 前面我们一起学习了Java中 ...
- java并发系列(二)-----线程之间的协作(wait、notify、join、CountDownLatch、CyclicBarrier)
在java中,线程之间的切换是由操作系统说了算的,操作系统会给每个线程分配一个时间片,在时间片到期之后,线程让出cpu资源,由其他线程一起抢夺,那么如果开发想自己去在一定程度上(因为没办法100%控制 ...
- Java多线程系列--“JUC线程池”01之 线程池架构
概要 前面分别介绍了"Java多线程基础"."JUC原子类"和"JUC锁".本章介绍JUC的最后一部分的内容——线程池.内容包括:线程池架构 ...
- java多线程系列(六)---线程池原理及其使用
线程池 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线程系列(三)之等待通知 ...
随机推荐
- The Preliminary Contest for ICPC China Nanchang National Invitational and International Silk-Road Programming Contest
打网络赛 比赛前的准备工作要做好 确保 c++/java/python的编译器能用 打好模板,放在桌面 A. PERFECT NUMBER PROBLEM #include <cstdio> ...
- Vue(一)安装
环境准备 这里我们就直接使用官方推荐的Vue CLI方式 CLI (@vue/cli) 是一个全局安装的 npm 包,提供了终端里的 vue 命令.它可以通过 vue create 快速创建一个新项目 ...
- 【gdoi2018 day2】第二题 滑稽子图(subgraph)(性质DP+多项式)
题目大意 [gdoi2018 day2]第二题 滑稽子图(subgraph) 给你一颗树\(T\),以及一个常数\(K\),对于\(T\)的点集\(V\)的子集\(S\). 定义\(f(S)\)为点集 ...
- JS学习笔记Day13
一.cookie (一)什么是cookie: 1.就是会话跟踪技术,存放在客户端浏览器中的一段文本信息 2.会话:从浏览网站开始到结束的这个过程称为一次会话,浏览器关闭,表示会话结束 3.会话跟踪技术 ...
- 使用AForge.NET Framework打开摄像头并截图 C#
videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice); if (videoDevices.Count == ...
- js的7种类型
众所周知,js有7种数据类型 1. null 2. undefined 3. boolean 4. number 5. string 6. 引用类型(object.array.function) 7 ...
- JS 正则中的命名捕获分组
假设你在一段陌生的代码中看到这样一个函数: function toLocalDate(date) { return date.replace(/(\d{2})-(\d{2})-(\d{4})/, &q ...
- QMainWindow-状态栏
self.status_bar = self.statusBar() # 显示状态栏 self.setStatusTip('这是一个窗口') #鼠标在指定控件上时,状态栏 ...
- Nginx+Apache环境的安装与配置
我们依然尽可能采用yum来安装我们需要的软件,由系统官方维护的软件,其安全性和稳定性都值得信赖,并且容易管理,升级方便,但是在CentOS和RHEL的官方yum源中暂时没有Nginx等软件包,所以我们 ...
- 20175204 张湲祯 2018-2019-2《Java程序设计》第三周学习总结
20175204 张湲祯 2018-2019-2<Java程序设计>第三周学习总结 教材学习内容总结 -第四章类与对象要点: -面向对象语言三个特性:封装性:继承:多态: -类:1.类是组 ...