java线程协作_java线程系列之三(线程协作)
上一篇讲述了线程的互斥(同步),但是在很多情况下,仅仅同步是不够的,还需要线程与线程协作(通信),生产者/消费者问题是一个经典的线程同步以及通信的案例。该问题描述了两个共享固定大小缓冲区的线程,即所谓的“生产者”和“消费者”在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者,通常采用线程间通信的方法解决该问题。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。本文讲述了JDK5之前传统线程的通信方式,更高级的通信方式可参见Java线程(九):Condition-线程通信更高效的方式和Java线程(篇外篇):阻塞队列BlockingQueue。
假设有这样一种情况,有一个盘子,盘子里只能放一个鸡蛋,A线程专门往盘子里放鸡蛋,如果盘子里有鸡蛋,则一直等到盘子里没鸡蛋,B线程专门从盘子里取鸡蛋,如果盘子里没鸡蛋,则一直等到盘子里有鸡蛋。这里盘子是一个互斥区,每次放鸡蛋是互斥的,每次取鸡蛋也是互斥的,A线程放鸡蛋,如果这时B线程要取鸡蛋,由于A没有释放锁,B线程处于等待状态,进入阻塞队列,放鸡蛋之后,要通知B线程取鸡蛋,B线程进入就绪队列,反过来,B线程取鸡蛋,如果A线程要放鸡蛋,由于B线程没有释放锁,A线程处于等待状态,进入阻塞队列,取鸡蛋之后,要通知A线程放鸡蛋,A线程进入就绪队列。我们希望当盘子里有鸡蛋时,A线程阻塞,B线程就绪,盘子里没鸡蛋时,A线程就绪,B线程阻塞,代码如下:
1 importjava.util.ArrayList;2 importjava.util.List;3 /**定义一个盘子类,可以放鸡蛋和取鸡蛋*/
4 public classPlate {5 /**装鸡蛋的盘子*/
6 List eggs = new ArrayList();7 /**取鸡蛋*/
8 public synchronizedObject getEgg() {9 while (eggs.size() == 0) {10 try{11 wait();12 } catch(InterruptedException e) {13 e.printStackTrace();14 }15 }16 Object egg = eggs.get(0);17 eggs.clear();//清空盘子
18 notify();//唤醒阻塞队列的某线程到就绪队列
19 System.out.println("拿到鸡蛋");20 returnegg;21 }22 /**放鸡蛋*/
23 public synchronized voidputEgg(Object egg) {24 while (eggs.size() > 0) {25 try{26 wait();27 } catch(InterruptedException e) {28 e.printStackTrace();29 }30 }31 eggs.add(egg);//往盘子里放鸡蛋
32 notify();//唤醒阻塞队列的某线程到就绪队列
33 System.out.println("放入鸡蛋");34 }35 static class AddThread implementsRunnable {36 privatePlate plate;37 private Object egg = newObject();38 publicAddThread(Plate plate) {39 this.plate =plate;40 }41 public voidrun() {42 plate.putEgg(egg);43 }44 }45 static class GetThread implementsRunnable {46 privatePlate plate;47 publicGetThread(Plate plate) {48 this.plate =plate;49 }50 public voidrun() {51 plate.getEgg();52 }53 }54 public static voidmain(String args[]) {55 Plate plate = newPlate();56 for(int i = 0; i < 10; i++) {57 new Thread(newAddThread(plate)).start();58 new Thread(newGetThread(plate)).start();59 }60 }61 }
输出结果:
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
放入鸡蛋
拿到鸡蛋
程序开始,A线程判断盘子是否为空,放入一个鸡蛋,并且唤醒在阻塞队列的一个线程,阻塞队列为空;假设CPU又调度了一个A线程,盘子非空,执行等待,这个A线程进入阻塞队列;然后一个B线程执行,盘子非空,取走鸡蛋,并唤醒阻塞队列的A线程,A线程进入就绪队列,此时就绪队列就一个A线程,马上执行,放入鸡蛋;如果再来A线程重复第一步,在来B线程重复第二步,整个过程就是生产者(A线程)生产鸡蛋,消费者(B线程)消费鸡蛋。
前段时间看了张孝祥老师线程的视频,讲述了一个其学员的面试题,也是线程通信的,在此也分享一下。
题目:子线程循环10次,主线程循环100次,如此循环100次,好像是空中网的笔试题。
1 classBoundedBuffer {2 final Lock lock = new ReentrantLock();//锁对象
3 final Condition notFull = lock.newCondition();//写线程条件
4 final Condition notEmpty = lock.newCondition();//读线程条件
5
6 final Object[] items = new Object[100];//缓存队列
7 int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/;8
9 public void put(Object x) throwsInterruptedException {10 lock.lock();11 try{12 while (count == items.length)//如果队列满了
13 notFull.await();//阻塞写线程
14 items[putptr] = x;//赋值
15 if (++putptr == items.length) putptr = 0;//如果写索引写到队列的最后一个位置了,那么置为0
16 ++count;//个数++
17 notEmpty.signal();//唤醒读线程
18 } finally{19 lock.unlock();20 }21 }22
23 public Object take() throwsInterruptedException {24 lock.lock();25 try{26 while (count == 0)//如果队列为空
27 notEmpty.await();//阻塞读线程
28 Object x = items[takeptr];//取值
29 if (++takeptr == items.length) takeptr = 0;//如果读索引读到队列的最后一个位置了,那么置为0
30 --count;//个数--
31 notFull.signal();//唤醒写线程
32 returnx;33 } finally{34 lock.unlock();35 }36 }37 }
大家注意到没有,在调用wait方法时,都是用while判断条件的,而不是if,在wait方法说明中,也推荐使用while,因为在某些特定的情况下,线程有可能被假唤醒,使用while会循环检测更稳妥。wait和notify方法必须工作于synchronized内部,且这两个方法只能由锁对象来调用。
java线程协作_java线程系列之三(线程协作)相关推荐
- java set复制_Java 集合系列之三:Set基本操作
1. Java Set 1. Java Set 重要观点 Java Set接口是Java Collections Framework的成员. Set不允许出现重复元素-----------无重复 Se ...
- java线程 教程_Java多线程系列教程
Java多线程系列教程 多线程是Java中不可避免的一个重要主体.从本章开始,我们将展开对多线程的学习.接下来的内容是对Java多线程内容的讲解,涉及到的内容包括,Object类中的wait(), n ...
- java queue 线程安全_java并发编程之线程安全方法
线程安全的实现方法,包含如下方式 一, 互斥同步 使用互斥锁的方式. 举个栗子 synchronized,最常用的同步实现方案, ReentrantLock,java并发包中工具,后续介绍. 互斥同步 ...
- java 手动线程调度_Java Thread 多线程 操作线程
5.线程的创建和启动 A.继承Thread类或实现Runnable接口,重写或实现run方法,run方法代表线程要完成的任务 B.创建Thread子类或是Runnable的实现类,即创建的线程对象:不 ...
- java创建线程代码_Java创建与结束线程代码示例
本文讲述了在Java中如何创建和结束线程的最基本方法,只针对于Java初学者.一些高级知识如线程同步.调度.线程池等内容将会在后续章节中逐步深入. 创建线程 创建普通线程有两种方式,继承Thread类 ...
- java线程池_Java多线程并发:线程基本方法+线程池原理+阻塞队列原理技术分享...
线程基本方法有哪些? 线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等. 线程等待(wait) 调用该方法的线程进入 WAITING 状态,只有等 ...
- java线程状态_java并发编程之线程状态
java线程中,线程状态是如何转换的呢?这一次我们一起来学习下. 线程状态: NEW: 线程创建之后,还没有启动.这时候它的状态就是NEW RUNNABLE: 正在Java虚拟机下跑任务的线程的状态. ...
- Java高并发和多线程系列 - 1. 线程基本概念
1. 什么是线程? 线程和进程的区别 在了解线程的概念前,我们应该先知道什么是进程? 进程是操作系统的基本概念之一, 它是正在执行的程序实例. * 下面的一些进程的基本概念你可以了解下 ------- ...
- java 多线程池_Java项目中,线程池中线程数量太大会有什么影响?
简单说一下吧!拿我们生活中非常常见的一例子来说:并不是人多就能把事情做好,增加了沟通交流成本.你本来一件事情只需要3个人做,你硬是拉来了6个人,会提升做事效率嘛?我想并不会. 线程数量过多的影响也是和 ...
- java 单线程执行器_Java基础-并发编程-线程执行器executor
线程实现方式 Thread.Runnable.Callable //实现Runnable接口的类将被Thread执行,表示一个基本任务 public interface Runnable { //ru ...
最新文章
- 【廖雪峰python入门笔记】if语句
- 基于相机和激光传感器的车顶视觉检测系统
- 计算机学院会会,学生分会——计算机学院学生会
- listview 重复动画效果
- postgresql----JSON类型和函数
- 什么!在CSS中的重要意义? [重复]
- Postman接口调试神器-Chrome浏览器插件
- 小程序 获取当前用户城市信息(省市区)
- matlab中有哪些输出函数,MATLAB中查找并输出的函数有什么
- 行云管家堡垒机的使用方法之二——新增登录凭证
- react-native 自定义 下拉刷新 / 上拉加载更多 组件
- 04.Unity ShaderGraph序列(Lightweight Pipeline相关扫盲)
- 金字塔原理——表达的逻辑
- 下三角99乘法表 C语言
- 证明厄米矩阵不同特征值对应特征向量正交
- 常用的推挽输出、开漏输出、上拉输入
- 转载 PVE 防火墙
- 【RFC6582 TCP快速恢复算法的NewReno修改】(翻译)
- ABAP:增强篇-MIGO过账增强之CHECK方法获取行项目
- P4735 贪心 + 可持久化 Trie