Java 多线程 新类库中的构件 Java编程思想读书笔记
来源:互联网 发布:淘宝美工多少钱一个月 编辑:程序博客网 时间:2024/06/06 07:11
CountDownLatch
它被用来同步一个或多个任务,强制它们等待由其他任务执行的一组操作完成。
你可以向CountDownLatch对象设置一个初始计数数值,任何在这个对象上调用wait()的方法都将阻塞,直到这个计数值到达0。 其他任务在结束其工作时,可以在该对象上调用countDown()来减少这个计数值。当计数值到达0,将自动notiryAll()。CountDownLatch被设计为只触发一次notiryAll(),计数值不会被重置。如果你需要能够重置计数值的版本,则可以使用CyclicBarrier。
CountDownLatch的典型用法是将一个程序分为n个互相独立的可解决任务,并创建值为0的CountDownLatch。当每个任务完成时,都会在这个锁存器上调用countDown()。等待问题被解决的任务在这个锁存器上调用await(),将它们自己拦住,直到锁存器计数结束。下面是演示这种技术的一个框架示例:
import java.util.Random; import java.util.concurrent.*; // 必须先执行一部分的任务class TaskPortion implements Runnable{ private static int counter = 0; //线程总数 private final int id = counter++; //当前线程在所有线程的位置 private static Random rand = new Random(47); private final CountDownLatch latch; TaskPortion(CountDownLatch latch) { this.latch = latch; } public void run() { try{ doWork(); //执行任务 latch.countDown(); //任务执行完降低计数器的数目 } catch(InterruptedException ex) { //以可接受的方式退出 } } public void doWork() throws InterruptedException { TimeUnit.MICROSECONDS.sleep(rand.nextInt(2000)); System.out.println(this + " completed "); } public String toString() { return String.format("%1$-3d", id); } } // 所有TaskPortion执行完这些任务才会执行 class WaitingTask implements Runnable { private static int counter = 0; private final int id = counter++; private final CountDownLatch latch; WaitingTask(CountDownLatch latch) { this.latch = latch; } public void run() { try{ latch.await(); //等待latch为0时才触发一次notiryAll() System.out.println("Latch barrier passed for " + this); } catch(InterruptedException ex){ System.out.println(this + " interrupted"); } } public String toString() { return String.format("WaitingTask %1$-3d", id); } } public class CountDownLatchDemo { static final int SIZE = 100; public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); //下面所有的任务都必须共享这个 latch CountDownLatch latch = new CountDownLatch(SIZE); for(int i = 0; i < 10; i++) exec.execute(new WaitingTask(latch)); for(int i = 0; i < SIZE; i++) exec.execute(new TaskPortion(latch)); System.out.println("Launched all tasks"); exec.shutdown(); } }
从输出可以看到,只有所有的TaskPortion执行完了,WaitingTask才开始执行。
TaskPortion将随机休眠一段时间,以模拟这部分工作的完成,而WaitingTask表示系统中必须等待的部分,它要等待到问题的初始化部分完成为止,即上例的TaskPortion完成。所有的任务都使用了main()定义的同一个CountDownLatch。
类库的线程安全
注意,TaskPortion包含一个静态的Random对象,这意味着多个任务可能会同时调用Random.nextInt(),这是否安全?
如果存在问题,在这种情况下,可能通过向TaskPortion提供其自己的Random对象来解决。也就是说,通过移除static限定符的方式解决。但是这个 问题对于Java标准类库中的来说,也大有存在:哪些是线程安全的?哪些不是?
遗憾的是,JDK文档并没有指出这一点。Random.nextInt()碰巧是安全的,但是,你必须通过使用Web引擎,或者审视Java类库代码,去逐个地提示这点。这对于被设计为支持,至少理论上支持并发的程序设计语言来说,并非是一件好事。
CyclicBarrier
CyclicBarriert适用于这样的情况:你希望创建一组任务,它们并行地执行工作,然后在进行下一个任务之前等待,这样直到所有任务都完成(看起来有些像join())。它使得所有并行任务都将在栅栏处列队,因此可以致地向前移动。这非常像CountDownLatch,只是CountDownLatch是只触发一次notifyAll事件,而CyclicBarrier可以多次重用。
下面是一个用CyclicBarriert实现的赛马游戏:
import java.util.*; import java.util.concurrent.*; //马class Horse implements Runnable { private static int counter = 0; //总共马的数量 private final int id = counter++; //第几匹马 private int strides = 0; //栅栏 private static Random rand = new Random(47); private static CyclicBarrier barrier; public Horse(CyclicBarrier b){ barrier = b; } public synchronized int getStrides(){ //马跑到了第几栅栏 return strides; } public void run() { try{ while(!Thread.interrupted()) { System.out.println("start"); synchronized (this) { strides += rand.nextInt(3); //随机跑过几个栅栏 } System.out.println("await"); barrier.await(); //进入等待 } } catch(InterruptedException e) { // 以可接受的方式退出 } catch(BrokenBarrierException e) { throw new RuntimeException(e); } } public String toString() { return "Horse " + id + " "; } public String tracks() { //描绘出当前马在栅栏的位置 StringBuilder s = new StringBuilder(); for(int i = 0; i < getStrides(); i++) s.append("*"); s.append(id); return s.toString(); } } //赛马游戏public class HorseRace { static final int FINISH_LINE =30; //30栅栏 private List<Horse> horses = new ArrayList<Horse>(); private ExecutorService exec = Executors.newCachedThreadPool(); private CyclicBarrier barrier; public HorseRace(int nHorses, final int pause) { barrier = new CyclicBarrier(nHorses, new Runnable() { public void run() { //开始一个回合 StringBuilder s = new StringBuilder(); for(int i = 0; i < FINISH_LINE; i++) s.append("="); System.out.println(s);//输出分隔线 for(Horse horse : horses) System.out.println(horse.tracks()); //输出当前马在栅栏的位置 //是否结束 for(Horse horse : horses){ if(horse.getStrides() >= FINISH_LINE) { System.out.println(horse + " won!"); exec.shutdownNow(); return ; } } try { TimeUnit.MICROSECONDS.sleep(pause); //停止一段时间 System.out.println("notifyAll "); } catch(InterruptedException e) { System.out.println("barrier-action sleep interrupted"); } } }); //启动线程 for(int i = 0; i < nHorses; i++) { Horse horse = new Horse(barrier); horses.add(horse); exec.execute(horse); } } public static void main(String[] args) { int nHorses = 3; int pause = 200; new HorseRace(nHorses, pause); } }
可以向CyclicBarrier提供一个“栅栏动作”,它是一个Runnable。注意,这里栅栏动作是作为匿名内部类创建的。它被提交给CyclicBarrier构造器了。CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。
CyclicBarrier使得每匹马都要执行为了向前移动所必需执行的所有工作,然后必须在栅栏处等待其他所有的马都准备完毕。当所有的马都向前移动时,CyclicBarrier将自动调用Runnable栅栏处动作任务,按顺序显示马和终点线的位置。
DelayQueue
DelayQueue是一个无界的BlockingQueue,用于放置实现了Delayed接口的对象。Delayed可以存放一个表示延迟的数值。Delayed扩展了Comparable接口。而DelayQueue的比较基准为延时的时间值。DelayQueue内部是使用PriorityQueue实现的。如果没有任何延迟到期,那么DelayQueue就不会有任何元素,并且poll()将返回null(正因为这样,你不能将null放置到这种队列中)
下面是一个示例,其中Delayed对象自身就是任务,而DelayedTaskConsumer将最“紧急”的任务从队列中取出,然后运行它。注意,这样 DelayedQueue就成为了优先级队列的一种变体:
import java.util.*; import java.util.concurrent.*; import static java.util.concurrent.TimeUnit.*; class DelayedTask implements Runnable, Delayed{ private static int counter = 0; private final int id = counter++; private final int delta; //经过多少时间任务到期 private final long trigger; //任务到期时间 protected static List<DelayedTask> sequence = new ArrayList<DelayedTask>(); //任务集合 public DelayedTask(int delayInMillisecodes) { delta = delayInMillisecodes; trigger = System.nanoTime() + NANOSECONDS.convert(delta,MILLISECONDS); //计算到期时间 sequence.add(this); } //返回离当前时间还有多久到期 public long getDelay(TimeUnit unit) { //System.nanoTime()返回最准确的可用系统计时器的当前值,以毫微秒为单位 //trigger减去当前时间转化为纳秒作为延迟值 return unit.convert(trigger - System.nanoTime(), NANOSECONDS); } //实现比较方法,以到期时间作为比较基准 public int compareTo(Delayed arg) { DelayedTask that = (DelayedTask)arg; if(trigger < that.trigger) return -1; if(trigger > that.trigger) return 1; return 0; } public void run() { System.out.println( " 运行 " + this); } public String toString() { return String.format("[%1$-4d]", delta) + " Task " + id; } public String summary() { return "(" + id + ":" + delta + ")"; } //ExecutorService查看任务创建顺序 public static class EndSentinel extends DelayedTask { private ExecutorService exec; public EndSentinel(int delay, ExecutorService e) { super(delay); exec = e; } public void run() { System.out.println("查看Task的创建顺序"); for(DelayedTask pt : sequence) { System.out.println(pt.summary() + " "); } System.out.println(); System.out.println(this + " Calling shutdownNow"); exec.shutdownNow(); } } } //使用优先队列执行任务class DelayedTaskConsumer implements Runnable { private DelayQueue<DelayedTask> q; //优先队列 public DelayedTaskConsumer(DelayQueue<DelayedTask> q) { this.q = q; } public void run() { try{ while(!Thread.interrupted()) q.take().run(); } catch(InterruptedException e) { //以可接受的方式退出 } System.out.println("Finished DelayedTaskConsumer"); } } public class DelayedQueueDemo { public static void main(String[] args) { Random rand = new Random(47); ExecutorService exec = Executors.newCachedThreadPool(); DelayQueue<DelayedTask> queue = new DelayQueue<DelayedTask>(); // 创建任务 for(int i = 0; i < 20; i++) queue.put(new DelayedTask(rand.nextInt(5000))); //查看任务创建顺序 queue.add(new DelayedTask.EndSentinel(5000, exec)); exec.execute(new DelayedTaskConsumer(queue)); //启动任务 } }
DelayedTask包含一个称为sequence的List<DelayedTask>,它保存了任务创建的顺序。
Delayed接口有一个方法名为getDelay(),它可以用来告知延迟到期有还有多长时间,或者延迟在多长时间之前已经到期了。这个方法强制使用TimeUnit类,因为这就是方法参数。这是一个非常方便的类,因为你可以很容易地转换单位而无需任何声明。例如,delta的值是以毫秒为单位存储的,而Java SE5的方法System.nanoTime()产生的时间则是以纳秒为单位的。你可以转换delta的值,通过声明它的单位以及你希望以什么单位表示:
TimeUnit.NANOSECONDS.convert(delta, MILLISECONDS);
为了排序,Delayed接口还继承了Comparable接口,因此必须实现compareTo(),使其可以产生合理的比较。toString() 和summary() 提供了输出格式化,而嵌套的EndSentinel类提供了一种关闭所有事物的途径。注意,因为DelayedTaskConsumer自身是一个任务,所人它有自己的Thread,它可以使用这个线程来运行从队列中获取的所有任务。由于任务是按照队列优先级的顺序执行的,因此在本例中不需要启动任何单独的线程来运行DelayedTask。
从输出中可以看到,任务创建的顺序对执行顺序没有任何影响,任务是按照所期望的延迟顺序执行的。
PriorityBlockingQueue
这是一个很基础的优先级队列,它具有可阻塞的读取操作。下面是一个示例,其中在优先级队列中的对象是按照优先级顺序从队伍中出现的任务。PrioritizedTask被赋予了一个优先级数,以此来提供这种顺序:
import java.util.*; import java.util.concurrent.*; class PrioritizedTask implements Runnable,Comparable<PrioritizedTask> { private Random rand = new Random(47); private static int counter = 0; private final int id = counter++; private final int priority; //优先级 protected static List<PrioritizedTask> sequence = new ArrayList<PrioritizedTask>(); public PrioritizedTask(int p) { this.priority = p; sequence.add(this); } public int compareTo(PrioritizedTask arg) { return priority < arg.priority ? 1 :(priority > arg.priority ? -1 : 0); } public void run() { try{ TimeUnit.MILLISECONDS.sleep(rand.nextInt(250)); } catch(InterruptedException e) { // Acceptable way to exit } System.out.println("运行: " + this); } public String toString() { return String.format("[%1$-3d]", priority) + " Task " + id; } public String summary() { return "(" + id + ":" + priority + ")"; } public static class EndSentinel extends PrioritizedTask { private ExecutorService exec; public EndSentinel(ExecutorService e) { super(-1); // Lowest priority in this program exec = e; } public void run() { int count = 0; for(PrioritizedTask pt : sequence) { System.out.println(pt.summary()); if(++count % 5 == 0) System.out.println(); } System.out.println(); System.out.println(this + " Calling shutdownNow()"); exec.shutdownNow(); } } } class PrioritizedTaskProducer implements Runnable { private Random rand = new Random(47); private Queue<Runnable> queue; private ExecutorService exec; public PrioritizedTaskProducer(Queue<Runnable> q, ExecutorService e) { queue = q; exec = e; // Used for EndSentinel } public void run() { // Unbounded queue: never blocks. // Fill it up fast with random priorities for(int i = 0; i < 20; i++) { queue.add(new PrioritizedTask(rand.nextInt(10))); Thread.yield(); } // Trickle in highest-priority jobs; try { for(int i = 0; i < 10; i++) { TimeUnit.MILLISECONDS.sleep(250); queue.add(new PrioritizedTask(10)); } // Add jobs, lowest priority first for(int i = 0; i < 10; i++) { queue.add(new PrioritizedTask(i)); } //A sentinel to stop all the tasks: queue.add(new PrioritizedTask.EndSentinel(exec)); } catch(InterruptedException e) { // Acceptable way to exit } System.out.println("Finished PrioritizedTaskProducer"); } } class PrioritizedTaskConsumer implements Runnable { private PriorityBlockingQueue<Runnable> q; public PrioritizedTaskConsumer(PriorityBlockingQueue<Runnable> q) { this.q = q; } public void run() { try { while(!Thread.interrupted()){ // Use current thread to run the task: q.take().run(); } } catch(InterruptedException e) { // Acceptable way to exit } System.out.println("Finished PrioritizedTaskConsumer"); } } public class PriorityBlockingQueueDemo { public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); PriorityBlockingQueue<Runnable> queue = new PriorityBlockingQueue<Runnable>(); exec.execute(new PrioritizedTaskProducer(queue, exec)); exec.execute(new PrioritizedTaskConsumer(queue)); } }与前一个示例相同,PrioritizedTask对象的创建序列被记录在sequence List中,用于和实际的执行顺序比较。run()方法将休眠一小段随机时间,然后打印对象信息,而EndSentinel提供 了和前面相同的功能,要确保它是队列中最后一个对象。
PrioritizedTaskProducer和PrioritizedTaskConsumer通过PriorityBlockingQueue彼此连接。因为这种队列的阻塞特性提供了所有必需的同步,所以你应该注意到了,这里不需要任何显式的同步 -------- 不必考虑当你从这种队列中读取时,其中是否还有元素,因此这个队列在没有元素进,将直接阻塞读取者。
- Java 多线程 新类库中的构件 Java编程思想读书笔记
- Java 多线程 同步 Java编程思想读书笔记
- Java 多线程 死锁 Java编程思想读书笔记
- java编程思想读书笔记-第十四章 多线程
- Java编程思想读书笔记
- java编程思想读书笔记
- java 编程思想 读书笔记
- <<java编程思想>>读书笔记
- java编程思想读书笔记
- java编程思想读书笔记
- 《Java编程思想》读书笔记
- Java 编程思想 - 读书笔记
- JAVA编程思想读书笔记
- 《java编程思想》读书笔记
- java编程思想读书笔记
- java 编程思想 读书笔记
- 《Java编程思想》读书笔记
- Java 多线程 基础入门 Java编程思想读书笔记
- 《STL源码剖析》学习笔记系列-----第一章:STL概论和版本简介
- flex图片路径问题以及找不到类型问题
- Valid Parentheses
- [LeetCode] Letter Combinations of a Phone Number
- android 创建桌面图标,luncher图标
- Java 多线程 新类库中的构件 Java编程思想读书笔记
- 算法(数学)与外语是程序员未来发展的基石
- [Poj2420]A Star not a Tree? (爬山算法||模拟退火算法)
- 生活点滴 -- 处事、心态、伤心、礼仪、学会长大!
- Hadoop之hive学习
- FastGUI for NGUI教程
- 【Nginx】epoll事件驱动模块
- 链表的基本操作
- Nagios监控MySQL报错:NRPE: Unable to read output的详细解决过程