许多 java 开发, 都是刚刚接触多线程开发. 但即使是有经验的开发, 也会陷入很多 多线程 的陷阱. 本篇内容, 基本上都是一些反例, 有些很低级但常见. 当你的程序没有得相应的期望, 希望本文能帮你了解到其中的微妙之处.
当然, 面试时拿来装逼用, 也是极好的.
先来 10 个.
我来评个级
玩命的创建线程池
现象: 系统资源耗尽, 进程僵死.
原因: 每次方法执行, 都 new 一个线程池. 代码示例.
小姐姐味道解决方式: 共用一个线程池即可.
作死等级: 五颗星
脑残等级: 五颗星
- void doJob(){
- ThreadPoolExecutor exe = new ThreadPoolExecutor(...);
- exe.submit(new Runnable(){...})
- }
现象: 某个线程一直持有锁而不释放, 造成锁泄漏.
原因: 未知异常或逻辑导致 unlock 函数未执行.
小姐姐味道解决方式: 始终将 unlock 函数放在 finally 中.
作死等级: 三颗星
脑残等级: 四颗星
- private final Lock lock = new ReentrantLock();
- void doJob(){
- try{
- lock.lock();
- //do. sth
- lock.unlock();
- }catch(Exception e){
- }
- }
忘记同步变量
现象: 在某个条件下, 抛出 IllegalMonitorStateException.
原因: 调用 wait,notify 等, 忘记 synchronized, 或者同步了错误的变量.
小姐姐味道解决方式: 调用这些函数之前, 要使用同步关键字同步它.
作死等级: 两颗星
脑残等级: 四颗星
- Object condition = new Object();
- condition.wait();
HashMap 死循环
现象: CPU 占用高, 发生死循环, 使用 jstack 查看是阻塞在 get 方法上.
原因: 在某种条件下, 进行 rehash 时, 会形成环形链. 某些 get 请求会走到这个环上.
小姐姐味道解决方式: 多线程环境下, 使用 ConcurrentHashMap, 别犹豫.
作死等级: 四颗星
脑残等级: 四颗星
给同步的变量重新赋值
现象: 不能够达到同步效果, 结果是错误的.
原因: 非基本类型被重新赋值, 会改变锁的指向, 不同线程持有的锁可能不一样.
小姐姐味道解决方式: 把锁对象声明为 final 类型.
作死等级: 四颗星
脑残等级: 三颗星
- List listeners = new ArrayList();
- void add(Listener listener, boolean upsert){
- synchronized(listeners){
- List results = new ArrayList();
- for(Listener ler:listeners){
- ...
- }
- listeners = results;
- }
- }
线程循环未捕获异常
现象: 线程作业无法继续运行, 不明终止.
原因: 未捕获循环中的异常, 造成线程退出.
小姐姐味道解决方式: 习惯性捕获所有异常.
作死等级: 三颗星
脑残等级: 三颗星
- volatile boolean run = true;
- void loop(){
- while(run){
- //do . sth
- int a = 1/0;
- }
- }
volatile 误作计数器
现象: 多线程计数结果有误.
原因: volatile 保证可见性, 不保证原子性, 多线程操作并不能保证其正确性.
小姐姐味道解决方式: 直接使用 Atomic 类.
作死等级: 三颗星
脑残等级: 两颗星
- volatile count = 0;
- void add(){
- ++count;
- }
错误保护范围
现象: 虽然使用了线程安全的集合, 但达不到同步效果.
原因: 操作要修改多个线程安全的集合, 但操作本身不是原子的.
小姐姐味道解决方式: 弄明白要保护的代码逻辑域.
作死等级: 三颗星
脑残等级: 四颗星
- private final ConcurrentHashMap<String,Integer> nameToNumber;
- private final ConcurrentHashMap<Integer,Salary> numberToSalary;
- public int geBonusFor(String name) {
- Integer serialNum = nameToNumber.get(name);
- Salary salary = numberToSalary.get(serialNum);
- return salary.getBonus();
- }
再比如下面的错误代码.
- Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());
- if(!map.containsKey("foo"))
- map.put("foo", "bar");
一些老的日期处理类
现象: 使用全局的 Calendar,SimpleDateFormat 等进行日期处理, 发生异常或者数据不准确.
原因: 这俩东西不是线程安全的, 并发调用会有问题.
小姐姐味道解决方式: 放在 ThreadLocal 中, 建议使用线程安全的 DateTimeFormatter.
作死等级: 三颗星
脑残等级: 三颗星
- SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
- Date dododo(String str){
- return format(str);
- }
代码死锁
现象: 代码产生死锁和相互等待.
原因: 代码满足了下面四个条件: 互斥; 不可剥夺; 请求和保持; 循环等待.
小姐姐味道解决方式: 破坏这四个条件. 或者少用同步.
作死等级: 两颗星
脑残等级: 一颗星
下面是一段简单的死锁代码.
- final Object lock1 = new Object();
- final Object lock2 = new Object();
- new Thread(new Runnable() {
- @Override
- public void run() {
- sleep(1000);
- synchronized (lock1) {
- synchronized (lock2) {
- }
- }
- }
- }).start();
- new Thread(new Runnable() {
- @Override
- public void run() {
- synchronized (lock2) {
- sleep(1000);
- synchronized (lock1) {
- }
- }
- }
- }).start();
long 变量读取无效值
现象: 会读取到非设置的值.
原因: long 变量读写不是原子的, 可能会读到 1 个变量的高 32 位和另一个变量的低 32 位字节.
小姐姐味道解决方式: 确保 long 和 double 变量的数据正确, 可以加上 volatile 关键字.
作死等级: 一颗星
脑残等级: 没有星
来源: http://www.tuicool.com/articles/Jveiu2m