[关闭]
@lambeta 2016-09-19T23:17:38.000000Z 字数 13256 阅读 564

附录A

translation


第三部分

附录

附录 A

练习题答案

第1章到第8章,每一章都是以习题作为结尾的,这些习题旨在测试你对章节内容的理解程度。本附录会给出这些习题的答案。

第1章:Threads和Runnables

  1. 线程就是一条在程序代码中独立执行的路径。
  2. 一个runnable就是一段封装在对象中的代码序列,它的类实现了Runnable接口。
  3. 类Thread提供了一个底层操作系统线程架构的统一接口。Runnable接口为关联着Thread对象的线程提供执行代码。
  4. 我们有两种方式去创建一个Runnable对象,创建一个实现了Runnable接口的匿名类或者使用lambda表达式。
  5. 我们有两种方式将一个runnable关联到一个Thread对象上,把一个runnable传递到Thread类的接收runnable参数的构造函数里或者在构造函数不接收runnable参数的时候,继承Thread类继而后重写它的void run()方法。由于Thread实现了Runnable接口,所以Thread对象也是runnables。
  6. 构成Thread状态的5个标识分别是线程名称、线程存活的标识、线程的执行状态(是否可执行?)、线程的优先级以及该线程是否为守护线程的标识。
  7. 答案是false:默认的线程名称以Thread-前缀开头。

  8. 通过调用接收线程名称的Thread构造函数或者void setName(String name)方法,你可以给线程一个非默认的名称。

  9. 通过调用Thread的boolean isAlive()方法判断一条线程是否活着。
  10. Thread.State枚举的常量包括NEW(该状态下线程还没有开始执行),RUNNABLE(该状态下线程正在JVM中执行),BLOCKED(该状态下线程被阻塞,正在等待一个监听锁),WAITING(该状态下线程无限制地等待另外一个线程执行特定的操作)以及TERMINATED(该状态下线程已经退出)。
  11. 你可以通过调用Thread的Thread.State getState()方法获取当前的线程执行状态。
  12. 优先级指的是线程的相对重要性。
  13. 使用setPriority()会影响应用程序在操作系统之间的可移植性,因为不同的调度器会采取不同的方式处理优先级。
  14. 传递给优先级的值介于Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之间。
  15. 答案是true。一个守护线程会在应用程序最后一个非守护线程消失之后自动死亡,因此应用程序才能终止。
  16. 如果这个线程之前已经启动运行或者已经死亡,这个方法就会抛出java.lang.IllegalThreadStateException
  17. 为了终止无限运行的应用程序,可以在Windows或者非Windows系统上同时按住Ctrl和C键。
  18. 组成Thread中断机制的方法包括void interrupt(),static boolean interrupted()以及boolean isInterrupted()。
  19. 答案是false:boolean isInterrupted()方法并不会清除线程的中断状态。这个中断状态并不会受到影响。
  20. 当线程被中断时,它会抛出InterruptedException。
  21. 一个忙循环就是一条被设计来消耗时间的循环语句。
  22. Thread中让一条线程等待另外一条线程死亡的方法有void join(),void join(long millis),void join(long millis, int nanos)。
  23. Thread中让一条线程睡眠的方法有void sleep(long millis)和void sleep(long millis, int nanos)。
  24. 清单A-1展示了在第1章中的IntSleep应用程序。

清单A-1 中断一条睡眠中的后台线程

  1. public class IntSleep
  2. {
  3. public static void main(String[] args)
  4. {
  5. Runnable r = new Runnable()
  6. {
  7. @Override
  8. public void run()
  9. {
  10. while (true)
  11. {
  12. System.out.println("hello");
  13. try
  14. {
  15. Thread.sleep(100);
  16. }
  17. catch (InterruptedException ie)
  18. {
  19. System.out.println("interrupted");
  20. break;
  21. }
  22. }
  23. }
  24. };
  25. Thread t = new Thread(r);
  26. t.start();
  27. try
  28. {
  29. Thread.sleep(2000);
  30. }
  31. catch (InterruptedException ie)
  32. {
  33. }
  34. t.interrupt();
  35. }
  36. }

