lvxingzhe 2019-11-04
除了操作原子性之外,还有一个比较容易引起线程不安全的原因:安全方法组合。使用多个线程安全的方法组合成一个方法,也有可能导致线程不安全的情况出现。
以ConcurrentHashMap类为例,ConcurrentHashMap是一个高并发高性能的map实现类,他的每个方法都是线程安全的。
下面是示例代码:
public class TestTwoAtomicMethods { private final ConcurrentHashMap<Integer,Integer> map = new ConcurrentHashMap<Integer,Integer>(); @Interleave public void update() { Integer result = map.get(1); if( result == null ) { map.put(1, 1); } else { map.put(1, result + 1 ); } } @Test public void testUpdate() throws InterruptedException { Thread first = new Thread( () -> { update(); } ); Thread second = new Thread( () -> { update(); } ); first.start(); second.start(); first.join(); second.join(); } @After public void checkResult() { assertEquals( 2 , map.get(1).intValue() ); } }
下面是控制台打印信息:
至于为什么会这样的,原因是因为在代理第5行执行完之后,在下面复制的判断过程中依然存在着多个线程同时进去if-else判断的可能性,借助vmlens这个插件,能够很明显看到原因,图如下:
图中可以看到在执行ConcurrentHashMap的原子操作get和put方法时候,出现了线程间的竞争,13和14线程分别先获取到了对象的锁,然后取得了map.get(1)的值,此时值为null,两个线程的取值都是null,剩下的就比较明了了。两个线程都进入了if-else判断的第一个条件语句中,又先后复制map.put(1,1),这样最终的结果map.get(1).intValue()就等于1,断言失败。