java并发编程实战读书笔记

并发编程实际上极其复杂,有很多绝对值得去看的建议。

  1. 不要在构造器里面调用可变函数(虚函数),这是所谓的安全构造需求;
  2. 最简单的线程安全类是不可变类,不可变类有如下硬性要求:
    • 对象创建以后就不能修改
    • 对象所有域都是final
    • 对象是安全构造的
  3. 所谓可见性指的是a线程修改了变量,b线程可以知道这种修改;所谓原子性指的是a线程在修改变量时,b线程不会干扰这种修改;
  4. 所谓安全发布,必须满足以下条件之一:
    • 在静态初始化函数中初始化一个对象引用
    • 将对象的引用保存到volatile类型或者AtomicReferance对象之中
    • 将对象的应用保存到某个正确构造对象的final域中
    • 将对象引用保存到一个由锁保护的域中
  5. volatile变量只能保证可见性,无法保证原子性;加锁则都可以保证(但是会繁琐一些)。
  6. 不要在构造器中调用新的线程,这会导致不安全发布;正确的做法是使用静态工厂函数,构造完毕后再启动线程(或者做其他发布);
  7. 最简单维持线程安全的方法是使用局部变量(栈封闭),其次是使用原子类,包括现场本地存储类ThreadLocal<>
  8. 大多数情况下,我们无须直接使用Thread这种基础类,java提供了很多适用范围广泛的组件,包括:
  9. 并发容器类,使用java.util.concurrent中定义的包括ConcurrentHashMap, CopyOnWriteArrayListConcurrentLinkedQueueBlockingQueueBlockingDeque),以及ConcurrentSkipListMapConcurrentSkipListSet(作为SortedMapSortedSet的替代品).BlockingQueuetakeput时,如果没有元素/空间就会阻塞;
  10. 如果线程被中断,会抛出InterruptedException如果是Runnable的对象,是不可以直接抛出这个异常的,必须捕获并使用Thread.currentThread().interrupt()恢复中断;其他类型的对象倒是无所谓,可以直接抛出或者捕获清理后重新抛出;
  11. 闭锁。CountDownLatch可以用来等待计数,FutureTask是一种Callable,可以通过get来获得结果(或者阻塞),Semaphore用来对资源进行限制计数,可以用来实现资源池,使用acquire获得一个资源,使用release释放一个资源,Barrier用来实现分段任务,当所有任务都到达Barrier时,任务继续,否则抛出BrokenBarrierException异常。本章给出了一个缓存的例子。
  12. Executor框架。Executor是一个接口,只规定了execute一个接口(其参数是一个Runnable class);Executors包含生产线程池的几个静态方法,如newFixedThreadPool(固定大小),newCachedThreadPool(无限增长)和newSingleThreadExecutor(单线程串行),和newScheduledThreadPool(定时执行)。如果这些都不能满足需求,可以直接使用ThreadPoolExecutor自己灵活构造线程池
  13. ExecutorService接口在Executor接口的基础上增加了生命周期,可以使用shutdownshutdownNowawaitTermination等方法进行生命周期(运行、关闭和已终止)控制和感知。
  14. ExecutorServicesubmit方法用于提交一个任务并返回一个Future,使用这个结果可以控制任务的生命周期;
  15. 线程的超时:一般可以用重载的getawait等方法指定超时时间;
  16. 线程的取消:习惯上使用一个volatile变量作为取消标志,但是如果线程阻塞了,这个方法就不好用了。最佳方式是使用中断(Future.cancel),注意需要处理好相关的异常;
  17. 可以通过继承Thread重载interrupt方法来将中断处理封闭在异常类中;可以通过使用ThreadPoolExecutor中的newTaskFor静态方法由Callable生产RunnableFuture