第2章:同步

  1. 线程中的三个问题分别是竞态条件、数据竞争以及缓存变量。
  2. 答案是false:当计算的正确性取决于相对时间或者调度器所控制的多线程交叉时,竞态条件就会发生。
  3. 同步指的是一种JVM的特性,它用于保证两条及其以上的线程不会同时在一个临界区中执行。
  4. 同步的两种属性分别是互斥和可见性。
  5. 同步是基于监听器实现出来的,它是控制对临界区进行访问的并发构造,必须不可分割地执行。每个Java对象都关联着一个监听器,线程可以通过上锁或解锁的方式获取或者释放监听器上的锁。
  6. 答案是true:已经获取锁的线程在调用Thread的任意sleep()方法时是不会释放锁的。
  7. 你可以通过在方法头前包含一个synchronized关键字的方式指定一个同步方法。
  8. 你可以通过采用synchronized(object) {}这种语法的方式指定一个同步块。
  9. 活性这个词代表着某件正确的事情最终会发生。
  10. 三种活性的挑战分别是死锁、活锁以及饿死(也被称为无限延迟)。
  11. volatile关键字有别于synchronized关键字之处在于volatitle只处理了可见性问题,而synchronized却处理了互斥和可见性问题。
  12. 答案是true:Java可以在无需同步的情况下安全地访问一个final的属性。
  13. 类CheckingAccount的线程问题是withdraw()方法中if (amount <= balance)和balance -= amount;(取到的钱超出可用余额)存在的竞态条件以及balance属性可能被缓存的问题。这个balance属性会被缓存在多处理器、多核系统中并且缓存的拷贝会被withdrawal线程使用,这个拷贝可能和当初通过默认主线程在构造函数中的设置的初始balance不一致。
  14. 清单A-2展示在第2章被调用的CheckingAccount应用程序。

清单 A-2 修复存在问题的账户检查程序

  1. public class CheckingAccount
  2. {
  3. private volatile int balance;
  4. public CheckingAccount(int initialBalance)
  5. {
  6. balance = initialBalance;
  7. }
  8. public synchronized boolean withdraw(int amount)
  9. {
  10. if (amount <= balance)
  11. {
  12. try
  13. {
  14. Thread.sleep((int) (Math.random() * 200));
  15. }
  16. catch (InterruptedException ie)
  17. {
  18. }
  19. balance -= amount;
  20. return true;
  21. }
  22. return false;
  23. }
  24. public static void main(String[] args)
  25. {
  26. final CheckingAccount ca = new CheckingAccount(100);
  27. Runnable r = new Runnable()
  28. {
  29. @Override
  30. public void run()
  31. {
  32. String name = Thread.currentThread().getName();
  33. for (int i = 0; i < 10; i++)
  34. }
  35. };
  36. Thread thdHusband = new Thread(r);
  37. thdHusband.setName("Husband");
  38. Thread thdWife = new Thread(r);
  39. thdWife.setName("Wife");
  40. thdHusband.start();
  41. thdWife.start();
  42. } }

这段程序使用了volatitle关键字应对潜在的缓存问题,并用synchronized关键字满足了互斥的需要。

第3章:等待和通知

  1. 所谓的条件(condition)指的就是持续执行的前置条件。
  2. 支持条件的API是由Object的三个wait()方法,一个notify()及一个notifyAll()方法组成的。这些wait()方法等待某个条件成立,而notify()和notifyAll()方法则在这个条件成立时通知处于等待中的线程。
  3. 答案是ture:wait()方法是可以被中断的。
  4. 你可以调用notifyAll()方法唤醒在同一对象监听器上等待的所有线程。
  5. 答案是false:已经获取锁的线程在调用Object上的任意wait()方法时会释放锁。
  6. 条件队列就是一种数据结构,用于存储那些等待某个条件成立的线程。这些等待中的线程被称为等待集合。
  7. 当你在同步上下文之外调用此API的任意方法时,会导致IllegalMonitorStateException发生。
  8. 假唤醒指的是线程在没有被通知、被中断或者超时的情况下醒来。
  9. 你应该在一个循环中调用wait()方法以保证活性和安全。
  10. 清单A-3展示了第3章中的Await应用程序

