前面写过一篇关于线程通信的博客,但是只是简单的罗列了几种线程通信的形式及语法;这几天又把《Thinking in Java上》对应的内容看了一遍,这一篇博客主要结合几个例子说明下几种线程通信方式的特点。
首先还是要明确线程通信要解决的问题是什么:考虑这么一个情况,我们现在要对一台车进行涂蜡和抛光,并且在进行抛光之前一定要保证已涂蜡,且在涂另一层蜡的时候,又要保证已经抛光;现在我们我们开启两个线程,一个负责涂蜡,另一个负责抛光;要想顺利的完成任务,那么显然这两个线程需要相互等待和协调。这就是线程通信的任务了–如果我们有一个任务需要多个线程协作完成,那么一些线程可能需要等另一些线程准备好资源后才能运行,我们需要通过线程之间的通信来安排这些线程执行的先后。Java中提供了好几种协调线程执行先后的方法即线程通信方法,下面就各自来分析下它们的特点。

一.wait()和notifyAll()
当线程在等待某个条件的时候,我们当然可以让线程不断的进行测试,当条件达到之后就执行任务,但是这种方法显然CPU利用率太低了;wait()提供了一种更好的途径,当我们等待的条件还没有达到时,我们可以选择将线程挂起。如果当前条件满足了,那么就可以调用notifyAll()来将这个线程唤醒继续执行或等待;这就是利用wait()和notifyAll()来进行线程通信的基本逻辑,看起来似乎很简单。但是其实还是有一些地方是值得注意的。

1):我们说调用wait()时线程会被挂起,但是同时还值得注意的是当前线程持有对象的锁是会被释放的;也是说,其它的线程可以重新获得该对象的锁,即该对象其它的synchronized方法在wait()期间是可以被执行的,这一点是很重要的,因为只有执行其它的synchronized方法才可能使得当前线程等待的条件达到。而与wait()类似的sleep()/yield()方法是不会释放锁的。
2):wait()和notify()/notifyAll()这几个方法都需要放到同步控制方法或则是同步代码控制块中使用;这一点也是因为他们需要操作对象的锁。而sleep()/yield()就没有这个要求了,可以在任何地方进行调用。
下面就以上面的汽车涂蜡抛光的过程来演示下如何使用线程间的通信:

