引言
线程之间可以通过wait()
、notify()
和notifyAll()
等方法,结合“生产者、消费者和队列”等设计模式来相互协作。线程之间协作的基础是任务之间的握手,其基础特性是互斥。
wait()、notify()、notifyAll()
方法介绍:
- 以上三个方法都要获取对象监视器;
wait()
:挂起线程,并放弃当前对象监视器;notify()
:唤醒一个等待‘notify()所在代码持有的对象监视器’的线程;notifyAll()
:唤醒所有等待线程,并让他们竞争对象监视器;
其他方法
shutdown()
:shutdownNow()
:
一.wait()
和notifyAll()
1.1基本特性
当任务遇到运行需要但是自身无法改变的条件时,可以调用wait()
来等待这个条件发生变化,而且只有在notify()、notifyAll()
发生时才会检查条件是否发生了变化。
注意,只有在同步代码块中才能使用wait()
方法(notify()
方法同理),否则编译可以通过但是运行时会抛出IllegalMonitorStateException
。
与sleep()、yield()
不同的时:
- 在
wait()
期间锁是释放的,因此其他方法或代码块可以获得锁并执行; - 可以通过
notify()、notifyAll()
或者指定时间的wait(XX,YY)
方法来从wait()
中恢复执行;
1.2源码示例
两个线程(打蜡-抛光)来给一辆车反复的打蜡-抛光。一共有4各类,汽车类、打蜡类、抛光类和驱动类:
class Car{ private boolean waxOn=false;//刚开始没有打蜡 public synchronized void waxed(){ waxOn=true;//打上蜡 notifyAll();//唤醒所有等待当前对象(this)监视器的线程; } public synchronized void buffed(){ waxOn=false;//抛光,此时准备另一个车打蜡抛光; notifyAll(); } public synchronized void waitForWaxing() throws InterruptedException { while(waxOn==false)//没打蜡,等待打蜡 wait(); } public synchronized void waitForBuffing() throws InterruptedException { while(waxOn==true)//打完蜡,等待抛光. wait(); } } //打蜡类 class WaxOn implements Runnable{ private Car car; public WaxOn(Car car) { this.car = car; } @Override public void run() { try{ while(!Thread.interrupted()){//测试当前线程是否已经中断 System.out.println("Wax On!"); TimeUnit.MILLISECONDS.sleep(200); car.waxed();//打蜡 car.waitForBuffing();//等待抛光 } }catch (InterruptedException e){ System.out.println("exetint via interrupt"); } System.out.println("退出打蜡任务"); } } class WaxOff implements Runnable{ private Car car; public WaxOff(Car car) { this.car = car; } @Override public void run() { try{ while(!Thread.interrupted()){ car.waitForWaxing();//等待打蜡,如果当前car对象被另一个任务成功打蜡,则继续运行; System.out.println("wax off"); TimeUnit.MILLISECONDS.sleep(200); car.buffed();//抛光 } }catch (InterruptedException e){ System.out.println("通过interrupt 退出"); } System.out.println("抛光任务结束"); } } public class WaxOmatic { public static void main(String[] args) throws InterruptedException { Car car=new Car(); ExecutorService exec= Executors.newCachedThreadPool(); exec.execute(new WaxOff(car));//抛光 exec.execute(new WaxOn(car));//打蜡 TimeUnit.SECONDS.sleep(5); // exec.shutdown(); 无法退出; exec.shutdownNow(); } }
汽车类只有一个标识是否已经打蜡的变量,程序基本流程如下:
- 抛光线程获取car对象监视器,调用
wait()
方法放弃对象监视器; - 打蜡线程获取car对象监视器,设置打蜡方法将变量设置为“已经打蜡”,然后唤醒等待car对象监视器的线程(notify方法),并在
waitForBuffing()
方法中放弃car对象监视器; - 抛光线程被notify方法唤醒(因为他在等待car对象监视器),并开始抛光————然后将car打蜡状态置为FALSE,继续下一轮循环,car为FALSE状态则被打蜡线程中调用的“等待打蜡”方法感知,打蜡线程开始下一轮循环;
while(condition){ wait(); }
+synchronized
的正确使用方式
thread1: synchronized(sharedMonitor){ //setup condition for thread2 sharedMonitor.notify();//等待sharedMonitor对象监视器的线程被唤醒; } thread2: while(condition){ //重要X:线程调度器可能再次切换线程; synchronized(sharedMonitor){ sharedMonitor.wait(); } }
以上,如果thread2在“重要X”处被切换到thread1,然后thread1将thread2的while判定条件设置为false,并唤醒thread2,此时线程2将毫无意义的进入wait,并可能无限制的挂起来等待信号,从而产生死锁。正确的写法应该防在condition上产生竞争条件:
thread1: synchronized(sharedMonitor){ //setup condition for thread2 sharedMonitor.notify();//等待sharedMonitor对象监视器的线程被唤醒; } thread2: synchronized(sharedMonitor){ while(condition){ sharedMonitor.wait(); } }
二.生产者和消费者
生产者是厨师、消费者是服务员,对这个实例建模,有肉、厨师、服务员和驱动类(餐厅)四个类,代码如下:
package concurrency; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; /** * @author 杜艮魁 * @date 2018/1/31 */ //Meal是Restaurant中未初始化的一个实例,并被其另外两个对象WaitPerson和Chef不断置null和实例化; class Meal{ private final int orderNum; public Meal(int orderNum) { this.orderNum = orderNum; } @Override public String toString() { return "Meal "+orderNum; } } //应当注意WaitPerson是Restaurant中的一个对象,对象创建方式 // WaitPerson waitPerson=new WaitPerson(this) 调用其构造参数如下,以此来使WaitPerson //作为Restaurant的变量的同时能够操作Restaurant中的变量:即Meal引用状态和唤醒Chef线程 class WaitPerson implements Runnable{ private Restaurant restaurant; public WaitPerson(Restaurant restaurant) { this.restaurant = restaurant; } @Override public void run() { try{ while(!Thread.interrupted()){ synchronized (this){ //对条件的判定放在锁里边,防止产生竞态条件 while(restaurant.meal==null)// //加锁对象是this-WaitPerson对象:当厨房没有肉时,服务员等待; wait(); } System.out.println("waiter got "+restaurant.meal); //Meal不为null时Chef类中用this加锁的代码块被挂起。而以下代码将Meal置为null,因此需要唤醒Chef对象,修改meal引用; synchronized (restaurant.chef){ //服务员已经拿到肉,此时将肉实例设置为空,并唤醒等待上行锁的线程 restaurant.meal=null; restaurant.chef.notifyAll();//fixme notifyAll()相当于this.notifyAll(),唤醒的是等待当前对象监视器的所有线程而非等待Restaurant对象中Chef对象监视器的线程; } } }catch (InterruptedException e){ System.out.println("waiter interrupted"); } } } //含义同WaitPerson class Chef implements Runnable{ private int count=0; Restaurant restaurant; public Chef(Restaurant restaurant) { this.restaurant = restaurant; } @Override public void run() { try{ while(!Thread.interrupted()){ synchronized (this){ while(restaurant.meal!=null) wait(); } if(++count==10){ System.out.println("out of food,closing"); restaurant.exec.shutdownNow();//fixme 注意关闭的方法仍然是调用Restaurant中的对象 } System.out.println("order up!");//要上菜啦!! synchronized (restaurant.waitPerson){ restaurant.meal=new Meal(count); //对应tag1处waitPerson发现"Meal"实例不为空时,在synchronized(this-waitPerson)代码块中调用wait——这句代码唤醒wait()的等待 restaurant.waitPerson.notifyAll(); } TimeUnit.MILLISECONDS.sleep(100); } }catch (InterruptedException e){ System.out.println("chef interrupted"); } } } public class Restaurant { Meal meal; WaitPerson waitPerson=new WaitPerson(this); Chef chef=new Chef(this); ExecutorService exec= Executors.newCachedThreadPool(); public Restaurant() {//创建对象时开始执行这两个线程 exec.execute(chef); exec.execute(waitPerson); } public static void main(String[] args) { new Restaurant(); } }
注意代码中的两个==TODO==:
obj.notifyAll()
和直接使用notifyAll()
的区别是后者唤醒的是等待当前对象this监视器的线程;
三.生产者和消费者和队列
1.1吐司的制作与消费
设想模型:吐司制造、放黄油、加果酱以及被消费。一共有七个类:吐司类、吐司阻塞队列类,吐司生产类、放黄油类、放果酱类和消费类,驱动类。其中:
- 吐司类含有id和当前状态——即是否摸了黄油和果酱等;吐司队列类继承自
LinkedBlockingQueue
,也是一个重要的java类库; - 吐司生产类、放黄油类、放果酱类和消费类都是“任务类”负责通过队列操控生产;
- 示例代码没有显示的同步,因为由
LinkedBlockingQueue
和系统设计隐式的处理了,根据LinkedBlockingQueue
源码可以发现,使用队列消除了显式的使用wait()
造成的类之间的耦合因为每个类都和自己相关的不lockingQueue通信; - 流程图如下:
实例代码:
class ToastQueue extends LinkedBlockingQueue<Toast>{} /** * @author 杜艮魁 * @date 2018/1/31 */ //id、状态,还有将他的状态设置为“添加黄油”、“添加果酱” class Toast{ public enum Status{DRY,BUTTERED,JAMMED} private Status status=Status.DRY;//默认状态为DRY private final int id; public Toast(int id) { this.id = id; } public void butter(){status=Status.BUTTERED;} public void jam(){status=Status.JAMMED; } public Status getStatus() { return status; } public int getId() { return id; } @Override public String toString() { return "Toast "+id+": "+status; } } //吐司制造者:用其负责的吐司队列做构造参数 class Toaster implements Runnable{ private ToastQueue toastQueue; private int count=0; private Random rand=new Random(47); public Toaster(ToastQueue toastQueue) { this.toastQueue = toastQueue; } @Override public void run() { try{ while(!Thread.interrupted()){ TimeUnit.MILLISECONDS.sleep(100+rand.nextInt(500)); Toast t=new Toast(count++); System.out.println(t); toastQueue.put(t);//阻塞插入 } }catch (InterruptedException e){ System.out.println("Toaster interrupter"); } System.out.println("Toast off"); } } //给吐司抹黄油的类 class Butterer implements Runnable{ private ToastQueue dryQueue,butteredQueue; public Butterer(ToastQueue dryQueue, ToastQueue butteredQueue) { this.dryQueue = dryQueue; this.butteredQueue = butteredQueue; } @Override public void run() { try{ while(!Thread.interrupted()){ //fixme 取出队头,如果队列为空则调用Lock的‘类wait()"方法 Toast t=dryQueue.take();//阻塞获取 t.butter(); System.out.println(t); butteredQueue.put(t); } }catch(InterruptedException e){ System.out.println("Butter interrupted"); } } } class Jammer implements Runnable{ private ToastQueue butteredQueue,finishedQueue; public Jammer(ToastQueue butteredQueue, ToastQueue finishedQueue) { this.butteredQueue = butteredQueue; this.finishedQueue = finishedQueue; } @Override public void run() { try{ while(!Thread.interrupted()){ Toast t=butteredQueue.take(); t.jam(); System.out.println(t); finishedQueue.put(t); } }catch (InterruptedException e){ System.out.println("Jam interrupted"); } System.out.println("jam off"); } } class Eater implements Runnable{ private ToastQueue finishedQueue;//即jammed后的吐司 private int counter=0; public Eater(ToastQueue finishedQueue) { this.finishedQueue = finishedQueue; } @Override public void run() { try{ while(!Thread.interrupted()){ Toast t=finishedQueue.take(); if(t.getId()!=counter++||t.getStatus()!=Toast.Status.JAMMED){//如果出队吐司序号不对或者状态不对,则关闭系统; System.out.println("error: "+t); System.exit(1); }else{ System.out.println("chomp! "+t); } } }catch (InterruptedException e){ System.out.println("eater interrupted"); } System.out.println("Eater off"); } } public class ToastOMatic { public static void main(String[] args) throws InterruptedException { //fixme 无参构造器是默认存在的,导出类调用父类无参构造器—继而调用含参构造器,从而可以发现队列容量为Integer.MAX_VALUE ToastQueue dryQueue=new ToastQueue(), butteredQueue=new ToastQueue(), finishedQueue=new ToastQueue(); ExecutorService exec= Executors.newCachedThreadPool(); exec.execute(new Toaster(dryQueue));//启动一个“制作吐司”线程来初始化吐司的id和状态并放进dryQueue队列 exec.execute(new Butterer(dryQueue,butteredQueue));//以上,同理 exec.execute(new Jammer(butteredQueue,finishedQueue)); exec.execute(new Eater(finishedQueue)); TimeUnit.SECONDS.sleep(8); exec.shutdownNow(); } }
四.管道通信:任务间输入输出
管道中两个重要的类是PipedWriter和PipedReader,分别负责向管道中读写数据:
- PipedWriter:向管道中写数据;
- PipedReader:向管道中读数据;
pipedReader=new PipedReader(pipedWriter)
,每个piperReader读取的管道都要对应一个wirter,可以一对多;
/** * 线程间通过管道通信: * 1.PipedWriter:向管道中写数据; * 2.PipedReader:向管道中读数据; * .pipedReader=new PipedReader(pipedWriter),每个piperReader读取的管道都要对应一个wirter */ class Sender implements Runnable{ private PipedWriter out=new PipedWriter(); public PipedWriter getOut() { return out; } @Override public void run() { try{ while(true){ for(char c='A';c<='z';c++){ out.write(c);//fixme 向管道中写数据 TimeUnit.MILLISECONDS.sleep(500); } } }catch (IOException e){ System.out.println(e+" sender write exception"); }catch (InterruptedException e){ System.out.println(e+" sender sleep interrupted"); } } } class Recerver implements Runnable{ private PipedReader in; public Recerver(Sender sender) throws IOException {//注意参数是Sender类 in=new PipedReader(sender.getOut());//fixme 用PipedWriter创建对应读取管道数据的类; } @Override public void run() { try{ while(true){ System.out.println("Read: "+(char)in.read()+","); } }catch (IOException e){ System.out.println(e+" Receive read exception"); } } } public class PipedIO { public static void main(String[] args) throws IOException, InterruptedException { Sender sender=new Sender(); Recerver recerver=new Recerver(sender); ExecutorService executorService= Executors.newCachedThreadPool(); executorService.execute(sender); executorService.execute(recerver); TimeUnit.SECONDS.sleep(4); executorService.shutdownNow(); } }
五.其他
Thread.join()
:等待此线程死亡。如果在一个线程中用另一个线程对象调用了join
方法,那么当前线程会等待电泳join方法的线程结束才继续运行;
来源:https://www.cnblogs.com/dugk/p/8900843.html