我是不是需要同步对仅由一个线程修改的列表的访问?

Posted

技术标签:

【中文标题】我是不是需要同步对仅由一个线程修改的列表的访问?【英文标题】:Do I need to synchronize access to a List that is only modified by one thread?我是否需要同步对仅由一个线程修改的列表的访问? 【发布时间】:2016-01-30 03:45:02 【问题描述】:

这里我有一个类,它有两个可以访问列表的线程。一个线程定期用更新的副本替换列表,另一个线程将列表的内容绘制到屏幕上。

public class ThreadSafePainter 
    private List<String> dataList = new ArrayList<>();

    /*
     *  starts a thread to periodically update the dataList
     */
    public ThreadSafePainter() 
        Thread thread = new Thread(() -> 
            while (true) 
                // replace out-dated list with the updated data
                this.dataList = getUpdatedData();
                // wait a few seconds before updating again
                Thread.sleep(5000);
            
        );
        thread.start();
    

    /*
     *  called 10 times/second from a separate paint thread
     *  Q: Does access to dataList need to be synchronized?
     */
    public void onPaint(Graphics2D g) 
        Point p = new Point(20, 20);

        // iterate through the data and display it on-screen
        for (String data : dataList) 
            g.drawString(data, p.x, p.y);
            p.translate(0, 20);
        
    

    /*
     *  time consuming data retrieval
     */
    private List<String> getUpdatedData() 
        List<String> data = new ArrayList<>();
        // retrieve external data and populate list
        return data;
    

我的问题是,我需要同步对 dataList 的访问吗?我该怎么做呢?这行得通吗:

public ThreadSafePainter() 
    ...
            synchronized (this) 
                this.dataList = getUpdatedData();
            
    ...


public void onPaint(Graphics2D g) 
    ...
    synchronized (this) 
        for (String data : dataList)
            ...
    

【问题讨论】:

每次迭代都会重新绘制整个屏幕吗? 由于getUpdatedData() 每次都会创建一个新列表,因此您只需要一个安全的发布。在这种情况下,将字段 dataList 声明为 volatile 就足够了。如果列表引用在填充后被存储并且永远不会再次修改(因为下一次更新会创建一个新列表)并且读取器在每次处理时读取一次引用(如 for(…: dataList) 所做的那样),这很重要。如果它需要在一个paint 期间多次访问该列表,则必须将其存储在一个局部变量中。 当两个或多个线程共享任何可变状态时,必须有某种机制来处理并发。无论是低级同步、高级并发类、Atomic* 类还是volatile 字段,视实际情况而定,但必须始终到位。 谁调用 onPaint()? 我同意@Holger 的评估。此外,这可能超出了您的问题范围,但您似乎掩盖了 getUpdatedData() 的实现。您需要确保这也是线程安全的,这可能涉及与 volatile 同步或切换。 【参考方案1】:

任何时候你有多个线程访问同一个可变状态(嗯,几乎任何时候,都有一些例外,比如当你知道状态不会在另一个线程的生命周期内发生变化时),你需要采取 一些类型的动作。在这种情况下,您正在改变字段dataList,并且您希望另一个线程对此做出反应。所以,你需要做“某事”。最通用的解决方案是使用synchronized,您对如何执行此操作的概述就可以了。

如果您想从某些东西中挤出最大性能(这对于 GUI 问题来说有点荒谬),或者您想展示您对并发性的深刻理解,您可以考虑更多适用于更多应用的轻量级替代方案有限的情况。在这种情况下,您只有一个作家,而作家只写一个参考。对于这种情况,volatile 就足够了。在这种代码中,我个人会坚持使用synchronized,因为当您更改代码时它不太可能中断,例如您可能添加另一个编写器线程或其他东西。

【讨论】:

【参考方案2】:

如果您不使列表同步,那么您应该使列表易失。这样,读取线程就可以获取 List 变量值的最新副本。

Here 是一个很好的解释。

【讨论】:

【参考方案3】:

官方 Java 文档指出,ArrayList 是不同步的。所以你需要同步它。

但是文档还说这仅适用于多个线程访问同一列表的情况。因此,在您的情况下,不需要同步它。但是如果你想 100% 确定你可以通过这个简单的调用来同步你的 List:

List<data_type> list = Collections.synchronizedList(new ArrayList<data_type>());

...其中“data_type”是您要存储的类型值。

【讨论】:

该文档具有误导性。即使列表不需要同步,您也需要以安全的方式发布对列表的引用。

以上是关于我是不是需要同步对仅由一个线程修改的列表的访问?的主要内容,如果未能解决你的问题,请参考以下文章

我是不是需要内存屏障来访问已完成的线程修改的内存?

深入解析Python中的线程同步方法

访问 List 项时 UI 和 Worker 线程同步

在两个线程之间使用 LinkedBlockingQueue 是不是意味着我们不需要同步它们对共享数据的访问?

同时从多个线程访问只读数据是不是明智?

Java线程同步synchronized方法与方法块