ConcurrentHashMap是线程安全的概念已经深入人心,让我们在使用的时候有些大意了,我也懒得动脑子,直接使用,结果碰到钉子了.
这个问题让我很郁闷,程序逻辑全是对的,但是问题却明明摆在那边,最后怀疑是HashMap的问题。
1. package
2.
3. import
4. import
5. import
6.
7. import
8.
9. public class
10.
11. private static Map<Long, ServiceDO> widgetCacheMap = new
12. /**
13. * @param args
14. */
15. public static void
16. // TODO Auto-generated method stub
17. for(int i=0;i<10000;i++){
18. new Thread(new
19. tt.start();
20. }
21. }
22.
23. static class Rund implements
24.
25. public void
26. // TODO Auto-generated method stub
27. test();
28. }
29.
30. /**
31. * 1W次,总有那么几次线程不安全
32. */
33. public void
34. new
35. tt.set();
36. int
37. tt.change();
38. int
39. if(s1==s2){
40. ":"+s2);
41. }
42. }
43.
44. }
45.
46.
47.
48. public void
49. new
50. new
51. 1);
52. mm.put(1L, ss);
53. widgetCacheMap = mm;
54. }
55. public void
56. new
57. new
58. 2);
59. mm.put(1L, ss);
60. widgetCacheMap = mm;
61. }
62.
63. }
执行10000次,多执行几次,或许你会发现,真的一般情况下是线程安全的,但是在大量并发的时候,线程就变得不那么安全了.
输出结果如下:
1. 2:2
2. 2:2
3. 2:2
为什么出现这种情况,我在第一个地方设置值,然后取值,第二个地方再设置值,然后取值,两个值应该不同的,判断相同的时候,既然出现了。有人怀疑是ConcurrentHashMap,那你可以换成HashMap试试.结果一样.
为什么是2,2不是1,1;当然一般情况下是1:2,并发情况下就变成2,2了.
有人怀疑是初始化widgetCacheMap的问题,那么改代码如下:
1. public void
2. //Map mm= new HashMap();
3. new
4. 1);
5. widgetCacheMap.put(1L, ss);
6. //widgetCacheMap = mm;
7. }
8. public void
9. //Map mm= new HashMap();
10. new
11. 2);
12. widgetCacheMap.put(1L, ss);
13. //widgetCacheMap = mm;
14. }
真是不改不知道,一改吓一跳,这回出现刚才说的情况1,1
1. 1:1
2. 2:2
3. 2:2
4. 2:2
5. 2:2
而且改了之后其并发问题更严重了,因为这里每一次put都需要加行锁,其并发的概念也就上升了.
推荐写法还是按第一次方法,对象的覆盖是原子的,最好加一把锁,否则你第一次覆盖了,第二次又被别人覆盖了.
于是代码如下:
1. public void
2. synchronized
3.
4. new
5. new
6. 1);
7. mm.put(1L, ss);
8. widgetCacheMap = mm;
9.
10. }
11. }
12. public void
13. synchronized
14. new
15. new
16. 2);
17. mm.put(1L, ss);
18. widgetCacheMap = mm;
19. }
20.
21. }
保持widgetCacheMap的变更成原子状态。当然还会出现上面的情况,这是为什么呢。
因为每一个线程获取的时候,可能取的是原子1,也可能是原子2,如果在多线程获取的时候加一把锁,那么获取的就是原子X,但至少是一个原子,要么1,要么2.
于是代码如下:
1. public void
2. synchronized
3. new
4. tt.set();
5. int
6. tt.change();
7. int
8. if(s1==s2){
9. ":"+s2);
10. }
11. }
12. }
结果又出现如上现象,这是为什么呢,因为锁里面还加着锁,锁最好是原子化,尽量保持最小范围,不能价懒,像我一样就悲剧了.
1. /**
2. * 1W次,总有那么几次线程不安全
3. */
4. public void
5. new
6. tt.set();
7. int s1 = -1;
8. synchronized
9. s1 = widgetCacheMap.get(1L).getStatus();
10. }
11. tt.change();
12. int s2 = -2;
13. synchronized
14. s2 = widgetCacheMap.get(1L).getStatus();
15. }
16. if(s1==s2){
17. ":"+s2);
18. }
19. }
还是出现上面这种情况,通阅全码,发现每一次都是原子了,应该没问题了。
但是还需要考虑run方法是多线程的,只有一个线程进入test,那就算原子了.如下:
唉,这是为什么呢,syn不起作用?
开始怀疑,于是去掉所有的syn,只添加run方法中的如下:
1. /**
2. * 1W次,总有那么几次线程不安全
3. */
4. public void
5. synchronized
6. new
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. ":"+s2);
17. }
18. }
19. }
整个进行原子操作,结果让人晕死。还是出现在,最后想了想,原来Hash或者CurrentHashMap也一样,在中间change了一下,而syn锁定的是一个不变的东西。
于如改代码如下:
1. /**
2. * 1W次,总有那么几次线程不安全
3. */
4. public void
5. synchronized ("") {
6. new
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. ":"+s2);
17. }
18. }
19. }
这回你怎么执行都是原子操作了。
总结:ConcurrentHashMap是线程安全的,那是在他们的内部操作,其外部操作还是需要自己来保证其同步的,特别是静态的ConcurrentHashMap,其有更新和查询的过程,要保证其线程安全,需要syn一个不可变的参数才能保证其原子性