线程安全类错误使用示范

版权声明:本文为博主原创文章,转载时需要带上原文链接。 https://blog.csdn.net/a158123/article/details/84948046

前言

线程安全是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步操作:

  1. 读取map中为key的值
  2. 读取到的值自增(这里其实也发生了3步操作)
  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

猜你喜欢

转载自blog.csdn.net/a158123/article/details/84948046