清单A-3 使用wait()以及notifyAll()来创建一个更高级的并发构造

  1. public class Await
  2. {
  3. static volatile int count;
  4. public static void main(String[] args)
  5. {
  6. Runnable r = () ->
  7. {
  8. Thread curThread = Thread.currentThread();
  9. System.out.printf("%s has entered runnable and is " +
  10. "waiting%n", curThread.getName());
  11. synchronized(Await.class)
  12. {
  13. count++;
  14. try
  15. {
  16. Thread.sleep(2000);
  17. while (count < 3)
  18. Await.class.wait();
  19. }
  20. catch (InterruptedException ie)
  21. {
  22. }
  23. }
  24. System.out.printf("%s has woken up and is " +
  25. "terminating%n",
  26. curThread.getName());
  27. };
  28. Thread thdA = new Thread(r, "thdA");
  29. Thread thdB = new Thread(r, "thdB");
  30. Thread thdC = new Thread(r, "thdC");
  31. thdA.start();
  32. thdB.start();
  33. thdC.start();
  34. r = new Runnable()
  35. {
  36. @Override
  37. public void run()
  38. {
  39. try
  40. {
  41. while (count < 3)
  42. Thread.sleep(100);
  43. synchronized(Await.class)
  44. {
  45. Await.class.notifyAll();
  46. }
  47. }
  48. catch (InterruptedException ie)
  49. {
  50. }
  51. }
  52. };
  53. Thread thd = new Thread(r);
  54. thd.start();
  55. }
  56. }

第4章:额外的线程能力

  1. 线程组就是一组线程,由类ThreadGroup表示。
  2. 使用一个线程组,你可以对其中的所有线程进行统一操作以简化线程管理。
  3. 因为多数有用的ThreadGroup方法都已经废弃,并且在获取一组活跃线程数和列举这些线程之间存在“检查时间到使用时间”这一类别的竞态条件,所以你应该避免使用线程组。
  4. 由于ThreadGroup在处理线程执行过程中产生的异常方面做出的贡献,你还是应该对它有所了解。
  5. 每个ThreadLocal的实例代表了一个线程局部变量,它为每个访问该变量的线程提供了单独的存储槽。
  6. 答案是true。如果当线程调用get()方法时,entry不存在,那么它会调用initialValue()方法。
  7. 你可以使用类InheritableThreadLocal把一个值从父线程传到子线程当中。
  8. 类Timer和TimerTask组成了Timer框架。
  9. 答案是false:Timer()会创建一个新的定时器,其任务执行线程会以非守护线程的方式运行。
  10. 在固定延迟执行中,每次执行都是相对于上次执行的实际发生时间的。当某次执行因为一些原因被延迟了(比如垃圾回收),后续的执行也都会被延迟。
  11. 你可以调用schedule()方法来调度一个固定延迟执行的任务。
  12. 在以固定速率执行的方式当中,每次执行都是相对于初次执行的调度时间而被调度的。当某个执行因为一些原因被延迟了(比如垃圾回收),两次以上的执行将会迅速相继发生。
  13. Timer的cancel()和TimerTask的cancel()方法的区别在于Timer的cancel()会终止定时器并放弃当前任意被调度的定时任务,而TimerTask的cancel()方法仅仅会取消正在调用的定时任务。
  14. 清单A-4展示了第4章中的BackAndForth应用程序。

