Java编程思想-并发(3)

生产者消费者队列

使用同步队列来解决任务协作的问题比使用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 interrupted
java.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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值