ConcurrentHashMap并不是绝对线程安全的

ConcurrentHashMap并不是绝对线程安全的

  • JDK

   ConcurrentHashMap是线程安全的概念已经深入人心,让我们在使用的时候有些大意了,我也懒得动脑子,直接使用,结果碰到钉子了.
这个问题让我很郁闷,程序逻辑全是对的,但是问题却明明摆在那边,最后怀疑是HashMap的问题。
Java代码   收藏代码
  1. package com.taobao.mmp.test;  

  2. import java.util.HashMap;  

  3. import java.util.Map;  

  4. import java.util.concurrent.ConcurrentHashMap;  

  5. import com.taobao.mmp.dataobject.ServiceDO;  

  6. publicclass TTTT {  

  7. privatestatic Map<Long, ServiceDO> widgetCacheMap = new ConcurrentHashMap<Long, ServiceDO>();  

  8. /**

  9.     * @param args

  10.     */

  11. publicstaticvoid main(String[] args) {  

  12. // TODO Auto-generated method stub

  13. for(int i=0;i<10000;i++){  

  14.            Thread tt = new Thread(new Rund());  

  15.            tt.start();  

  16.        }  

  17.    }  

  18. staticclass Rund implements Runnable{  

  19. publicvoid run() {  

  20. // TODO Auto-generated method stub

  21.            test();  

  22.        }  

  23. /**

  24.         * 1W次,总有那么几次线程不安全

  25.         */

  26. publicvoid test(){  

  27.                TTTT tt = new TTTT();  

  28.                tt.set();  

  29. int s1 = widgetCacheMap.get(1L).getStatus();  

  30.                tt.change();  

  31. int s2 = widgetCacheMap.get(1L).getStatus();  

  32. if(s1==s2){  

  33.                    System.out.println(s1+":"+s2);  

  34.                }  

  35.        }  

  36.    }  

  37. publicvoid set() {  

  38.            Map mm= new HashMap();  

  39.            ServiceDO ss = new ServiceDO();  

  40.            ss.setStatus(1);  

  41.            mm.put(1L, ss);  

  42.            widgetCacheMap = mm;  

  43.    }  

  44. publicvoid change(){  

  45.            Map mm= new HashMap();  

  46.            ServiceDO ss = new ServiceDO();  

  47.            ss.setStatus(2);  

  48.            mm.put(1L, ss);  

  49.            widgetCacheMap = mm;  

  50.    }  

  51. }  


执行10000次,多执行几次,或许你会发现,真的一般情况下是线程安全的,但是在大量并发的时候,线程就变得不那么安全了.
输出结果如下:
Java代码   收藏代码
  1. 2:2

  2. 2:2

  3. 2:2


为什么出现这种情况,我在第一个地方设置值,然后取值,第二个地方再设置值,然后取值,两个值应该不同的,判断相同的时候,既然出现了。有人怀疑是ConcurrentHashMap,那你可以换成HashMap试试.结果一样.
为什么是2,2不是1,1;当然一般情况下是1:2,并发情况下就变成2,2了.
有人怀疑是初始化widgetCacheMap的问题,那么改代码如下:
Java代码   收藏代码
  1. publicvoid set() {  

  2. //Map mm= new HashMap();

  3.        ServiceDO ss = new ServiceDO();  

  4.        ss.setStatus(1);  

  5.        widgetCacheMap.put(1L, ss);  

  6. //widgetCacheMap = mm;

  7. }  

  8. publicvoid change(){  

  9. //Map mm= new HashMap();

  10.        ServiceDO ss = new ServiceDO();  

  11.        ss.setStatus(2);  

  12.        widgetCacheMap.put(1L, ss);  

  13. //widgetCacheMap = mm;

  14. }  


真是不改不知道,一改吓一跳,这回出现刚才说的情况1,1

Java代码   收藏代码
  1. 1:1

  2. 2:2

  3. 2:2

  4. 2:2

  5. 2:2


