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