清单A-4 借助Timer,反复地将一个星号前后移动

  1. import java.util.Timer;
  2. import java.util.TimerTask;
  3. public class Untitled
  4. {
  5. static enum Direction { FORWARDS, BACKWARDS }
  6. public static void main(String[] args)
  7. {
  8. TimerTask task = new TimerTask()
  9. {
  10. final static int MAXSTEPS = 20;
  11. volatile Direction direction = Direction.FORWARDS;
  12. volatile int steps = 0;
  13. @Override
  14. public void run()
  15. {
  16. switch (direction)
  17. {
  18. case FORWARDS : System.out.print("\b ");
  19. System.out.print("*");
  20. break;
  21. case BACKWARDS: System.out.print("\b ");
  22. System.out.print("\b\b*");
  23. }
  24. if (++steps == MAXSTEPS)
  25. {
  26. direction =
  27. (direction == Direction.FORWARDS)
  28. ? Direction.BACKWARDS
  29. : Direction.FORWARDS;
  30. steps = 0;
  31. }
  32. }
  33. };
  34. Timer timer = new Timer();
  35. timer.schedule(task, 0, 100);
  36. }
  37. }

第5章:并发工具类和Executor框架

  1. 并发工具类是一组用于克服Java底层线程问题的类和接口的框架。特别地,低级的并发原语如synchronized和wait()/notify()经常难以正确使用。过于依赖这些并发原语会导致性能问题,进而影响了应用程序的扩展性。Java底层的线程功能也并未包含线程池以及信号量这样的高级构造。
  2. 包java.util.concurrent、java.util.concurrent.atomic以及java.util.concurrent.locks构成了并发实用工具类。
  3. 一个任务是这样一种对象,它的类实现了java.lang.Runnable(一个可运行的任务)或者java.util.concurrent.Callable接口(一个可被调用的任务)。
  4. 一个executor就是这样一种对象,它的类直接或间接地实现了java.util.concurrent.Executor接口,这样便从任务执行机制中解耦了任务的提交操作。
  5. Executor接口仅仅只关注Runnable接口。也就是说,一个可运行任务是没法简单地向其调用者返回结果值的(因为Runnable的run()方法不会返回结果值);Executor接口也没有提供一种方式来追踪正在执行中的任务进程、取消正在执行的任务或者确定运行的任务什么时候结束执行;Executor无法执行一组可运行的任务;而且Executor也没有为应用程序提供一种关闭executor的方式(更为正确地关闭executor)。
  6. ExecutorService接口克服了Executor的局限性。
  7. Runnable的run()方法和Callable的call()方法之间的区别如下:run()无法返回结果值,而call()方法可以;run()方法无法抛出受检异常,而call()方法可以。
  8. 答案是false:你可以从Callable的call()方法中抛出受检和非受检异常,但是只能从Runnable的run()方法中抛出非受检的异常。
  9. future是这样一种对象,它的类实现了Future接口。它代表了一种异步的计算并且提供了取消任务、返回任务结果值以及确定是否任务已经结束的方法。
  10. 类Executors的newFixedThreadPool()方法创建了一个线程池复用固定数量的线程操作一个共享的无限队列。至多有nThreads个线程同时处理任务。如果额外的任务在所有线程都活跃时被提交了,它们会在队列中等待一条可用线程。如果在executor停止之前,任何线程因为执行当中的失败而终止,而后续任务的执行又必要的时候一条新的线程会取代其位置。这些在线程池中的线程会一直存在直到executor被显式地关闭。
  11. 清单A-5展示了第5章中的CountingThreads应用程序:

清单A-5 基于Executor的线程计数

  1. import java.util.concurrent.Executors;
  2. import java.util.concurrent.ExecutorService;
  3. public class CountingThreads
  4. {
  5. public static void main(String[] args)
  6. {
  7. Runnable r = new Runnable()
  8. {
  9. @Override
  10. public void run()
  11. {
  12. String name = Thread.currentThread().getName();
  13. int count = 0;
  14. while (true)
  15. System.out.println(name + ": " + count++);
  16. }
  17. };
  18. ExecutorService es = Executors.newFixedThreadPool(2);
  19. es.submit(r);
  20. es.submit(r);
  21. }
  22. }