package lkl;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;class Car{private boolean waxOn=false; //是否已涂蜡public Car(){}public synchronized void waxed(){ //对车涂蜡waxOn=true; //标志已涂蜡//通知所有等待该对象锁的线程,waxOn标志有改变notify();//notifyAll()因为本例中只需要通知一个线程,所以notifyAll()和notifyAll()效果是一样的}public synchronized void buffed(){ //对车进行抛光waxOn=false;//标志已经抛光//通知所有等待该对象锁的线程,waxOn标志有改变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;}public void run(){while(!Thread.interrupted()){ //使用线程的中断标志来控制子线程的循环try{car.waitForBuffing();//等待抛光System.out.println("涂蜡!");car.waxed();//涂蜡}catch(InterruptedException ex){System.out.println("中断异常退出");}}}
}//抛光线程
class  WaxOff implements Runnable{private Car car;public WaxOff(Car car){this.car=car;}public void run(){while(!Thread.interrupted()){try{car.waitForWaxing();//等待涂蜡System.out.println("抛光!");car.buffed();//抛光}catch(InterruptedException ex){System.out.println("中断异常退出");}}}
}public class WaxoMatic {public static void main(String[] args) throws Exception{Car car = new Car();ExecutorService exec = Executors.newCachedThreadPool();exec.execute(new WaxOff(car));exec.execute(new WaxOn(car));exec.shutdown();TimeUnit.MILLISECONDS.sleep(1);exec.shutdownNow();System.exit(1);}
}
/**Output涂蜡!
抛光!
涂蜡!
抛光!
涂蜡!
抛光!
涂蜡!
抛光!
涂蜡!
抛光!
涂蜡!
抛光!
涂蜡!
抛光!
涂蜡!
抛光!* */

上面的代码中有一点需要注意:我们再检查标志是否改变的时候使用了while()循环,这是为了防止任务被唤醒而标志没有改变情况下线程错误的运行。出现这种情况的原因我们会在下面谈到。

3):notify()与notifyAll()的区别
当某种条件达到时,Java提供了两种方法来唤醒等待的线程:notify()和notifyAll()。可以简单的认为它们之间的不同是notify()只会随机唤醒一个线程,而notifyAll()会唤醒所有的线程。但是实际上没有那么简单,首先notify()会唤醒一个线程,那么到底什么样的线程才可能被它唤醒,是所有的线程都有机会被它唤醒么?当然不是这样,我们要明确调用notify()或notifyAll()的线程肯定是持有某个对象的锁,而我们唤醒的线程也就是在等待这些锁挂起的线程(即前面在持有这个对象锁而调用wait()将自己挂起的线程);所以notify()只会随机唤醒一个在等待当前对象锁挂起的线程,而notifyAll()会唤醒所有在等待当前对象锁挂起的线程而不是所有挂起的线程,当然这种唤醒是依次进行的而不是同时唤醒的。正是因为notifyAll()会唤醒所有等待当前对象锁的挂起线程,可能某些线程虽然被唤醒但是条件还没达到需要继续挂起,所以上面我们采取了while循环检查标志的做法。
下面这个程序比较好的说明了上面的规则:

package lkl;import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;/**演示notify()和notifyAll()的不同* notify()只会唤醒一个等待的线程,而notifyAll()会唤醒所有等待这个对象锁的线程.* 这里有两点需要注意,1是notifyAll()唤醒"所有的线程",并不是指当前所有阻塞的线程* 而是指所有挂起在调用notifyAll()方法所用对象锁上的线程* 2是以前自己以为notifyAll()虽然会唤醒阻塞在一个锁上的所有线程,但是应该只会* 选择其中一个获得锁的线程执行,但是下面的程序输出结果表明所有的线程都会执行一次* 说明以前的想法是错的,正确的解释在下面* */class Blocker{public synchronized void waitingCall(){try{while(!Thread.interrupted()){wait();System.out.print(Thread.currentThread()+" ");}}catch(InterruptedException ex){System.out.println("通过中断异常退出");}}public synchronized void prod(){notify();}public synchronized void prodAll(){notifyAll();}
}class Task1 implements Runnable{public static Blocker blocker = new Blocker();public void run(){blocker.waitingCall();//等待notify}
}class Task2 implements Runnable{public static Blocker blocker = new Blocker();public void run(){blocker.waitingCall();}
}//从输出可以看出两点:
//1.notify()会随机选择一个任务进行唤醒
//2.notifyAll()会唤醒阻塞在同一个锁上的所有任务
//对notifyAll()和notify()的一点想法
//假设第一个线程先响应notifyAll()获得空闲的锁然后执行System.out.println()然后再执行wait()
//将锁释放,接着又有一个线程响应notifyAll()获得锁,再执行如上步骤;最后的结果就是
//所有的线程都可以执行一次System.out.println()语句,即每个线程都被唤醒了一次。
//也就是说notifyAll()可以保证每个阻塞在这个锁上的线程都能获得一次锁(但是一次还是只能有一个线程执行的),
//但是获得锁以后线程能不能执行任务却是不一定的
//(有可能标记还没改变,满足不了运行条件)。但是如果某个获得线程获得锁以后
//不释放锁了,那么其它线程也就不能获得锁了。而notify()只会选择一个线程让其获得锁
//然后就不管其它线程了
public class NotifyVsNotifyAll {public static void main(String[] args) throws Exception{ExecutorService exec = Executors.newCachedThreadPool();for(int i=0;i<5;i++){exec.execute(new Task1());}exec.execute(new Task2());exec.shutdown();Timer timer = new Timer();//启动一个定时任务timer.scheduleAtFixedRate(new TimerTask(){boolean prod=true;public void run(){if(prod){System.out.println("\nnotify");Task1.blocker.prod();prod=false;}else{System.out.println("\nnotifyAll()");Task1.blocker.prodAll();prod=true;}}}, 400,400);TimeUnit.SECONDS.sleep(5);timer.cancel();System.out.println("Timer canceled");System.out.println("Task2.blocker.prodAll() ");Task2.blocker.prodAll();TimeUnit.MILLISECONDS.sleep(500);System.out.println("shutting down");exec.shutdownNow();}
}
/**Output
notify
Thread[pool-1-thread-1,5,main]
notifyAll()
Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-5,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-2,5,main]
notify
Thread[pool-1-thread-1,5,main]
notifyAll()
Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1-thread-5,5,main]
notify
Thread[pool-1-thread-1,5,main]
notifyAll()
Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-5,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-2,5,main]
notify
Thread[pool-1-thread-1,5,main]
notifyAll()
Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1-thread-5,5,main]
notify
Thread[pool-1-thread-1,5,main]
notifyAll()
Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-5,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-2,5,main]
notify
Thread[pool-1-thread-1,5,main]
notifyAll()
Thread[pool-1-thread-1,5,main] Thread[pool-1-thread-2,5,main] Thread[pool-1-thread-3,5,main] Thread[pool-1-thread-4,5,main] Thread[pool-1-thread-5,5,main] Timer canceled
Task2.blocker.prodAll()
Thread[pool-1-thread-6,5,main] shutting down
通过中断异常退出
通过中断异常退出
通过中断异常退出
通过中断异常退出
通过中断异常退出
通过中断异常退出* */

下面实现一个简单的生产者消费者问题:一个饭店有一个厨师一个服务员和一个BusBoy。服务员必须等待厨师准备好膳食,当厨师准备好了以后就通知服务员上菜,服务员上菜以后就通知BusBoy进行清理,然后继续等待。很显然这里涉及到三个线程的协作,具体的实现如下:

package lkl;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;class Meal{//表示顾客的订餐,每个订餐都有唯一的编号private final int  orderNum;public Meal(int orderNum){this.orderNum=orderNum;}public String toString(){return "Meal"+orderNum;}
}//侍者类
class WaitPerson implements Runnable{private Restaurant r;//侍者所属的餐馆public WaitPerson(Restaurant r){this.r=r;}public void run(){try{while(!Thread.interrupted()){/*** 首先要检查meal是否为空,如果为空的话需要调用当前对象的* wait()方法将当前线程挂起,所以需要先用synchronized代码块获得* 当前对象的锁。同时要使用while循环检测,以防止不正确的唤醒* */synchronized(this){ //如果meal还没准备好或则还未清理,则调用wait()将当前线程挂起while(r.meal==null|| r.needClean){  wait();}}/*** 如果meal已经准备好了,则可以将其送给客人(设为null)* 同时要提醒chef,当前meal已经送走,可以准备下一份了。* 这里需要调用chef的notifyAll()方法以通知挂起在其锁上面的线程* 所以在这里需要调用synchronized代码块,以先获得chef上的锁* */System.out.println("waitPerson get " + r.meal);synchronized(r.chef){r.meal=null;r.chef.notifyAll();}synchronized(r.busBoy){r.needClean=true;//表示需要清理r.busBoy.notifyAll(); //唤醒清理线程}}}catch(InterruptedException ex){System.out.println("通过中断异常退出");}}
}//厨师类
class Chef implements Runnable{private Restaurant r;public Chef(Restaurant r){this.r=r;}private int count=0;public void run(){try{while(!Thread.interrupted()){synchronized(this){while(r.meal!=null){wait();}}System.out.println("Order up! ");if(count==10){System.out.println("Out of food! closing");/**这里需要注意下,调用shutdownNow以后,会向ExecutorService* 中的每一个线程都发送一个interrupt(),但是这并不会导致线程马上结束* 线程会继续执行,如果遇到可中断阻塞会以抛出异常的形式退出,否则* 会到达循环检查的地方,然后通过检查循环条件退出。从下面的输出可以看得到*/r.exec.shutdownNow();}synchronized(r.waitPerson){r.meal = new Meal(count++);r.waitPerson.notifyAll();}TimeUnit.MILLISECONDS.sleep(100);}}catch(InterruptedException ex){System.out.println("通过中断异常退出");}}
}//每次上菜后负责清理的类
class BusBoy implements Runnable{private Restaurant r;public BusBoy(Restaurant r){this.r=r;}public void run(){try{while(!Thread.interrupted()){synchronized(this){while(r.needClean==false){// System.out.println("test");wait();}}System.out.println("Clean Up");r.needClean=false; //表示清理结束}}catch(InterruptedException ex){System.out.println("中断退出");}}
}public class Restaurant {public boolean needClean=false;public Meal meal=null;public BusBoy busBoy = new BusBoy(this);public WaitPerson waitPerson = new WaitPerson(this);public  Chef chef = new Chef(this);ExecutorService exec = Executors.newCachedThreadPool();public Restaurant(){exec.execute(waitPerson);exec.execute(chef);exec.execute(busBoy);}public static void main(String[] args){new Restaurant();}
}
/*Output
Order up!
waitPerson get Meal0
Clean Up
Order up!
waitPerson get Meal1
Clean Up
Order up!
waitPerson get Meal2
Clean Up
Order up!
waitPerson get Meal3
Clean Up
Order up!
waitPerson get Meal4
Clean Up
Order up!
waitPerson get Meal5
Clean Up
Order up!
waitPerson get Meal6
Clean Up
Order up!
waitPerson get Meal7
Clean Up
Order up!
waitPerson get Meal8
Clean Up
Order up!
waitPerson get Meal9
Clean Up
Order up!
Out of food! closing
中断退出
waitPerson get Meal10
通过中断异常退出* */

从上面的代码可以看到如果我们想把当前线程挂起,随便取的一个对象的锁然后调用wait()就好了,没有太多的讲究。但是当我们改变条件以后,就必须想清楚我们是想通过这个条件唤醒那个锁的挂起线程,然后再利用同步代码块取的它的锁,然后再调用notifyAll()/notify()。

二.使用显示的Lock和Condition对象
我们可能使用Lock来进行同步控制,这时候我们不能获得对象的锁了,也就不能使用前面wait() /notifyAll()来进行线程间的通信了。但是lock对象会对应一个Condition对象,我们可以使用它来进行线程间的通信。具体的来讲:可以通过调用Condition对象上调用await()来挂起一个任务;当外部条件发生变化时,可以调用signal()来唤醒一个任务运行,或者调用signalAll()来唤醒所有在这个Condition上被其自身挂起的任务。总而言之,线程通信的过程和思想基本上和wait()/notifyAll()差不多,下面用这种方式重写了上面的代码如下:

package lkl;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class Meal2 {private final int OrderNum;public Meal2(int OrderNum){this.OrderNum=OrderNum;}public String toString(){return "Meal "+OrderNum;}
}class WaitPerson2 implements Runnable{private Restaurant2 r;public Lock lock = new ReentrantLock();public Condition condtion =lock.newCondition();public WaitPerson2(Restaurant2 r){this.r=r;}public  void run(){try{while(!Thread.interrupted()){lock.lock(); //先获得本身的锁,然后在测试r.meal,看是否需要调用本身的condition将当前线程挂起try{while(r.meal==null){condtion.await();}}finally{lock.unlock();}System.out.println("waitPerson get "+r.meal);r.chef.lock.lock(); //先获得r.chef的锁try{r.meal=null;//调用r.chef.condition.signalAll()唤醒//挂在r.chef.conditon上的线程r.chef.condition.signalAll();}finally{r.chef.lock.unlock();}}}catch(InterruptedException ex){System.out.println("通过中断异常退出");}}
}class Chef2 implements Runnable{public int count=0;public Lock lock = new ReentrantLock();public Condition condition = lock.newCondition(); private Restaurant2 r;public Chef2(Restaurant2 r){this.r=r;}public void run(){try{while(!Thread.interrupted()){lock.lock();try{while(r.meal != null){condition.await();}}finally{lock.unlock();}System.out.println("OrderUp");if(count==10){System.out.println("out of food Closing");r.exec.shutdownNow(); //发送Interrupted信息}r.waitPerson.lock.lock();try{r.meal = new Meal2(++count);r.waitPerson.condtion.signalAll();}finally{r.waitPerson.lock.unlock();}}}catch(InterruptedException ex){System.out.println("通过中断异常退出");}}
}public class Restaurant2 {public Meal2 meal = null;public WaitPerson2 waitPerson = new WaitPerson2(this);public  Chef2  chef = new Chef2(this);public ExecutorService exec = Executors.newCachedThreadPool();public Restaurant2(){exec.execute(chef);exec.execute(waitPerson);exec.shutdown();}public static void main(String[] args){new Restaurant2();}
}

从上面重写的代码来看,除了比原来更复杂以外并没有什么额外的好处。但是据说这种方式在复杂的任务中可以提供很好的灵活性。

三.使用同步队列
使用上面两种方式进行线程通信,都显得非常的繁琐:我们必须仔细的组织每个线程每种情况下的握手。而同步队列提供了更高层次上的抽象;对于同步队列,我们首先可以把它看成一个普通的队列,但是如果一个线程向其中写入时队列满了,则这个线程会被挂起直到有元素被消耗掉以后允许再写入时线程恢复;同理在某个线程在读取时若队列为空也会发生这样的事情。常用的同步队列有LinkedBlockingQueue,这是一个无界的队列;ArrayBlockingQueue,这是具有固定尺寸的同步队列。同步队列的使用还是比较简单的,下面我们利用其来解决一个问题:有一台机器上具有三个任务:一个制作土司,一个给土司涂黄油,一个给涂好黄油上涂果酱,我们可以通过各个处理过程中的BlockingQueue来解决这个问题,代码如下:

package lkl;import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/*** 可以看到使用阻塞队列极大的简化了程序,每个线程只需要和自己的阻塞队列打交道* 而不需要关心同步问题及和其它线程通信的问题* 1.多个线程共享一个阻塞队列,不需要考虑同步问题* 2.多个线程使用阻塞队列可以自动进行通信,不需要自己来实现* 3.适合于生产者消费者类型的问题* *///封装的土司类
class Toast{public enum Status { DRY,BUTTERED,JAMMED}private Status status = Status.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;}public String toString(){return "Toast "+id+"Status: "+status;}
}//创建一个Toast阻塞队列
class ToastQueue extends LinkedBlockingQueue<Toast>{}//创建土司的线程
//负责向toastQueue中添加Toast对象
class Toaster implements Runnable{private ToastQueue toastQueue; //持有一个存放生成土司的阻塞队列private int count=0;private Random rand = new Random(47);public Toaster(ToastQueue toastQueue){this.toastQueue=toastQueue;}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 ex){System.out.println("Toaster通过中断异常退出");}System.out.println("Toaster off");}
}//给土司涂黄油的线程
class Butterer implements Runnable{//持有装有已生成土司的队列,及涂好黄油的土司的队列private ToastQueue dryQueue,butterQueue;private Random rand = new Random(100);public Butterer(ToastQueue dryQueue,ToastQueue butterQueue){this.dryQueue = dryQueue;this.butterQueue = butterQueue;}public void run(){try{while(!Thread.interrupted()){TimeUnit.MILLISECONDS.sleep(100+rand.nextInt(100));//模拟涂黄油所需时间Toast t = dryQueue.take();//如果队列为空,则阻塞t.butter(); //涂黄油System.out.println(t);butterQueue.put(t);//装入队列中,如果队列满则会阻塞}}catch(InterruptedException ex){System.out.println("Butterer通过中断退出");}System.out.println("Butter off");}
}//给土司涂番茄酱的线程
class Jammer implements Runnable{//持有涂好黄油的土司队列,及完成队列private ToastQueue butterQueue,finishedQueue;public Jammer(ToastQueue butterQueue,ToastQueue finishedQueue){this.butterQueue=butterQueue;this.finishedQueue=finishedQueue;}public void run(){try{while(!Thread.interrupted()){TimeUnit.MILLISECONDS.sleep(101);//模拟涂番茄酱所需时间Toast t = butterQueue.take(); t.jam();System.out.println(t);finishedQueue.put(t);}}catch(InterruptedException ex){System.out.println("Jammer通过中断异常退出");}System.out.println("Jammer off");}
}//消费土司的线程
class Eater implements Runnable{private ToastQueue finishedQueue;public Eater(ToastQueue finishedQueue){this.finishedQueue=finishedQueue;}private int count=0;public void run(){try{while(!Thread.interrupted()){Toast t = finishedQueue.take();//下面的代码用于确认土司都是顺序进入的,并且都完成了所有流程//枚举类型的调用类似于静态变量,都需要通过类来调用if(t.getId() != ++count || t.getStatus() !=Toast.Status.JAMMED){System.out.println("error");}else{System.out.println("Chomp! "+t);}}}catch(InterruptedException ex){System.out.println("Eater通过中断异常退出");}System.out.println("Eater off");}
}public class ToastOmatic {public static void main(String[] args) throws Exception{//各个线程需要使用的阻塞队列ToastQueue  dryQueue = new ToastQueue(),butterQueue = new ToastQueue(),finishedQueue = new ToastQueue();ExecutorService exec = Executors.newCachedThreadPool();//分别开启各个线程exec.execute(new Toaster(dryQueue));exec.execute(new Butterer(dryQueue,butterQueue));exec.execute(new Jammer(butterQueue,finishedQueue));exec.execute(new Eater(finishedQueue));exec.shutdown();TimeUnit.SECONDS.sleep(1);exec.shutdownNow();//终止}
}/*Output
Toast 1Status: DRY
Toast 1Status: BUTTERED
Toast 1Status: JAMMED
Chomp! Toast 1Status: JAMMED
Toast 2Status: DRY
Toast 2Status: BUTTERED
Toast 2Status: JAMMED
Chomp! Toast 2Status: JAMMED
Toast 3Status: DRY
Toast 3Status: BUTTERED
Toast 3Status: JAMMED
Chomp! Toast 3Status: JAMMED
Butterer通过中断退出
Butter off
Jammer通过中断异常退出
Jammer off
Toaster通过中断异常退出
Toaster off
Eater通过中断异常退出
Eater off* */

从上面的代码看,同步队列不但可以用于线程间通信,当其作为共享资源时还可以自动保证正确的同步,不需要我们自己写代码维护。

四.线程间利用管道进行输入/输出

通过输入输出在线程间进行通信是一种很有效的方法,Java中提供了管道来完成这个功能,其包括了两个类:PipedWriter类(允许线程向管道中写)和PipedReader类(允许线程从管道中读)。管道很类似于上面的同步队列,但是注意向管道中写入的只能是字符串(字符数组)和数字,PipedReader的read()方法是阻塞的。管道使用的基本逻辑:一个线程向PipedWriter对象中写入一些内容,然后另一个线程从与前面PipedWriter对象相关联的PipedReader()对象中读取这些内容,从而实现了线程间的通信。下面的代码示范了管道的基本特性:

package lkl;import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;/*** 使用管道(Piped)进行线程间通信* 包括了两个类PipedWriter和PipedReader,通信的大概思想是一个线程向PipedWriter对象写入* 信息,然后另一个线程就可以从与之关联的PipedReader对象中读取这些信息,如果没有信息可读* 则read()方法阻塞;所以本质上来讲,其实还是一个阻塞队列.* 最易想到的应用场景就是一个线程不断的生产某种资源向PipedWriter对象中写入,另一个线程* 就从与之相关联的PipedReader对象中读取这种资源,类似于阻塞队列但是只能传入* 字符和字符串信息.当然我们传入的不一定要是 资源,* 也可以是线程间协作的信息,因为read()是阻塞的,就可以代替wait()和notify()进行线程* 间的通信了* *///发送信息的线程
class Sender implements Runnable{private Random rand = new Random(100);private PipedWriter out = new PipedWriter();public PipedWriter getPipedWriter(){return out;}public void run(){try{while(true){for(char c='A'; c<='Z';c++){out.write(c);//写入后睡眠,但是读取线程是不会睡眠的,所以就会阻塞TimeUnit.MILLISECONDS.sleep(rand.nextInt(500)); }}}catch(IOException ex){System.out.println("Sender IO Exception");}catch(InterruptedException ex){System.out.println("Sender InterrupedException");}}
}//接受信息的线程
class Receiver implements Runnable{private PipedReader in;public Receiver(Sender sender) throws IOException{//创建与out相关联的PipedReader对象in = new PipedReader(sender.getPipedWriter());}public void run(){try{while(true){//这里的read()方法会阻塞//注意从read()阻塞中被中断属于IO异常而不是中断异常System.out.println("Read "+(char)in.read());}}catch(IOException ex){System.out.println("Receiver IOException");}}
}public class PipedIO {public static void main(String[] args) throws Exception {Sender sender = new Sender();Receiver receiver = new Receiver(sender);ExecutorService exec = Executors.newCachedThreadPool();exec.execute(receiver);exec.execute(sender);exec.shutdown();TimeUnit.SECONDS.sleep(3);exec.shutdownNow();}
}/*Output
Read A
Read B
Read C
Read D
Read E
Read F
Read G
Read H
Read I
Read J
Read K
Read L
Receiver IOException
Sender InterrupedException
*/

Thinking in Java---再谈线程通信相关推荐

  1. Java多线程之线程通信之生产者消费者阻塞队列版

    Java多线程之线程通信之生产者消费者传统版和阻塞队列版 目录 线程通信之生产者消费者传统版 线程通信之生产者消费者阻塞队列版 1. 线程通信之生产者消费者传统版 题目: 一个初始值为零的变量,两个线 ...

  2. Java之【线程通信】--标志位练习

    * 写两个线程,一个线程打印1-52,另一个线程答应字母A-Z. * 打印顺序为12A34B56C--5152Z.通过使用线程之间的通信协调关系. 注:分别给两个对象构造一个对象o,数字每打印两个或字 ...

  3. java浅谈线程安全之锁

    在java锁的知识中,我们首先要知道分布式锁和本地锁的概念. 1.本地锁: 在单进程的系统中,存在多个线程去同时操作某个共享变量时,就需要使用本地锁,最常用的关键字:synchronized 2.分布 ...

  4. Java之【线程通信】--标志位练习2

    定义一个线程A,输出1 - 10之间的整数,定义一个线程B,逆序输出1 - 10之间的整数,要求线程A和线程B交替输出 方法一:非标志位方法 package Homework; //1 定义一个线程A ...

  5. java 多线程 8 : 线程通信

    wait(): 使当前线程等待,直到其他线程调用该同步监视器notify()方法 , 或者notifyAll()方法来唤醒该线程. wai()方法有三种形式 , 无参的一直等 :带毫秒的 , 和带微秒 ...

  6. java 线程 condition_Java编程中实现Condition控制线程通信

    java中控制线程通信的方法 1.传统的方式:利用synchronized关键字来保证同步,结合wait(),notify(),notifyall()控制线程通信.不灵活. 2.利用condition ...

  7. java 管程通信_Java多线程07_线程通信之管程法与信号灯法

    Java多线程07_线程通信之管程法与信号灯法 线程通信 的应用场景是 消费者/生产者问题: 解决线程通信的方法: wait()表示线程一直等待,直到接到通知(会释放锁) wait(long time ...

  8. Java学习系列(十五)Java面向对象之细谈线程、线程通信(下)

    竞争资源(共享资源):如果有多条线程需要并发访问.并修改某个对象,该对象就是"竞争资源".为了避免多个线程"自由竞争"修改共享资源所导致的不安全问题. 线程同步 ...

  9. java的知识点34——线程通信 || 生产者与消费者问题

    线程通信 应用场景:生产者和消费者问题 • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费 • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待, ...

最新文章

  1. 4.ClassLink - 一种新型的VPC 经典网络的连接方式
  2. java 单机版_JAVA单机版管理系统源代码.pdf
  3. 20_集合_第20天(Map、可变参数、Collections)
  4. java 利用dfs生成简单的随机迷宫(效率不高)
  5. linux勒索病毒名称,Linux 也有加密勒索病毒了,也有人真的中了
  6. 07.es_thread_pool使用
  7. 关于Java中serialUID的序列化的问题
  8. LOADRUNNER8.1中文版加10000客户端破解补订
  9. java gson fromjson,Gson的fromJson()方法
  10. linux安装elasticsearch5.5
  11. 39--打印从1到最大的n位数
  12. oracle模拟重叠事务,ORACLE的事务读一致性与语句读一致性
  13. JSF请求处理过程(二)请求处理过程总览(FacesServlet#service)
  14. php-resque的设计和使用
  15. 狂神设计模式笔记-工厂模式
  16. 程序猿能力矩阵 — 敢測吗?看自己在哪个档次
  17. Idea安装Eslint插件详解 提示:Plugin NativeScript was not installed解决
  18. linux没有.brashrc文件,Linux 安装 Redis4.0.6
  19. 阅读go语言程序设计
  20. 奇迹mu开服教程:服务端的架设及开服注意事项

热门文章

  1. oracle认证考试_Oracle云认证–通过此3小时免费课程通过考试
  2. 【分层图】分层图学习笔记
  3. Linux创建和删除软连接
  4. 装修记事之网络规划篇
  5. 编程模板中给出了一个字符串,其中包含了含有重复的人名,请直接输出出现最多的人名。
  6. Java静态方法调用内部类
  7. 使用matplotlib绘图时坐标轴中文显示乱码的解决方法
  8. Ubuntu下开启护眼模式
  9. (不正当关机)笔记本开机循环修复解决方案(删除bootsafe64_ev.sys文件)
  10. 物联网卡与现代医疗的结合会产生怎样的效果?