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彼此连接。因为这种队列的阻塞特性提供了所有必需的同步,所以你应该注意到了,这里不需要任何显式的同步 -------- 不必考虑当你从这种队列中读取时,其中是否还有元素,因此这个队列在没有元素进,将直接阻塞读取者。

0 0