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