深入浅出 Java Concurrency (39): 并发总结 part 3 常见的并发陷阱
常见的并发陷阱
volatile
volatile只能强调数据的可见性,并不能保证原子操作和线程安全,因此volatile不是万能的。参考指令重排序
volatile最常见于下面两种场景。
a. 循环检测机制

while( ! done ){
dosomething();
}
b. 单例模型 (http://www.blogjava.net/xylz/archive/2009/12/18/306622.html)
synchronized/Lock
看起来Lock有更好的性能以及更灵活的控制,是否完全可以替换synchronized?
在锁的一些其它问题中说过,synchronized的性能随着JDK版本的升级会越来越高,而Lock优化的空间受限于CPU的性能,很有限。另外JDK内部的工具(线程转储)对synchronized是有一些支持的(方便发现死锁等),而对Lock是没有任何支持的。
也就说简单的逻辑使用synchronized完全没有问题,随着机器的性能的提高,这点开销是可以忽略的。而且从代码结构上讲是更简单的。简单就是美。
对于复杂的逻辑,如果涉及到读写锁、条件变量、更高的吞吐量以及更灵活、动态的用法,那么就可以考虑使用Lock。当然这里尤其需要注意Lock的正确用法。

lock.lock();
try{
//do something
}finally{
lock.unlock();
}
一定要将Lock的释放放入finally块中,否则一旦发生异常或者逻辑跳转,很有可能会导致锁没有释放,从而发生死锁。而且这种死锁是难以排查的。
如果需要synchronized无法做到的尝试锁机制,或者说担心发生死锁无法自恢复,那么使用tryLock()是一个比较明智的选择的。

if(lock.tryLock()){
try{
//do something
}finally{
lock.unlock();
}
}
甚至可以使用获取锁一段时间内超时的机制Lock.tryLock(long,TimeUnit)。 锁的使用可以参考前面文章的描述和建议。
锁的边界
一个流行的错误是这样的。
if(!map.containsKey(key)){
map.put(key,value);
}
看起来很合理的,对于一个线程安全的Map实现,要存取一个不重复的结果,先检测是否存在然后加入。 其实我们知道两个原子操作和在一起的指令序列不代表就是线程安全的。 割裂的多个原子操作放在一起在多线程的情况下就有可能发生错误。
实际上ConcurrentMap提供了putIfAbsent(K, V)的“原子操作”机制,这等价于下面的逻辑:
return map.get(key);
}else{
return map.put(k,v);
}
除了putIfAbsent还有replace(K, V)以及replace(K, V, V)两种机制来完成组合的操作。
提到Map,这里有一篇谈HashMap读写并发的问题。
构造函数启动线程
下面的实例是在构造函数中启动一个线程。
int x,y;
Thread thread;
public Runner(){
this.x=1;
this.y=2;
this.thread=new MyThread();
this.thread.start();
}
}
这里可能存在的陷阱是如果此类被继承,那么启动的线程可能无法正确读取子类的初始化操作。
因此一个简单的原则是,禁止在构造函数中启动线程,可以考虑但是提供一个方法来启动线程。如果非要这么做,最好将类设置为final,禁止继承。
丢失通知的问题
这篇文章里面提到过notify丢失通知的问题。
对于wait/notify/notifyAll以及await/singal/singalAll,如果不确定到底是否能够正确的收到消息,担心丢失通知,简单一点就是总是通知所有。
如果担心只收到一次消息,使用循环一直监听是不错的选择。
非常主用性能的系统,可能就需要区分到底是通知单个还是通知所有的挂起者。
线程数
并不是线程数越多越好,在下一篇文章里面会具体了解下性能和可伸缩性。 简单的说,线程数多少没有一个固定的结论,受限于CPU的内核数,IO的性能以及依赖的服务等等。因此选择一个合适的线程数有助于提高吞吐量。
对于CPU密集型应用,线程数和CPU的内核数一致有助于提高吞吐量,所有CPU都很繁忙,效率就很高。 对于IO密集型应用,线程数受限于IO的性能,某些时候单线程可能比多线程效率更高。但通常情况下适当提高线程数,有利于提高网络IO的效率,因为我们总是认为网络IO的效率比较低。
对于线程池而言,选择合适的线程数以及任务队列是提高线程池效率的手段。
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
对于线程池来说,如果任务总是有积压,那么可以适当提高corePoolSize大小;如果机器负载较低,那么可以适当提高maximumPoolSize的大小;任务队列不长的情况下减小keepAliveTime的时间有助于降低负载;另外任务队列的长度以及任务队列的拒绝策略也会对任务的处理有一些影响。
深入浅出 Java Concurrency (39): 并发总结 part 3 常见的并发陷阱的更多相关文章
- [转] 多线程 《深入浅出 Java Concurrency》目录
http://ifeve.com/java-concurrency-thread-directory/ synchronized使用的内置锁和ReentrantLock这种显式锁在java6以后性能没 ...
- 《深入浅出 Java Concurrency》目录
最近在学习J.U.C,看到一个大神 关于这个系列写的非常精辟,由于想做笔记,故系列转载并记录之. 原文:http://www.blogjava.net/xylz/archive/2010/07/08/ ...
- 深入浅出 Java Concurrency - 目录 [转]
这是一份完整的Java 并发整理笔记,记录了我最近几年学习Java并发的一些心得和体会. J.U.C 整体认识 原子操作 part 1 从AtomicInteger开始 原子操作 part 2 数组. ...
- 《深入浅出 Java Concurrency》—并发容器 ConcurrentMap
(转自:http://blog.csdn.net/fg2006/article/details/6404226) 在JDK 1.4以下只有Vector和Hashtable是线程安全的集合(也称并发容器 ...
- 深入浅出 Java Concurrency (38): 并发总结 part 2 常见的并发场景[转]
常见的并发场景 线程池 并发最常见用于线程池,显然使用线程池可以有效的提高吞吐量. 最常见.比较复杂一个场景是Web容器的线程池.Web容器使用线程池同步或者异步处理HTTP请求,同时这也可以有效的复 ...
- 深入浅出 Java Concurrency (18): 并发容器 part 3 ConcurrentMap (3)[转]
在上一篇中介绍了HashMap的原理,这一节是ConcurrentMap的最后一节,所以会完整的介绍ConcurrentHashMap的实现. ConcurrentHashMap原理 在读写锁章节部分 ...
- 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则
转: http://www.blogjava.net/xylz/archive/2010/07/03/325168.html 在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到 ...
- 深入浅出 Java Concurrency (15): 锁机制 part 10 锁的一些其它问题
主要谈谈锁的性能以及其它一些理论知识,内容主要的出处是<Java Concurrency in Practice>,结合自己的理解和实际应用对锁机制进行一个小小的总结. 首先需要强调的 ...
- 《深入浅出 Java Concurrency》——原子操作
part1 从AtomicInteger開始 从相对简单的Atomic入手(java.util.concurrent是基于Queue的并发包.而Queue.非常多情况下使用到了Atomic操作.因此首 ...
随机推荐
- Tomcat调优详解
前言 在这里告诫一下那些感觉自己啥都会的朋友们,其实你会的可能只是皮毛,不要感觉这个东西以前已经做过了,就不想去做了 其实你还远没有达到精通的地步,遇到以前做过的东西,也要用心的再去做一遍,你可能会从 ...
- 如何使用Spring管理Filter和Servlet
在使用spring容器的web应用中,业务对象间的依赖关系都可以用context.xml文件来配置,并且由spring容器来负责依赖对象 的创建.如果要在filter或者servlet中使用sprin ...
- 2018 年 -- 15 个有意思的 JavaScript 和 CSS 库
在Tutorialzine上你可以了解最新最酷的Web发展趋势.这就是为什么每个月都会发布一些偶然发现并认为值得你关注的最佳资源的缘由. Direction Reveal (方向展示) 该插件检测光标 ...
- D3.js(v3)+react 制作 一个带坐标与比例尺的柱形图 (V3版本)
现在用D3.js + react做一个带坐标轴和比例尺的柱形图.我已经尽力把代码全部注释上了,最后我也会把完整柱形图代码奉上.如果还有疑惑的,可以去翻看一下我之前介绍的方法,以下方法都有介绍到. 还有 ...
- 校园商铺-4店铺注册功能模块-8店铺注册之Controller层的改造
不合理的地方: 1. 并不需要将InputStream转换成File类型,直接将InputStream传进入交给CommonsMultipartfile去处理就可以了 如果做这样的转换,每次都需要生成 ...
- MySQL架构和索引
MySQL架构 逻辑架构图: 大概分为四层,这个见仁见义,有不同的分法: 第一层Connectors:处理不同语言与SQL的交互 第二层Connection Pool :连接池,管理缓存用户连接,线程 ...
- PHP缓存技术简单介绍
一.数据缓存 这里所说的数据缓存是指数据库查询缓存,每次访问页面的时候,都会先检测相应的缓存数据是否存在,如果不存在,就连接数据库,得到数据,并把查询结果序列化后保存到文件中,以后同样的查询结果就直接 ...
- PSCC2019常用基础操作
一.常用设置 1.界面设置(快捷键Ctrl+K):可以对PS界面的颜色.导出格式.性能等等进行设置(这里暂存盘建议设置D盘或F盘,默认C盘). 2.常用面板整理(菜单栏->窗口) 二.常用快捷键 ...
- php开发面试题---面试常用英语(你能介绍你自己吗?)
php开发面试题---面试常用英语(你能介绍你自己吗?) 一.总结 一句话总结: Could you please describe yourself? 1.为什么觉得自己适合这份工作? Why do ...
- skyline中大数据量的三维场景刷新速度问题
我们做了一个的类似于TE Pro的桌面系统来代替TE Pro演示我们的大三维场景.我们的三维场景包括100平方公里的全要素场景,有建筑物,地面.小品.部件.植被等.在系统运行后,三维场景刷不起来,速 ...