V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
flowaters
V2EX  ›  Java

Java 多线程同步问题求助

  •  
  •   flowaters · 2019-06-07 23:18:44 +08:00 · 2815 次点击
    这是一个创建于 2026 天前的主题,其中的信息可能已经有所发展或是发生改变。

    想用 wait/notify 和 map 来实现一个线程写入,另一个线程随后读出的同步处理。

    但是遇到了 happens-before 问题。

    一个可以复现的代码如下:

    import java.time.LocalDateTime;
    import java.util.Map;
    import java.util.UUID;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class MainConcurrentHashmapTest {
    
    	private static ExecutorService executorService = Executors.newFixedThreadPool(2);
    
    	public static void main(String[] args) {
    
    		for (int i = 0; i != 100000; ++i) {
    			test();
    		}
    
    		executorService.shutdown();
    	}
    
    	public static void test() {
    
    		final Map<String, Object> map = new ConcurrentHashMap<String, Object>();
    
    		final Object mutex = new Object();
    		final String key = UUID.randomUUID().toString();
    
    		Runnable consumer = new Runnable() {
    
    			@Override
    			public void run() {
    				synchronized (mutex) {
    					try {
    						mutex.wait(10000L);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					Object value = map.remove(key);
    					if (value == null) {
    						System.out.println("got null value for key = " + key);
    					}
    				}
    			}
    		};
    
    		Runnable producer = new Runnable() {
    
    			@Override
    			public void run() {
    				map.put(key, LocalDateTime.now().toString());
    				synchronized (mutex) {
    					mutex.notify();
    				}
    			}
    		};
    
    		executorService.submit(consumer);
    		executorService.submit(producer);
    	}
    
    }
    

    运行的时候会发现,有时候得不到最新的 value 值,如下:

    got null value for key = f972a078-c314-4cd1-a5e4-d3ce11195a7e
    got null value for key = 6ef9476c-8ded-43ec-900e-5f859eeaec1e
    got null value for key = 8ecff482-eb9a-4e63-8eee-c950a485fbb0
    

    为什么呢?有什么改进建议么?

    7 条回复    2019-06-09 01:35:37 +08:00
    chendy
        1
    chendy  
       2019-06-07 23:45:10 +08:00
    线程池执行任务的顺序不一定是提交的顺序,consumer 的方法可能会在 producer 之前执行,所以会取到 null
    momocraft
        2
    momocraft  
       2019-06-07 23:51:13 +08:00
    沒有東西保證 consumer 比 producer “隨後” 獲得鎖

    工具就比較多了 比如 CountdownLatch
    springmarker
        3
    springmarker  
       2019-06-08 00:22:26 +08:00
    BlockingQueue 不就好了
    mejee
        4
    mejee  
       2019-06-08 01:16:14 +08:00 via Android
    楼上说的对,还有一个问题,根据代码,不停地 submit 这两个 runable,很可能在某一次执行中,producer 先 notify,这就导致 consumer 会 wait 10000 ms,造成程序卡顿。还有,你每次 test 方法运行,用的是不同的 mutex,根据前面的情况,这就可能导致两个 consumer 同时霸占两个线程,直到 wait 等待超时,造成程序卡顿,幸好你 wait 带了个时间参数,否则你运行这么多次的 test 方法,非常有可能这个线程池停在那,导致你程序一直不执行下去
    NewDraw
        5
    NewDraw  
       2019-06-08 08:49:30 +08:00 via Android
    你的需求是阻塞队列。
    johnniang
        6
    johnniang  
       2019-06-08 13:23:31 +08:00 via Android
    建议先看懂#生产者消费者模型#。
    dailiha01sy
        7
    dailiha01sy  
       2019-06-09 01:35:37 +08:00
    用 Exchange
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1050 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 19:36 · PVG 03:36 · LAX 11:36 · JFK 14:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.