12. 清单A-6展示了第5章中的CountingThreads应用程序,线程的名称是自定义的:

清单A-6 基于Executor的A、B线程计数

  1. import java.util.concurrent.Executors;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.ThreadFactory;
  4. public class CountingThreads
  5. {
  6. public static void main(String[] args)
  7. {
  8. Runnable r = new Runnable()
  9. {
  10. @Override
  11. public void run()
  12. {
  13. String name = Thread.currentThread().getName();
  14. int count = 0;
  15. while (true)
  16. System.out.println(name + ": " + count++);
  17. }
  18. };
  19. ExecutorService es =
  20. Executors.newSingleThreadExecutor(new NamedThread("A"));
  21. es.submit(r);
  22. es = Executors.newSingleThreadExecutor(new NamedThread("B"));
  23. es.submit(r);
  24. }
  25. }
  26. class NamedThread implements ThreadFactory
  27. {
  28. private volatile String name; // newThread() could be called by a
  29. // different thread
  30. NamedThread(String name)
  31. {
  32. this.name = name;
  33. }
  34. @Override
  35. public Thread newThread(Runnable r)
  36. {
  37. return new Thread(r, name);
  38. }
  39. }

### 第6章:同步器

1. 同步器指的是控制通用同步方法的类。
2. 倒计时门闩会导致一条或多条线程在“门口”一直等待直到另一条线程打开这扇门,线程才得以继续运行。它是由一个计数变量和两个操作组成的,这两个操作分别是“导致一条线程等待直到计数变为0”以及“递减计数变量”。
3. 调用CountDownLatch的void countDown()方法,当计数降至0时释放所有等待线程。
4. 同步屏障允许一组线程彼此互相等待,直到抵达到某个公共的屏障点。因为该屏障在等待线程被释放之后可以重用,所以称它为可循环使用的屏障。该同步器对于这类数量固定,并且互相之间必须不时等待彼此的多线程应用很有用。
5. 答案是false:当有线程正在等待时该同步屏障被重置了(通过 reset()方法)以及当同步屏障在await()方法被调用或任意线程正在等待时同步屏障被打破,CyclicBarrier的int await()方法就会抛出java.util.concurrent.BrokenBarrierException。
6. 交换器提供了一个线程彼此之间能够交换对象的同步点。每条线程都会往这个交换器的exchange()方法传入一些对象,匹配伙伴线程,同时接收伙伴线程中的对象作为返回值。
7. Exchanger的V exchange(V x)会在这个交互点上等待其他线程到达(除非调用线程被中断了),之后将所给对象传入其中,接收其它线程的对象作为返回。
8. 信号量维护了一组许可证(permit)来约束访问被限制资源的线程数。当没有可用的许可证时,线程的获取尝试会一直阻塞直到其它的线程释放一个许可证。
9. 信号量有两种类型,分别是计数信号量(当前值可以被递增加1)和二进制信号量或互斥信号量(当前值只能取0或1)。
10. phaser是一个更加弹性的同步屏障。和同步屏障一样,一个phaser使得一组线程在屏障上等待,在最后一条线程到达之后,这些线程得以继续执行。phaser也提供barrier action的等价操作。和同步屏障协调固定数目的线程不同,一个phaser能够协调不定数目的线程,这些线程可以在任何时候注册。为了实现这一功能,phaser使用了phase和phase值。
11. 类Phaser的int register()方法返回用于分类抵达的phase值。如果这个值是负数,该phaser就会终止,此时注册不产生任何影响。这个数值被称为抵达phase值。
12. 清单A-7展示了在第6章中被调用的PC应用程序。

