java中如何应对读改写场景

166 阅读1分钟

前言

volatile可以确保数据及时刷新到主存,但是对于读改写场景还是无能为力

举个例子

public class ConcurrentHashMapExample {
	public static void main(String[] args) throws InterruptedException {
		Map<String, Long> ordersMap = new ConcurrentHashMap<>();
		ordersMap.put("Delhi", 0l);
		ordersMap.put("London", 0l);
		ordersMap.put("New York", 0l);
		ordersMap.put("Sydney", 0l);
		ExecutorService service = Executors.newFixedThreadPool(2);
		service.submit(() -> processOrders(ordersMap));
		service.submit(() -> processOrders(ordersMap));
		service.awaitTermination(3, TimeUnit.SECONDS);
		service.shutdown();
		System.out.println(ordersMap);
	}

	private static void processOrders(Map<String, Long> ordersMap) {
		for (String city : ordersMap.keySet()) {
			for (int i = 0; i < 50; i++) {
				Long oldOrder = ordersMap.get(city);
				ordersMap.put(city, oldOrder + 1);
			}
		}
	}
}

正确输出应该是

{Delhi=100, New York=100, London=100, Sydney=100}

但是试着多运行几遍,会就发现如下的情况

{Delhi=51, New York=73, London=71, Sydney=100}

在ConcurrentHashMap中value是用volatile修饰的,为什么还会出现这个情况呢?

 static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
}

探究原因

对于读改写操作,volatile并不能保证正确,需要使用原子类解决 以volatile的自增为例

volatile的读改写
volatile确保了读取到的是最新的值,符合语义,那么该如何解决上面的问题呢使用原子类

public class ConcurrentHashMapExample {
	public static void main(String[] args) throws InterruptedException {
		Map<String, AtomicLong> ordersMap = new ConcurrentHashMap<>();
		ordersMap.put("Delhi", new AtomicLong(0L));
		ordersMap.put("London", new AtomicLong(0L));
		ordersMap.put("New York", new AtomicLong(0L));
		ordersMap.put("Sydney", new AtomicLong(0L));
		ExecutorService service = Executors.newFixedThreadPool(2);
		service.submit(() -> processOrders(ordersMap));
		service.submit(() -> processOrders(ordersMap));
		service.awaitTermination(1, TimeUnit.SECONDS);
		service.shutdown();
		System.out.println(ordersMap);
	}

	private static void processOrders(Map<String, AtomicLong> ordersMap) {
		for (String city : ordersMap.keySet()) {
			for (int i = 0; i < 50; i++) {
				ordersMap.get(city).incrementAndGet();
			}
		}
	}
}

主要参考这篇文章