使用volatile来确保Java中共享(但不是并发)数据的可见性

时间:2014-04-22 13:20:40

标签: java multithreading concurrency volatile memory-visibility

我试图实现LZ77的快速版本,我有一个问题要问你关于并发编程的问题。

现在我的final byte[] bufferfinal int[] resultHolder都有相同的长度。该计划执行以下操作:

  1. 主线程写入所有缓冲区,然后通知线程并等待它们完成。

  2. 单个工作线程处理缓冲区的一部分,在结果持有者的同一部分中保存结果。工人的部分是独家的。之后,通知主线程并且工作人员暂停。

  3. 当所有工作人员暂停时,主线程读取resultHolder中的数据并更新缓冲区,然后(如果需要)该过程从第1点开始。

  4. manager(主线程)中的重要事项声明如下:

    final byte[] buffer = new byte[SIZE];
    final MemoryHelper memoryHelper = new MemoryHelper(); 
    final ArrayBlockingQueue<Object> waitBuffer = new ArrayBlockingQueue<Object>(TOT_WORKERS);
    final ArrayBlockingQueue<Object> waitResult = new ArrayBlockingQueue<Object>(TOT_WORKERS);
    final int[] resultHolder = new int[SIZE];
    

    MemoryHelper只是包装一个volatile字段并提供两种方法:一种用于读取它,另一种用于写入它。

    Worker的run()代码:

    public void run() {
        try {
            // Wait main thread
            while(manager.waitBuffer.take() != SHUTDOWN){
                // Load new buffer values
                manager.memoryHelper.readVolatile();
    
                // Do something
                for (int i = a; i <= b; i++){
                    manager.resultHolder[i] = manager.buffer[i] + 10;
                }
    
                // Flush new values of resultHolder
                manager.memoryHelper.writeVolatile();
                // Signal job done
                manager.waitResult.add(Object.class);
            }
        } catch (InterruptedException e) { }
    }
    

    最后,主要线程的重要部分:

    for(int i=0; i < 100_000; i++){
        // Start workers
        for (int j = 0; j < TOT_WORKERS; j++)
            waitBuffer.add(Object.class);
        // Wait workers
        for (int j = 0; j < TOT_WORKERS; j++)
            waitResult.take();
    
        // Load results
        memoryHelper.readVolatile();
        // Do something
        processResult();
        setBuffer();
        // Store buffer
        memoryHelper.writeVolatile();
    }
    

    ArrayBlockingQueue上的同步效果很好。我怀疑是使用readVolatile()writeVolatile()。我被告知写入易失性字段会刷新内存所有以前更改过的数据,然后从另一个线程中读取它会使它们可见。

    在这种情况下,确保正确的可见性就足够了吗?从来没有真正的并发访问相同的内存区域,因此volatile字段应该比ReadWriteLock便宜很多。

1 个答案:

答案 0 :(得分:7)

这里甚至不需要volatile,因为BlockingQueues已经提供了必要的内存可见性保证:

  

内存一致性效果:与其他并发集合一样,在从{{1}访问或删除该元素之后将对象放入BlockingQueue happen-before操作之前,线程中的操作在另一个线程中。

通常,如果您已经进行了某种同步,则可能不需要做任何特殊操作来确保内存可见性,因为它已经由您使用的同步原语保证。

但是,当您没有显式同步时(例如,在无锁算法中),BlockingQueue读取和写入可用于确保内存可见性。

<强> P上。 S上。

此外,您似乎可以使用CyclicBarrier代替您的队列解决方案,它专为类似场景而设计。