清单A-7 基于信号量的生产者和消费者

  1. import java.util.concurrent.Semaphore;
  2. public class PC
  3. {
  4. public static void main(String[] args)
  5. {
  6. Shared s = new Shared();
  7. Semaphore semCon = new Semaphore(0);
  8. Semaphore semPro = new Semaphore(1);
  9. new Producer(s, semPro, semCon).start();
  10. new Consumer(s, semPro, semCon).start();
  11. }
  12. }
  13. class Shared
  14. {
  15. private char c;
  16. void setSharedChar(char c)
  17. {
  18. this.c = c;
  19. }
  20. char getSharedChar()
  21. {
  22. return c;
  23. }
  24. }
  25. class Producer extends Thread
  26. {
  27. private final Shared s;
  28. private final Semaphore semPro, semCon;
  29. Producer(Shared s, Semaphore semPro, Semaphore semCon)
  30. {
  31. this.s = s;
  32. this.semPro = semPro;
  33. this.semCon = semCon;
  34. }
  35. @Override
  36. public void run()
  37. {
  38. for (char ch = 'A'; ch <= 'Z'; ch++)
  39. {
  40. try
  41. {
  42. semPro.acquire();
  43. }
  44. catch (InterruptedException ie)
  45. {
  46. }
  47. s.setSharedChar(ch);
  48. System.out.println(ch + " produced by producer.");
  49. semCon.release();
  50. }
  51. }
  52. }
  53. class Consumer extends Thread
  54. {
  55. private final Shared s;
  56. private final Semaphore semPro, semCon;
  57. Consumer(Shared s, Semaphore semPro, Semaphore semCon)
  58. {
  59. this.s = s;
  60. this.semPro = semPro;
  61. this.semCon = semCon;
  62. }
  63. @Override
  64. public void run()
  65. {
  66. char ch;
  67. do
  68. {
  69. try
  70. {
  71. semCon.acquire();
  72. }
  73. catch (InterruptedException ie)
  74. {
  75. }
  76. ch = s.getSharedChar();
  77. System.out.println(ch + " consumed by consumer.");
  78. semPro.release();
  79. }
  80. while (ch != 'Z');
  81. }
  82. }

第7章:锁框架

  1. 锁就是实现了接口Lock的类的实例,它提供了比sychronized保留字更为弹性的锁操作。锁也能通过Condition对象支持等待/通知机制。
  2. 当线程进入临界区时,较之内置锁,Lock对象最大的好处就是具备退出请求锁的能力。
  3. 答案是true:当调用线程未持有锁时,ReentrantLock的unlock()方法就会抛出IllegalMonitorStateException。
  4. 你可以调用Lock的Condition newCondition()方法获取与特定Lock实例相关联的Condition实例。
  5. 答案是false:ReentrantReadWriteLock()方法创建了一个不带有公平顺序策略的ReentrantReadWriteLock。
  6. JDK 8引入的StampedLock是一种capability-based的锁,拥有三种控制读写访问的模式。它用类似于ReentrantReadWriteLock的方式区分了互斥和非互斥的锁,而且还提供了ReentrantReadWriteLock锁不支持的乐观读。
  7. LockSupport的目的在于为创建锁以及其他同步类提供基本的线程阻塞原语。
  8. 清单A-8展示了在第7章中的ID类。

清单A-8 基于ReentrantLock的ID生成器

  1. import java.util.concurrent.locks.ReentrantLock;
  2. public class ID
  3. {
  4. private static int counter; // initialized to 0 by default
  5. private final static ReentrantLock lock = new ReentrantLock();
  6. public static int getID()
  7. {
  8. lock.lock();
  9. try
  10. {
  11. int temp = counter + 1;
  12. try
  13. {
  14. Thread.sleep(1);
  15. }
  16. catch (InterruptedException ie)
  17. {
  18. }
  19. return counter = temp;
  20. }
  21. finally
  22. {
  23. lock.unlock();
  24. }
  25. }
  26. }

