前言
线程安全是Java
面试中的常客,而在Java中有一些类本身是线程安全的,这些类就是线程安全类,例如ConcurrentHashMap
。但是有时候错误地使用线程安全类反而会出现线程不安全的情况。例如下面的例子
错误示范
/**
* 关于线程安全类的错误使用示范
* @author RJH
* create at 2018/12/10
*/
public class ThreadUnsafeDemo {
/**
* 累加线程类
*/
private static class UnsafeThread implements Runnable{
/**
* 共享的数据
*/
private Map<String,Integer> map;
/**
* 累计的key
*/
private String key;
/**
* 线程执行次数
*/
private AtomicInteger times;
public UnsafeThread(Map<String, Integer> map, String key,AtomicInteger times) {
this.map = map;
this.key = key;
this.times=times;
}
@Override
public void run() {
if(map.containsKey(key)){//包含特定的key就累加
map.put(key,map.get(key)+1);
}else{
map.put(key,1);
}
//累计执行次数
times.incrementAndGet();
}
}
/**
* 线程安全类ConcurrentHashMap,表示共享的数据
*/
private static Map<String,Integer> map=new ConcurrentHashMap<>();
/**
* 线程执行次数
*/
private static AtomicInteger times=new AtomicInteger(0);
public static void main(String[] args) {
String key="test";
//初始化线程池
ExecutorService executor=Executors.newFixedThreadPool(10);
for(int i=0;i<100;i++){//执行100次
Runnable job=new UnsafeThread(map,key,times);
executor.execute(job);
}
//线程终止
executor.shutdown();
//死循环等待线程池中所有任务执行完毕
while (!executor.isShutdown()){
}
//数据累加后的值
System.out.println("total:"+map.get(key));
//执行次数
System.out.println("times:"+times.get());
}
}
执行结果
total:93
times:100
分析
出现这样的结果主要是因为在对map
累加的过程中,出现了非原子性操作:
map.put(key,map.get(key)+1);
这段代码其实对map
进行了3步操作:
- 读取
map
中为key
的值 - 读取到的值自增(这里其实也发生了3步操作)
- 将自增后的值存储到
map
而线程安全类的线程安全其实指的是类中的每一个方法是线程安全的。
解决办法
可以使用synchronized
来确保这3步操作的原子性,只需要修改run()
方法即可
@Override
public void run() {
synchronized (map){
if(map.containsKey(key)){//包含特定的key就累加
map.put(key,map.get(key)+1);
}else{
map.put(key,1);
}
}
//累计执行次数
times.incrementAndGet();
}
修改后执行结果
total:100
times:100