【问题标题】:Correct way to pass a POJO (no volatile fields,synchronized methods) between multiple threads and assure visibility of changes?在多个线程之间传递 POJO(无易失性字段,同步方法)并确保更改可见性的正确方法?
【发布时间】:2020-08-13 19:02:04
【问题描述】:

我发现了很多与可见性、同步和线程相关的问题和答案,但似乎都没有涵盖我的特定用例(或者我可能只是不擅长搜索;-)所以我会问一个新问题并希望一些慷慨的灵魂能启发我:)

我的问题是:鉴于下面的代码,访问主线程中 WorkItem 项的字段是否会正确反映线程池工作线程对它们所做的任何更改?

我的怀疑是“否”,因为这感觉类似于传递一个包含一些值的数组,只是在数组引用上同步,而不是在单个元素上同步...... JDK 中存在像 AtomicReferenceArray 这样的类肯定是​​有原因的以及为什么他们在访问单个元素时使用 getVolatile()/setVolatile()。

package com.voipfuture.voipmng.monitoring;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class IsThisThreadSafe
{
    public static class WorkItem { public String field1,field2; /* etc. */ }

    public static void main(String[] args) throws InterruptedException
    {
        final ExecutorService service = Executors.newFixedThreadPool(5);

        final List<WorkItem> items = List.of(new WorkItem(), new WorkItem());

        final CountDownLatch finished = new CountDownLatch(items.size());
        for (WorkItem item : items)
        {
            service.submit(() ->
            {
                try
                {
                    synchronized (item)
                    {
                        // mutate object
                        item.field1 = "test";
                    }
                }
                finally
                {
                    finished.countDown();
                }
            });
        }
        finished.await();

        for (WorkItem item : items)
        {
            // will this make sure all changes done inside
            // threadpool worker threads are visible here ?
            synchronized (item)
            {
                // do stuff with work item
                System.out.println(item.field1);
            }
        }
    }
}

【问题讨论】:

  • 我强烈推荐使用标准的 Java 代码格式;在这种情况下,它会将代码的垂直大小减少约 25%。另外,如果您要处理多线程,我强烈建议您了解 happens-before 概念(特别是 POJO 上的操作发生在同一线程中的其他任何事情之前,以及闩锁倒计时发生在等待之前)。

标签: java multithreading synchronization thread-safety


【解决方案1】:

鉴于下面的代码,在主线程中访问 WorkItem 项的字段是否会正确反映线程池工作线程对其所做的任何更改?

答案是肯定的,你会的。确保可视性有两个原因。

  1. synchronized(item):假设您没有CountDownLatch(接下来会详细介绍),但您只有synchronized(item)。由于您正在同步读取和写入同一实例,因此读取线程将看到在写入之前发生的更改。 但是,如果没有发生写入,那么您将看不到 test 的值,您只会看到 null。因此,即使它保证了内存可见性,它也不能保证程序顺序。

  2. finished.await(); 现在假设您删除了synchronized(item)。如果您所拥有的只是finished.countDown() 和finished.await(),那么您保证在await 继续之前发生的任何更新现在在退出await 后将可见。因此,只需使用CountDownLatch,您就可以为您的程序提供内存可见性和并发程序顺序。

来自CountDownLatch javadocs

内存一致性影响:在计数达到零之前,线程中的操作在调用 countDown() 之前发生在从另一个线程中的相应 await() 成功返回之后的操作。

【讨论】:

  • 谢谢,我错过了JavaDoc关于CountDownLatch的内存一致性的评论。
【解决方案2】:

如果所有线程对共享对象的所有访问(读取和写入)都在同步块中完成,则访问是安全的。同步块添加了一个内存屏障,因此任何等待写入主内存的修改都会被提交。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-27
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 2012-02-05
    • 1970-01-01
    • 2021-04-11
    • 1970-01-01
    相关资源
    最近更新 更多