最近抽出了点时间,把java并发编程的一些概念和策略总结了一下:
1. 同一个程序中的多个线程可以被同时调度到多个CPU上(利用这一点通常能提高cpu的使用率)
2. 多线程运用的例子:RMI、Servlet、GUI(通常情况下:GUI对象都只能在实事件线程中访问且GUI的对象是被封闭在 单个线程当中)、Timer等
3. 只有当类中仅包含自己的状态时,线程安全类才是有意义的
4. 在一定条件下,可以安全地放宽状态变量的封装性
5. 同步术语:sychronized、volatile类型的变量、显式锁、原子变量(concurrent包中)
6. 最常见的竞态条件:先检查后执行(check-and-act),读取--修改--写入
7. 内置锁:可重入(可重复获取相同的锁)
8. 在构造方法中可以创建线程,但最好不要启动线程,以防止启动的线程访问未完全构造的this引用
9. 构造方法中调用非private和final方法(即有可能被复写的方法)时,也会导致this引用逸出
10. 线程封闭的三种方式:Ad-hoc线程封闭、栈封闭、ThreadLocal类
11. final域(引用不指向可变对象的情况下)是线程安全的
12. 安全发布对象的常用模式:
①. 在静态初始化函数中初始化一个对象引用
②. 将对象保存到某个正确构造对象的final类型域中
③. 将对象的引用保存到volatile类型的域或者AtomicReference对象中
④. 将对象的引用保存到一个由锁保护的域中
13. 在并发编程中使用和共享对象时的策略:
①. 线程封闭
②. 只读共享
③. 保护对象(持有特定的锁)
④. 线程安全共享(线程安全的对象提供的公共访问接口)
14. 在现有的安全类中添加功能:
①. 通过继承扩展
②. 通过辅助类(客户端加锁机制)
③. 通过类的组合
15. 特定情况可以考虑并发容器替代同步容器
同步容器:将所有的对容器状态的访问都串行化,以实现线程安全(如:Vector)
并发容器:通过快照(如:CopyOnWriteArrayList)、分段锁(如:ConcurrentHashMap)等方式实现线程安全
16. 利用BlockingQueue实现“生产者——消费者”模式;利用BlockingDeque实现“工作密取”模式
17. 中断线程的一般处理方法:
第一种:run方法中无循环体
public void run(){
If(Thread.isInterrupted()){
return;
}
}
第二种:run方法中有循环体
public void run(){
for(;;;){
If(Thread.isInterrupted()){
break;
}
}
}
第三种:抛出中断异常结束程序
public void doEverIfNoException() throws InterruptedException{
If(Thread.isInterrupted()){
throw new InterruptedException();
}
}
在执行currentThread.interrupt之后,也可以通过调用Thread.sleep或者obj.wait或者currentThread.join使线程抛出中断异常后结束,但JVM并不能保证阻塞方法检测到中断的速度(通常情况下还是非常快的)。
第四种:利用一个标志位来控制程序
public void run(){
valotile isCancled = false;
while(!isCancled){
doSomething();
}
}
18. 一些常用的同步工具类:闭锁(如:CountDownLatch, FutureTask),信号量(如:Semaphore),栅栏(如:CyclicBarrier, Exchanger)
19. 在使用Thread的地方,考虑如果使用Executor是否能获得更好的效果
20. 通常用ScheduledThreadPoolExecutor来替代Timer来执行延迟任务与周期任务
21. 递归算法的并行化处理(每一次递归都不存在依赖关系时)
22. 注意通过控制获取“锁”的顺序的一致性来避免“死锁”,也可以尝试使用“定时锁”,同时要注意不要相信“线程的优先级”(因为它对平台具有依赖性),最后不要忘记防止“活锁”(在一个线程内重复执行某一操作,使线程无法继续向下执行)
23. 在试图提高并发程序的性能时,要注意程序的“可扩展性”和“安全性”,并以测试为基准,不要猜想
24. 线程引入的开销:上下文切换、内存同步(如:通过刷新缓存是缓存无效)、阻塞(Spin-waiting方式,操作系统挂起被阻塞线程)
注:阻塞导致线程在其执行的时间片还未用完之前就被交换出去,而在随后当要获取的锁或者其他资源可用时,又被切换回来
25. 减少锁的竞争3种方式:
①. 减少锁的持有时间(如:缩小锁的范围)
②. 降低锁的请求频率(如:减小锁的粒度,但采用此种方法应该考虑上下文切换频率增高带来的额外开销)
③. 使用带有协调机制的锁(如:定时锁)
26. 在激烈竞争情况下,ReentrantLock非公平锁性能高于其公平锁,原因:在恢复一个被挂起的线程与该线程正在开始运行之间存在着严重的延迟
27. ReentrantLock的特性包括:可定时、可轮询、可中断、公平队列、非块结构
28. ReentrantReadWriteLock支持多个线程同时读取,但支持一个线程写入,写锁可以降级为读锁,读锁不能升级为写锁(读锁升级为写锁可能导致死锁)
29. 必要时可以构造自己的同步工具(大多数的开发者这一生都不需要这么做,原因你懂得!)
30 显示的Condition对象,不知大家用过没有?可以用来控制“多生产者---多消费者”模型
31. CAS------原子变量-------锁,这些在不同场景下的性能可以好好分析比较一下
32. 双重检查的罪恶:
public class DoubleCheckedLocking {
private static Resource resource;
public static Resource getInstance() {
if (resource == null) { //此处可能导致获到不完整的对象
synchronized (DoubleCheckedLocking.class) {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
static class Resource {
}
}
双重检查的替代方案:延迟初始化占位类模式
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
}
public static Resource getResource() {
return ResourceFactory.ResourceHolder.resource;
}
static class Resource {
}
}
33. Java内存模型的Happens-Before规则(有精力的看看吧,但实际应用中通常还是借助同步来控制操作的顺序)
来源:oschina
链接:https://my.oschina.net/u/248570/blog/73236