320 likes | 523 Views
Java并发编程的常见陷阱. boyan@taobao.com(伯岩). 不要把并发当成万能锤子. 多线程 != 性能提升. Amdahl定律. 。 如果F是必须 串行化执行的比重 ,那么Amdahl定律告诉我们,在一个N 处理器的机器中 , 我们最多可以加速 :. 正确使用读写锁. 读时不能写 写时不能读 可以并发读 不能并发写 读写比例高. 典型错误——LRUMap简单实现. 继承LinkedHashMap,enableLRU设置为true,覆写removeEldestEntry方法 , 使用读写锁同步 读时不能写 √ 写时不能读 √
E N D
Java并发编程的常见陷阱 boyan@taobao.com(伯岩)
不要把并发当成万能锤子 • 多线程 != 性能提升
Amdahl定律 • 。如果F是必须串行化执行的比重,那么Amdahl定律告诉我们,在一个N处理器的机器中 • ,我们最多可以加速:
正确使用读写锁 • 读时不能写 • 写时不能读 • 可以并发读 • 不能并发写 • 读写比例高
典型错误——LRUMap简单实现 • 继承LinkedHashMap,enableLRU设置为true,覆写removeEldestEntry方法, • 使用读写锁同步 • 读时不能写√ • 写时不能读√ • 可以并发读Ⅹ • 不能并发写 √ • 读写比例高 √
在构造函数中启动线程 • 继承带来的隐患
问题 • 假设A的构造函数中启动某个线程,该线程读取A中的实例变量i • B继承A,并在构造函数中重新给i赋值 • 问题: B的实例初始化,首先初始化父类A,启动线程,线程此时读取的i非B所期望。 • 解决: • 1、不允许继承——final • 2、单独的start方法(推荐)
正确使用wait/notify • 一个世界在等待
常见问题 • 代码: • 问题: • 未同步 • If替代while——虚假唤醒 • notify替代notifyAll——被遗忘的线程
Atomic+Atomic!=Atomic • MethodA is thread-safe • MethodBis thread-safe • 组合起来还是线程安全的吗? • public void methodC(){ • MethodA(); • MethodB(); • }
我要同步 • 容器是同步的,就没有问题了吗? • 同步的不是容器,而是寂寞
正确处理中断 • 将中断进行到底
Thread.interrupt()干什么了? • 设置中断状态 • 中断阻塞操作
How • wait,sleep,join都是nativemthod,直接抛出InterruptedException • InterruptibleChannel,关闭连接, • 抛出ClosedByInterruptException(AbstractInterruptibleChannel.java):
错误案例1 • 取消任务,取消不了?
错误案例2 • 吞掉中断,上层代码怎么办?
错误案例3 • 包装成Runtime异常?
取消任务的正确做法 • 没有阻塞操作, volatile状态变量 • 不响应中断的阻塞操作,如socket.read()之类,关闭socket。 • 响应中断的阻塞操作(如sleep,wait,join等),推荐状态变量+捕捉中断异常
处理InterruptedException • 除非你明确知道你在干什么,否则不要简单地catch并忽略 • 声明Check异常,继续抛出,交给他人处理 • 重设中断状态,让上层代码发现中断状态。
嫁错郎,加错锁 • 女怕嫁错郎
Don't • #1: Don’t synchronize on an object you’re changing • #2: Don’t synchronize on a String literal • #3: Don’t synchronize on auto-boxed values • #4: Don’t synchronize on null • #5: Don’t synchronize on a Lock object • #6: Don’t synchronize on getClass() • #7: Be careful locking on a thread-safe object with • encapsulated locking
错误案例1 • 只把杭州当汴州 • 问题: • foo是null? • 改变了foo指向的对象,加锁形同虚设
错误案例2 • 躲猫猫 • 问题: • Bar跟Foo的test方法中的锁不一样。
错误案例3 • 对同步的容器加锁,使用的锁与容器内部使用的锁不一定是同一个。 • HashTable、Vector√ • ConcurrentHashMap X
不一致的同步 • int的读写,问题不是太大
邂逅Map • 忘记我的另一半,不可原谅
阿甘,先别run • 美国犀利哥
Start Vs. Run • 哥只是爱跑步 • 问题: • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4533087 • 在jdk1.4.2以前,每个thread初始化都会加入ThreadGroup,如果你只是调用run而不是 • start,那么此thread将无法被正常回收造成内存泄漏。在JDK5之后,将加入ThreadGroup • 这一步从构造函数转移到start方法,因此不会有问题。
正确使用Volatile • Volatile能做什么? • 状态标识,如取消任务线程 • 安全发布,如修复DLC问题 • 开销较低的读写锁
正确使用Volatile • Volatile不能做什么? • 不能用于做计数器 • 与其他变量构成不变式
规避j.u.c的bug • LinkedBlockingQueue.poll(time,timeUnit) 内存泄漏,其他queue也有类似问题 • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6460501 • Semaphore.tryAcquire内存泄漏甚至hang住 • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6460501 • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6241823 • ReentrantReadWriteLock可能在没有任何线程持有锁的情况下被hang住,这是 • 一系列的BUG: • http://bugs.sun.com/view_bug.do?bug_id=6822370 • http://bugs.sun.com/view_bug.do?bug_id=6903249 • 可以说j.u.c在1.5这个版本上有非常多的bug,具体可以查看下sun的bug database • ,推荐升级jdk到最新稳定版。