而且改了之后其并发问题更严重了,因为这里每一次put都需要加行锁,其并发的概念也就上升了.
推荐写法还是按第一次方法,对象的覆盖是原子的,最好加一把锁,否则你第一次覆盖了,第二次又被别人覆盖了.
于是代码如下:
Java代码   收藏代码
  1. publicvoid set() {  

  2. synchronized (widgetCacheMap) {  

  3.        Map mm= new HashMap();  

  4.        ServiceDO ss = new ServiceDO();  

  5.        ss.setStatus(1);  

  6.        mm.put(1L, ss);  

  7.        widgetCacheMap = mm;  

  8.    }  

  9. }  

  10. publicvoid change(){  

  11. synchronized (widgetCacheMap) {  

  12.        Map mm= new HashMap();  

  13.        ServiceDO ss = new ServiceDO();  

  14.        ss.setStatus(2);  

  15.        mm.put(1L, ss);  

  16.        widgetCacheMap = mm;  

  17.    }  

  18. }  


保持widgetCacheMap的变更成原子状态。当然还会出现上面的情况,这是为什么呢。
因为每一个线程获取的时候,可能取的是原子1,也可能是原子2,如果在多线程获取的时候加一把锁,那么获取的就是原子X,但至少是一个原子,要么1,要么2.
于是代码如下:
Java代码   收藏代码
  1. publicvoid test(){  

  2. synchronized (widgetCacheMap) {  

  3.            TTTT tt = new TTTT();  

  4.            tt.set();  

  5. int s1 = widgetCacheMap.get(1L).getStatus();  

  6.            tt.change();  

  7. int s2 = widgetCacheMap.get(1L).getStatus();  

  8. if(s1==s2){  

  9.                System.out.println(s1+":"+s2);  

  10.            }  

  11.        }  

  12.    }  



结果又出现如上现象,这是为什么呢,因为锁里面还加着锁,锁最好是原子化,尽量保持最小范围,不能价懒,像我一样就悲剧了.


Java代码   收藏代码
  1. /**

  2. * 1W次,总有那么几次线程不安全

  3. */

  4. publicvoid test(){  

  5.        TTTT tt = new TTTT();  

  6.        tt.set();  

  7. int s1 = -1;  

  8. synchronized (widgetCacheMap) {  

  9.          s1 = widgetCacheMap.get(1L).getStatus();  

  10.        }  

  11.        tt.change();  

  12. int s2 = -2;  

  13. synchronized (widgetCacheMap) {  

  14.          s2 = widgetCacheMap.get(1L).getStatus();  

  15.        }  

  16. if(s1==s2){  

  17.            System.out.println(s1+":"+s2);  

  18.        }  

  19. }  


还是出现上面这种情况,通阅全码,发现每一次都是原子了,应该没问题了。
但是还需要考虑run方法是多线程的,只有一个线程进入test,那就算原子了.如下:
唉,这是为什么呢,syn不起作用?
开始怀疑,于是去掉所有的syn,只添加run方法中的如下:
Java代码   收藏代码
  1. /**

  2.     * 1W次,总有那么几次线程不安全

  3.     */

  4. publicvoid test(){  

  5. synchronized (widgetCacheMap) {  

  6.            TTTT tt = new TTTT();  

  7.            tt.set();  

  8. int s1 = -1;  

  9.              s1 = widgetCacheMap.get(1L).getStatus();  

  10.            tt.change();  

  11. int s2 = -2;  

  12.              s2 = widgetCacheMap.get(1L).getStatus();  

  13. if(s1==s2){  

  14.                System.out.println(s1+":"+s2);  

  15.            }  

  16.        }  

  17.    }  



整个进行原子操作,结果让人晕死。还是出现在,最后想了想,原来Hash或者CurrentHashMap也一样,在中间change了一下,而syn锁定的是一个不变的东西。
于如改代码如下:

Java代码   收藏代码
  1. /**

  2.     * 1W次,总有那么几次线程不安全

  3.     */

  4. publicvoid test(){  

  5. synchronized ("") {  

  6.            TTTT tt = new TTTT();  

  7.            tt.set();  

  8. int s1 = -1;  

  9.              s1 = widgetCacheMap.get(1L).getStatus();  

  10.            tt.change();  

  11. int s2 = -2;  

  12.              s2 = widgetCacheMap.get(1L).getStatus();  

  13. if(s1==s2){  

  14.                System.out.println(s1+":"+s2);  

  15.            }  

  16.        }  

  17.    }  


这回你怎么执行都是原子操作了。

总结:ConcurrentHashMap是线程安全的,那是在他们的内部操作,其外部操作还是需要自己来保证其同步的,特别是静态的ConcurrentHashMap,其有更新和查询的过程,要保证其线程安全,需要syn一个不可变的参数才能保证其原子性  


你可能感兴趣的:(线程安全)