java Volatile/synchronization 在 arraylist 上

java Volatile/synchronization on arraylist

我的程序是这样的:

public class Main {
    private static ArrayList<T> list;

    public static void main(String[] args) {
        new DataListener().start();
        new DataUpdater().start();
    }

    static class DataListener extends Thread {
        @Override
        public void run() {
            while(true){
                //Reading the ArrayList and displaying the updated data
                Thread.sleep(5000);
            }
        }
    }

    static class DataUpdater extends Thread{
        @Override
        public void run() {
            //Continuously receive data and update ArrayList;
        }
    }
}

为了在两个线程中都使用这个 ArrayList,我知道两个选项:

  1. 使 ArrayList 易变。但是我在 this article 中读到,只有在 "Writes to the variable do not depend on its current value." 时才允许变量变易变,我认为在这种情况下它确实如此(因为例如当您对 ArrayList 执行添加操作时,ArrayList 的内容在此操作之后取决于 ArrayList 的当前内容,或者不是吗?)。此外,DataUpdater 必须时不时地从列表中删除一些元素,而且我还读到不可能从不同线程编辑 volatile 变量。

  2. 使这个ArrayList成为同步变量。但是,我的DataUpdater会不断的更新ArrayList,这样会不会阻止DataListener读取ArrayList?

我是否误解了这里的任何概念,或者是否有其他选择可以实现这一目标?

确实,两个解决方案中的none就足够了。您实际上需要同步 arraylist 上的完整迭代,以及对 arraylist 的每次写入访问:

synchronized(list) {
    for (T t : list) {
        ...
    }
}

synchronized(list) {
    // read/add/modify the list
}

Volatile 根本帮不了你。 volatile的意思是线程A对一个共享变量所做的修改对线程B是立即可见的。通常这样的更改可能在某些缓存中只对创建它们的线程可见,并且 volatile 只是告诉 JVM 不要进行任何缓存或优化,这将导致值更新被延迟。

所以它不是一种同步方式。这只是确保变化可见性的一种手段。此外,它更改为 变量 ,而不是更改为该变量 引用的 对象。也就是说,如果您将 list 标记为 volatile,只有当您 将新列表分配给 list 时才会有所不同,如果您更改列表的内容!


您的另一个建议是使 ArrayList 成为一个同步变量。这里有一个误解。变量不能同步。唯一可以同步的是 code - 整个方法或其中的特定块。您使用对象作为 同步监视器

监视器是对象本身(实际上,它是监视器的对象的逻辑部分),而不是变量。如果您在同步旧值后将不同的对象分配给同一个变量,那么您将无法使用旧监视器。

但无论如何,同步的不是对象,而是您决定使用该对象同步的代码。

因此您可以使用 list 作为监视器来同步其上的操作。但是你不能list同步。


假设你想使用列表作为监视器来同步你的操作,你应该设计它以便写入线程不会一直持有锁。也就是说,它只是为单个读取-更新、插入等抓取它,然后释放它。为下一个操作再次抓住它,然后释放它。如果同步整个方法或整个更新循环,其他线程将永远无法读取它。

在阅读线程中,您可能应该这样做:

List<T> listCopy;

synchronized (list) {
    listCopy = new ArrayList(list);
}

// Use listCopy for displaying the value rather than list

这是因为显示速度可能很慢 - 它可能涉及 I/O、更新 GUI 等。因此,为了最大限度地减少锁定时间,您只需从列表中复制值,然后释放监视器,以便更新线程可以完成它的工作。


除此之外,java.util.concurrent 包等中还有许多类型的对象旨在帮助处理这种情况,其中一侧正在写入,另一侧正在读取。查看文档 - 也许 ConcurrentLinkedDeque 适合您。

  1. make the ArrayList volatile.

您无法创建 ArrayList volatile。您不能使任何对象易变。 Java 中唯一可变的是 字段。

在您的示例中,list 而不是 ArrayList

private static ArrayList<T> list;

listMain class.

静态字段

volatile 关键字仅在一个线程 更新 字段并且另一个线程随后 访问 字段时才重要。

这一行更新 list,但 not 更新 volatile field:

list.add(e);

执行该行后,列表已更改,但该字段仍引用相同的列表对象。