`

ConcurrentHashMap并不是绝对线程安全的

阅读更多

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

Java代码  收藏代码
  1. package com.taobao.mmp.test;  
  2.   
  3. import java.util.HashMap;  
  4. import java.util.Map;  
  5. import java.util.concurrent.ConcurrentHashMap;  
  6.   
  7. import com.taobao.mmp.dataobject.ServiceDO;  
  8.   
  9. public class TTTT {  
  10.   
  11.     private static Map<Long, ServiceDO> widgetCacheMap = new ConcurrentHashMap<Long, ServiceDO>();  
  12.     /** 
  13.      * @param args 
  14.      */  
  15.     public static void main(String[] args) {  
  16.         // TODO Auto-generated method stub  
  17.         for(int i=0;i<10000;i++){  
  18.             Thread tt = new Thread(new Rund());  
  19.             tt.start();  
  20.         }  
  21.     }  
  22.   
  23.     static class Rund implements Runnable{  
  24.   
  25.         public void run() {  
  26.             // TODO Auto-generated method stub  
  27.             test();  
  28.         }  
  29.   
  30.         /** 
  31.          * 1W次,总有那么几次线程不安全 
  32.          */  
  33.         public void test(){  
  34.                 TTTT tt = new TTTT();  
  35.                 tt.set();  
  36.                 int s1 = widgetCacheMap.get(1L).getStatus();  
  37.                 tt.change();  
  38.                 int s2 = widgetCacheMap.get(1L).getStatus();  
  39.                 if(s1==s2){  
  40.                     System.out.println(s1+":"+s2);  
  41.                 }  
  42.         }  
  43.   
  44.     }  
  45.   
  46.   
  47.   
  48.     public void set() {  
  49.             Map mm= new HashMap();  
  50.             ServiceDO ss = new ServiceDO();  
  51.             ss.setStatus(1);  
  52.             mm.put(1L, ss);  
  53.             widgetCacheMap = mm;  
  54.     }  
  55.     public void change(){  
  56.             Map mm= new HashMap();  
  57.             ServiceDO ss = new ServiceDO();  
  58.             ss.setStatus(2);  
  59.             mm.put(1L, ss);  
  60.             widgetCacheMap = mm;  
  61.     }  
  62.   
  63. }  


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

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


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

Java代码  收藏代码
  1. public void 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. public void 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. public void set() {  
  2.     synchronized (widgetCacheMap) {  
  3.   
  4.         Map mm= new HashMap();  
  5.         ServiceDO ss = new ServiceDO();  
  6.         ss.setStatus(1);  
  7.         mm.put(1L, ss);  
  8.         widgetCacheMap = mm;  
  9.   
  10.     }  
  11. }  
  12. public void change(){  
  13.     synchronized (widgetCacheMap) {  
  14.         Map mm= new HashMap();  
  15.         ServiceDO ss = new ServiceDO();  
  16.         ss.setStatus(2);  
  17.         mm.put(1L, ss);  
  18.         widgetCacheMap = mm;  
  19.     }  
  20.   
  21. }  


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

Java代码  收藏代码
  1. public void 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. public void 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.     public   void test(){  
  5.         synchronized (widgetCacheMap) {  
  6.             TTTT tt = new TTTT();  
  7.             tt.set();  
  8.             int s1 = -1;  
  9.   
  10.               s1 = widgetCacheMap.get(1L).getStatus();  
  11.             tt.change();  
  12.             int s2 = -2;  
  13.               s2 = widgetCacheMap.get(1L).getStatus();  
  14.   
  15.             if(s1==s2){  
  16.                 System.out.println(s1+":"+s2);  
  17.             }  
  18.         }  
  19.     }  



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

Java代码  收藏代码
  1. /** 
  2.      * 1W次,总有那么几次线程不安全 
  3.      */  
  4.     public   void test(){  
  5.         synchronized ("") {  
  6.             TTTT tt = new TTTT();  
  7.             tt.set();  
  8.             int s1 = -1;  
  9.   
  10.               s1 = widgetCacheMap.get(1L).getStatus();  
  11.             tt.change();  
  12.             int s2 = -2;  
  13.               s2 = widgetCacheMap.get(1L).getStatus();  
  14.   
  15.             if(s1==s2){  
  16.                 System.out.println(s1+":"+s2);  
  17.             }  
  18.         }  
  19.     }  


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

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

分享到:
评论

相关推荐

    java多线程的讲解和实战

    4. **线程优先级与调度**:Java的`Thread`类提供了设置线程优先级的方法,如`setPriority(int priority)`,但实际线程调度依赖于操作系统的策略,优先级并不保证绝对的执行顺序。 5. **守护线程(Daemon)**:守护...

    java多线程设计

    4. 使用并发集合:Java并发包(java.util.concurrent)提供了线程安全的集合,如ConcurrentHashMap、CopyOnWriteArrayList等,它们内部实现了线程同步,无需额外的同步控制。 5. 原子操作(Atomic):AtomicInteger...

    Java多线程练习题

    1. 线程优先级:每个线程都有一个优先级,优先级高的线程更容易被调度执行,但并不保证绝对优先。 2.守护线程(Daemon Thread):后台运行的线程,当所有非守护线程结束时,守护线程也会自动结束。 六、线程池 Java...

    Java多线程的总结

    但是,优先级并不是绝对的,实际的线程调度还受到操作系统的制约,因此不应过度依赖线程优先级。 七、死锁 死锁是指两个或多个线程相互等待对方释放资源,导致都无法继续执行的状态。避免死锁的关键在于合理设计...

    精通java多线程

    Java的并发集合库(java.util.concurrent包)提供了线程安全的集合类,如ConcurrentHashMap、CopyOnWriteArrayList和ConcurrentLinkedQueue等,它们在多线程环境下能确保数据的一致性和安全性。 十、异常处理 在多...

    pb多线程实现的例程

    但在现代操作系统中,线程优先级并不总是能确保调度的绝对顺序。 通过学习和实践这个“pb多线程实现”的例程,开发者可以深入理解多线程编程的原理,掌握如何在实际项目中运用多线程提高程序效率,以及如何处理线程...

    马士兵多线程笔记.zip

    8. **并发集合**:Java的并发包(java.util.concurrent)提供了线程安全的集合,如ConcurrentHashMap、CopyOnWriteArrayList等,它们在内部实现了高效的并发控制。 9. **线程中断**:通过Thread.interrupt()方法...

    java线程基础(IBM教程)

    线程安全是指在多线程环境下,代码依然能正确执行。Java提供了多种线程安全的数据结构,如`ConcurrentHashMap`、`AtomicInteger`等,并提供了`synchronized`、`volatile`等关键字来保证线程可见性和数据一致性。 ...

    Java多线程的经典资料.rar

    6. **线程优先级**:Java的`Thread`类提供了设置线程优先级的方法,但实际的调度很大程度上依赖于操作系统的策略,因此优先级并不保证绝对的执行顺序。 7. **守护线程**:通过`setDaemon(true)`可以将线程标记为...

    Java多线程详解

    Java并发库提供了一些线程安全的集合,如ConcurrentHashMap、CopyOnWriteArrayList等,它们内部实现了锁或其他同步机制,可以在多线程环境下高效安全地使用。 八、线程间的通信 Java提供了wait()、notify()和...

    java 程序多线程设计课件

    9. **线程优先级**:Java线程有三个优先级:`MIN_PRIORITY`, `NORM_PRIORITY`, `MAX_PRIORITY`,但优先级并不保证绝对的执行顺序,因为线程调度由JVM决定。 10. **守护线程(Daemon Thread)**:守护线程不会阻止...

    多线程核心技术

    10. **并发集合类**:Java并发包(`java.util.concurrent`)提供了一系列线程安全的集合类,如`ConcurrentHashMap`、`CopyOnWriteArrayList`等,它们在多线程环境下性能更优。 11. **Future和Callable接口**:`Future...

    java线程入门教程,涉及线程基本知识,显浅易懂..zip

    但线程优先级并不保证绝对的执行顺序,而是影响调度策略。 5. **守护线程(Daemon)** 守护线程是为其他线程提供服务的线程,比如垃圾回收线程就是守护线程。当所有非守护线程结束时,程序会退出,即使还有守护...

    多线程学习

    5. **线程的优先级**:Java中的线程优先级范围是1(Thread.MIN_PRIORITY)到10(Thread.MAX_PRIORITY),但优先级并不绝对保证执行顺序,只是影响调度概率。 6. **线程的中断和异常**:通过调用interrupt()方法可...

    Java之多线程学习代码

    6. **线程优先级**:每个线程都有一个优先级,较高的优先级线程可能比低优先级线程优先获得CPU执行时间,但Java线程的优先级并不保证绝对的执行顺序。 7. **守护线程(Daemon Thread)**:守护线程不会阻止程序的...

    Java线程与多线程教程Java开发Java经验技巧共4页

    - **线程安全的集合**:如`ConcurrentHashMap`, `CopyOnWriteArrayList`, `BlockingQueue`等,它们内部实现了线程同步机制,避免了数据竞争问题。 7. **死锁、活锁与饥饿** - **死锁**:两个或多个线程互相等待...

    Java多线程实例程序

    Java线程具有优先级,可以影响调度策略,但并不保证绝对的执行顺序。优先级较高的线程可能会比优先级较低的线程先执行,但不总是如此。 9. **线程安全的类** Java提供了一些线程安全的集合类,如`...

    java多线程.rar

    但优先级并不保证绝对的执行顺序,具体取决于JVM和操作系统。 6. **线程中断**: `Thread.interrupt()`方法用于中断线程,线程可通过检查`Thread.currentThread().isInterrupted()`或`Thread.interrupted()`来响应...

    15道面试常问的Java多线程面试题!.zip

    优先级高的线程更容易获得执行机会,但并不保证绝对优先执行。 4. **同步机制** - synchronized关键字:用于控制对共享资源的访问,可以修饰方法或代码块。 - volatile关键字:保证变量在多线程环境中的可见性和...

    Java多线程学习资料

    9. **线程中断**:`Thread.interrupt()`方法用于中断线程,但并不能立即停止线程,而是设置一个中断标志。线程需在运行中检查`isInterrupted()`或`interrupted()`方法,以响应中断请求。 10. **死锁(Deadlock)**...

Global site tag (gtag.js) - Google Analytics