Java编程思想-并发(3)

来源:互联网 发布:淘宝助理5.8图片搬家 编辑:程序博客网 时间:2024/05/18 23:25

生产者消费者队列

使用同步队列来解决任务协作的问题比使用wait和notify更加方便,因为后者需要在每次交互时都握手。而同步队列在任何时刻只允许一个任务插入或移除元素。在java.util.concurrent.BlockingQueue接口中提供了这个队列。该接口提供了大量的标准实现:LinkedBlockingQueue、ArrayBlockingQueue等。

如果消费者任务试图从队列中获取对象,而该队列此时为空,那么这些队列还可以挂起消费者任务。所以,阻塞队列比传统的wait和notify简单、可靠。

下面的示例将多了LiftOff对象的执行串行化了,消费者是LiftOffRunner,它将每个LiftOff对象从BlockingQueue中推行并直接运行。(即,BlockingQueue将显式地调用run而使用自己的线程运行,而不是为每一个任务启动一个新的线程。

class LiftOffRunner implements Runnable {    private BlockingQueue<LiftOff> rockets;    public LiftOffRunner(BlockingQueue<LiftOff> queue) {        rockets = queue;    }    public void add(LiftOff lo) {        try {            rockets.put(lo);        } catch(InterruptedException e) {            System.out.print("Interrupted during put()");        }    }    //当使用BlockingQueue加入或移除任务时,BlockingQueue实际上时=是直接调用了run方法,将run在BlockingQueue开辟的线程中执行,而不是新开一个任线程调用run方法。    @Override    public void run() {        try {            while(!Thread.interrupted()) {                LoftOff rocket = rockets.take();                rocket.run();            }        } catch(InterruptedException e) {            System.out.print("waking from take()");        }        System.out.print("Exiting LoftOffRunner");    }}public class Test {    static void getKey() {        try {            new BufferedReader(new InputStreamReader(System.in)).readLine();        } catch(IOException e) {            throw new RuntimeException(e);        }    }    static void getKey(String message) {        System.out.print(message);        getKey();    }    static void test(String msg, BlockingQueue<LiftOff> queue) {        System.out.print(msg);        LiftOffRunner runner = new LiftOffRunner(queue);        Thread t = new Thread(runner);        t.start();        for(int i = 0; i < 5; ++i) {            runner.add(new LiftOff(5));            getKey("Press 'enter' (" + msg + ")");            t.interrupt();            System.out.print("Finished " + msg + " test");        }    }    public static void main(String[] args) {        //无限大小的队列        test("LinkedBlockingQueue", new LinkedBlockingQueue<LiftOff>());        //固定大小的队列        test("ArrayBlockingQueue", new ArrayBlockingQueue<LiftOff>());        //size为1的队列        test("SynchronizedQueue", new SynchronizedQueue<LiftOff>());    }}

主线程中启动了一个子线程t,并传入一个LiftOffRunner任务,该子线程t将并发执行LiftOffRunner的run方法,而在run方法中显式调用了LiftOff的run方法(LiftOff也是一个Runnable),并非启动了一个新的线程,也就是说,这个程序只有两个线程,一个主线程,一个子线程,主线程负责监测从键盘中读入的enter,当敲入回车,程序退出。所有的Blocking的add操作都是在唯一的子线程中执行的,由于使用了BlockingQueue ,所有的同步可以忽略。

一个BlockingQueue的示例:吐司 BlockingQueue

下面这个BlockingQueue的示例,模拟了一台机器执行的三个任务:一个制作吐司、一个给吐司抹黄油、另一个在抹过黄油的吐司上涂果酱。我们可以使用三个BlockingQueue来模拟这个过程:

class Toast {    public enum Status {        DRY, BUTTERED, JAMMED    }    private Status status = Status.DRY;    private final int id;    public Toast(int idn) {        id = idn;    }    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;    }}class ToastQueue extends LinkedBlockingQueue<Toast> {}class Toaster implements Runnable {    private ToastQueue toastQueue;    private int count = 0;    private Random random = new Random(47);    public Toaster(ToastQueue tq) {        toastQueue = tq;    }    public void run() {        try {            while(!Thread.interrupted()) {                TimeUnit.MILLSECONDS.sleep(500);                Toast t = new Toast(count++);                System.out.print(t);                toastQueue.put(t);            }        } catch(InterruptedException e) {            System.out.print("Toaster interrupted");        }        System.out.print("Toaster off");    }}class Butterer implements Runnable {    private ToastQueue dryQueue, butteredQueue;    public Buttterer(ToastQueue dry, ToastQueue buttered) {        dryQueue = dry;        butteredQueue = buttered;    }    public void run() {        try {            while(!Thread.interrupted()) {                Toast t = dryQueue.take();                t.butter();                System.out.print(t);                butteredQueue.put(t);            }        } catch(InterruptedException e) {            System.out.print("Butterer interrupted");        }        System.out.print("Butterer off");    }}class Jammer implements Runnable {    private ToastQueue butteredQueue, finishedQueue;    public Jammer(ToastQueue buttered, ToastQueue finishedQueue) {        this.butteredQueue = buttered;        this.finishedQueue = finishedQueue;    }    public void run() {        try {            while(!Thread.interrupted()) {                Toast t = butteredQueue.take();                System.out.print(t);                finishedQueue.put(t);            }        } catch(InterruptedException e) {            System.out.print("Jammer interrupted");        }        System.out.print("Jammer off");    }}class Eater implements Runnable {    public void run() {        try {            while(!Thread.interrupted()) {                Toast t = finishedQueue.take();                if(t.getId() != Toast.Status.JAMMED) {                    System.out.print("Error: " + t);                } else {                    System.out.print("Chomp! " + t);                }            } catch(InterrruptedException e) {                System.out.print("Eater interrupted");            }        }        System.out.print("Eater off");    }}public class ToasterMatic {    public static void main(String[] args) {        ToastQueue dryQueue = new ToastQueue();        ToastQueue butteredQueue = new ToastQueue();        ToastQueue finishedQueue = new ToastQueue();        ExecutorService exec = Executors.newCachedThreadPool();        exec.exceute(new Toaster(dryQueue));        exec.exceute(new Butterer(dryQueue, butteredQueue));        exec.exceute(new Jammer(butteredQueue, finishedQueue));        exec.exceute(new Eater(finishedQueue));        TimeUnit.SECONDS.sleep(5);        exec.shutdownNow();    }}

任务间使用管道进行输入/输出

通过输入/输出在线程间进行通信通常很有用。提供线程功能的类库以“管道”的形式对线程间的输入/输出类库中的对应物就是PipedWriter类(允许任务向管道写)和PipedReader类(允许不同任务从同一个管道中读取)。管道也是生产者/消费者问题的一个解决方案。管道是一个阻塞队列。在引入BlockingQueue之前,管道非常常用。

下面的例子将使用管道在两个任务间通信:

class Sender implements Runnable {    private Random random = new Random(47);    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.write(c);                            TimeUnit.MILLSECONDS.sleep(rand.nextInt(500));                }            }        } catch(IOException e) {            System.out.print(e + " Sender write exception");        } catch(InterruptedException e) {            System.out.print(e + " Sender sleep interrupted");        }    }}class Receiver implements Runnable {    private PipedReader in;    public Receiver(Sender sender) throws IOException {        in = new PipedReader(sender.getPipedWriter());    }    public void run() {        try {            while(true) {            System.out.print("Read: " + (char)in.read() + ", ");            }        } catch(IOException e) {            System.out.print(e + "Receiver read exception");        }    }}public class PipedIO {    public static void main(String[] args) {        Sender sender = new Sender();        Receiver receiver = new Receiver(sender);        ExecutorService exec = Executors.newCachedThreadPool();        exec.execute(sender);        exec.execute(receiver);        TimeUnit.SECONS.sleep(4);        exec.shutdown();    }}
//输出Read: A, Read: B, Read: B, Read: D, Read: E, Read: F, Read: G, Read: H, Read: I, Read: J, Read: K, Read: L, Read: M, Read: N, Read: O, java.langInterruptedException: sleep interrupted Sender sleep interruptedjava.lang.io.InterruptedIOException Receiver read exception

Sender 和Receiver表示两个需要通信的任务。Sender创建一个PipedWriter用于向“管道”中写入,而Receiver建立一个PipedReader,这个PipedReader必须知道从哪里读取,所需必须传入一个PipedWriter参数。程序将并发执行这两个任务,Sender每次向管道按照A-z写入一个英文字母,然后随机休眠0-500毫秒数,接着由Receiver读取这个英文字母并打印,程序将在4秒后中断。

从输出还可以看到,调用shutdown时,程序立马中断了,说明PipedReader是可以中断的。这也是它与普通IO的区别,后者将不能被打断(即System.in.read()将不能被打断)。

0 0
原创粉丝点击