Java线程安全列表

And*_*rey 13 java multithreading

我有一个List,它可以在线程安全上下文中使用,也可以在非线程安全上下文中使用.它将是哪一个,是不可能事先确定的.

在这种特殊情况下,每当列表进入非线程安全上下文时,我都会使用它进行包装

Collections.synchronizedList(...)

但我不想包装它,如果不进入非线程安全的上下文.Fe,因为列表很大并且使用密集.

我读过Java,它的优化策略是严格的多线程 - 如果你没有正确地同步你的代码,它不能保证在线程间上下文中正确执行 - 它可以重新组织代码,提供一致性仅在一个线程的上下文中(参见http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.3).铁,

OP1; OP2; OP3;

可以重组

OP3; OP2; OP1;

,如果它产生相同的结果(在单线程上下文中).

现在我想知道,如果我

  1. 填写我的列表,然后再用synchronizedList包装它,

  2. 把它包起来,

  3. 然后由不同的线程使用

, - 是否有可能,不同的线程会看到此列表仅部分填充或根本未填充?JVM可能推迟(1)直到(3)之后?是否有一种正确而快速的方法使(大)List成为线程安全的非线程安全的?

Paŭ*_*ann 12

当您通过线程安全的方式将列表提供给另一个线程时(例如使用synchronized块,volatile变量或者AtomicReference),可以保证第二个线程看到整个列表处于传输时的状态(或任何更晚的状态)国家,但不是早期的国家).

如果之后不进行更改,则也不需要synchronizedList.


编辑(在评论之后,备份我的声明):

我假设如下:

  • 我们有一个易变的变量list.

    volatile List<String> list = null;
    
    Run Code Online (Sandbox Code Playgroud)
  • 线程A:

    1. 创建一个List L并用元素填充L.
    2. 设置list为指向L(这意味着写入Llist)
    3. 对L.没有进一步的修改

    样本来源:

    public void threadA() {
       List<String> L = new ArrayList<String>();
       L.add("Hello");
       L.add("World");
       list = l;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 线程B:

    1. 从中读取K. list
    2. 迭代K,打印元素.

    样本来源:

    public void threadB() {
         List<String> K = list;
         for(String s : K) {
             System.out.println(s);
         }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 所有其他线程都不会触及列表.

现在我们有了这个:

  • 线程A中的动作1-A和2-A按程序顺序排序,因此1在2之前.
  • 线程B中的动作1-B和2-B按程序顺序排序,因此1在2之前.
  • 线程A中的动作2-A和线程中的动作1-B按同步顺序排序,因此2-A出现在1-B之前,因为

    于挥发性变量(§8.3.1.4)写v同步-与所有后续读取的v任何线程(其中,随后根据该同步命令被定义).

  • 之前发生阶是单个线程与所述同步命令的程序的命令的传递闭包.所以我们有:

    1-A发生在2-A发生之前 - 在1-B发生之前 - 在2-B之前发生

    因此1-A发生在2-B之前.

  • 最后,

    如果一个动作发生在另一个动作之前,则第一个动作在第二个动作之前可见并在第二个之前被命

所以我们的迭代线程真的可以看到整个列表,而不仅仅是它的某些部分.因此,使用单个volatile变量传输列表就足够了,在这种简单的情况下我们不需要同步.


关于线程A的程序顺序,还有一个编辑(这里,因为我有比注释中更多的格式自由).(我还在上面添加了一些示例代码.)

从JLS(部分程序顺序):

在由每个线程t执行的所有线程间动作中,t的程序顺序是反映根据t的线程内语义将执行这些动作的顺序的总顺序.

那么,线程A的线程内语义是什么?

有些段落上面:

内存模型确定可以在程序中的每个点读取哪些值.隔离中每个线程的操作必须遵循该线程的语义,除了每次读取所看到的值由内存模型确定.当我们参考这个时,我们说该程序遵循线程内语义.线程内语义是单线程程序的语义,允许基于线程内读取操作所看到的值完全预测线程的行为.为了确定执行中的线程t的动作是否合法,我们只是评估线程t的实现,因为它将在单线程上下文中执行,如本规范其余部分所定义.

本规范的其余部分包括第14.2节(块):

通过从第一个到最后一个(从左到右)按顺序执行每个局部变量声明语句和其他语句来执行块.

因此,程序顺序确实是程序源代码中给出语句/表达式的顺序.

因此,在我们的示例源中,内存操作创建一个新的ArrayList,添加"Hello",添加"World",并指定list(前三个由更多的子动作组成)确实是按此程序顺序.

(VM不必按此顺序执行操作,但此程序顺序仍然有助于发生之前的顺序,从而有助于其他线程的可见性.)