Java并发编程实践 目录
并发编程 04—— 闭锁CountDownLatch 与 栅栏CyclicBarrier
并发编程 06—— CompletionService : Executor 和 BlockingQueue
并发编程 10—— 任务取消 之 关闭 ExecutorService
并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
并发编程 13—— 线程池的使用 之 配置ThreadPoolExecutor 和 饱和策略
概述
第1部分 定义
“毒丸”是指一个放在队列上的对象,其含义是:“当得到这个对象时,立即停止。”在FIFO 队列中,“毒丸”对象将确保消费者在关闭之前首先完成队列中的所有工作,在提交“毒丸”对象之前提交的所有工作都会被处理,而生产者在提交了“毒丸”对象后,将不会在提交任何工作。在下面的程序清单中给出了一个单生产者——单消费者的桌面搜索示例,使用了“毒丸”对象来关闭服务。
第2部分 实例
1 /** 2 * 7.17 通过“毒丸”对象来关闭服务 3 * @ClassName: IndexingService 4 * @author xingle 5 * @date 2014-11-12 下午1:58:06 6 */ 7 public class IndexingService { 8 private static final int CAPACITY = 1000; 9 private static final File POISON = new File(""); 10 private final IndexerThread consumer = new IndexerThread(); 11 private final CrawlerThread producer = new CrawlerThread(); 12 private final BlockingQueue<File> queue; 13 //private final FileFilter fileFilter; 14 private final File root; 15 16 public IndexingService(File root) { 17 this.root = root; 18 this.queue = new LinkedBlockingQueue<File>(CAPACITY); 19 20 } 21 22 private boolean alreadyIndexed(File f) { 23 return false; 24 } 25 26 //生产者 27 class CrawlerThread extends Thread { 28 public void run() { 29 try { 30 crawl(root); 31 } catch (InterruptedException e) { /* fall through */ 32 } finally { 33 while (true) { 34 try { 35 System.out.println("放入“毒丸”"); 36 queue.put(POISON); 37 break; 38 } catch (InterruptedException e1) { /* retry */ 39 } 40 } 41 } 42 } 43 44 private void crawl(File root) throws InterruptedException { 45 File[] entries = root.listFiles(); 46 if (entries != null) { 47 for (File entry : entries) { 48 if (entry.isDirectory()) 49 crawl(entry); 50 else if (!alreadyIndexed(entry)){ 51 System.out.println("放入生产者队列文件:"+entry.getName()+" 来自线程:"+Thread.currentThread().getName()); 52 queue.put(entry); 53 } 54 } 55 } 56 } 57 } 58 59 //消费者 60 class IndexerThread extends Thread { 61 public void run() { 62 try { 63 while (true) { 64 File file = queue.take(); 65 if (file == POISON){ 66 System.out.println("遇到“毒丸”,终止"); 67 break; 68 } 69 70 else 71 indexFile(file); 72 } 73 } catch (InterruptedException consumed) { 74 } 75 } 76 77 public void indexFile(File file) { 78 System.out.println("消费者取出文件:"+file.getName()+" 来自线程:"+Thread.currentThread().getName()); 79 /* ... */ 80 }; 81 } 82 83 public void start() { 84 producer.start(); 85 consumer.start(); 86 } 87 88 public void stop() { 89 producer.interrupt(); 90 } 91 92 public void awaitTermination() throws InterruptedException { 93 consumer.join(); 94 } 95 96 }
测试代码:
1 /** 2 * 7.17 测试主程序( 通过“毒丸”对象来关闭服务) 3 * @ClassName: IndexingService_Main 4 * @author xingle 5 * @date 2014-11-12 下午2:25:36 6 */ 7 public class IndexingService_Main { 8 public static void main(String[] args) { 9 File file = new File("D:\\test1/"); 10 IndexingService c = new IndexingService(file); 11 c.start(); 12 try { 13 TimeUnit.MICROSECONDS.sleep(100);// 停止XX时间,显示出消费速度慢于生产速度 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 c.stop(); 18 } 19 }
单次执行结果:
只有在生产者和消费者的数量都已知的情况下,才可以使用”毒丸“对象。
在 IndexingService中采用的解决方案可以扩展到多个生产者:只需每个生产者都想队列放入一个”毒丸“对象,并且消费者仅当在接收到N个生产者的”毒丸“对象时才停止。这种方法也可以扩展到多个消费者的情况,只需生产者将N个”毒丸“对象放入队列。然而,当生产者和消费者的数量较大时,这种方法将变的难以使用。只有在无界队列中,”毒丸“对象才能可靠地工作。