第8章:额外的并发工具

  1. 线程安全集合存在的两大问题是可能抛出ConcurrentModificationException以及糟糕的性能。因为在迭代集合的过程当中可能会有其它的线程修改集合,所以在迭代之前获取一个锁是很有必要的。如果没有获取锁并且集合遭到修改,那很可能引发ConcurrentModificationException。同样地,如果同步集合被多条线程频繁访问,性能就会很糟糕。
  2. 并发集合是具有并发性能和高扩展性面向集合的类型,它们位于java.util.concurrent包中。
  3. 弱一致性的迭代器具有以下属性:
    • 迭代开始之后,被删除但还没有通过迭代器的next()方法被返回的元素,就不会再被返回了。
    • 迭代开始之后被添加的元素可能会返回也可能不会返回。
    • 在集合迭代的过程中,即便对集合做了改变,也没有任何元素会被返回超过一次。
  4. BlockingQueue是接口java.util.Queue的子接口,它支持阻塞操作,即在获取一个元素之前,等待队列成为非空;在存储一个元素之前,等待队列中的空间变为可用。
  5. ConcurrentMap是java.util.Map的子接口,声明了额外的原子的方法putIfAbsent()、remove()、replace()。

  6. ArrayBlockingQueue是一个基于数组的有界阻塞队列。LinkedBlockingQueue是一个基于链接节点的非有界阻塞队列。

  7. 答案是true:面向并发的集合类型是集合框架的一部分。
  8. 类ConcurrentHashMap和HashMap在行为上别无二致,但是被设计用于无需显式同步就可以工作在多线程的上下文。
  9. 使用ConcurrentHashMap时,你会调用其putIfAbsent()方法去检查一个map是否包含指定的值。当这个值不存在的时候,不依赖外部的同步将它放进此map中。
  10. 原子变量封装了单个变量,并且支持在这个变量上进行无锁和线程安全的操作。如:AtomicInteger。
  11. 类AtomicIntegerArray描述了一个整型的数组,其元素可被原子更新。
  12. 答案是false:volatile并不支持原子的读-改-写的操作序列。
  13. compare-and-swap指令负责并发工具类承诺的性能提升。
  14. Fork/Join框架由特定的executor service和线程池构成。executor service可以运行任务,并且这个任务会被分解成较小的任务,它们从线程池中被fork(被不同的线程执行)出来,在join(即它的所有子任务都完成了)之前会一直等待。

  15. Fork/Join框架绝大部分由java.util.concurrent包中的ForkJoinPool、ForkJoinTask、ForkJoinWorkerThread、RecursiveAction、RecursiveTask以及CountedCompleter类组成。

  16. 为了完成有意义的工作,RecursiveAction的void compute()方法需要重写。
  17. 一个completion service就是java.util.concurrent.CompletionService接口的实现,用于从已完成任务(消费者)结果的消费中解耦新的异步任务(生产者)的生产。V是这个任务返回结果的类型。
  18. 你可以照下面的方式使用completion服务:通过调用CompletionService任意的submit()方法提交一个用于执行(通过工作线程)的任务。每个方法都会返回一个Future<V>实例代表该任务的未完成状态。之后你可以调用poll()方法来轮训这个任务的完成状态或者调用阻塞式的take()方法(译者注:获得结果)。消费者通过调用take()方法获取一个已完成的任务。这个方法在任务已经完成之前会一直阻塞。之后它会返回一个Future<V>对象来表示这一完成的任务,调用Future<V>的get()方法即可获取结果。
  19. 你可以通过completion服务并借助类ExecutorCompletionService<V>来执行任务,这个类实现了CompletionService<V>接口,同时提供executor用于支持任务执行。
  20. 和int total = ++counter;等价的原子变量如下:
  1. AtomicInteger counter = new AtomicInteger(0);
  2. int total = counter.incrementAndGet();

和int total = counter--;等价的原子变量如下:

  1. AtomicInteger counter = new AtomicInteger(0);
  2. int total = counter.getAndDecrement();
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注