Java线程安全列表 [英] Java thread safety of list

查看:159
本文介绍了Java线程安全列表的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a List, which is to be used either in thread-safe context or in a non thread-safe one. Which one will it be, is impossible to determine in advance.

在这种特殊情况下,每当列表进入非线程安全的上下文时,我用

In such special case, whenever the list enters non-thread-safe context, I wrap it using


Collections.synchronizedList(...)

Collections.synchronizedList(...)

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

But I don't want to wrap it, if doesn't enter non-thread-safe context. F.e., because list is huge and used intensively.

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

I've read about Java, that its optimization policy is strict about multi-threading - if you don't synchronize your code correctly, it is not guaranteed to be executed correctly in inter-thread context - it can reorganize code significantly, providing consistency in context of only one thread (see http://java.sun.com/docs/books/jls/third_edition/html/memory.html#17.3). F.e.,


op1;
op2;
op3;

op1; op2; op3;

可以重新组织在


op3;
op2;
op1;

op3; op2; op1;

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

, if it produces same result (in one-thread context).

现在我想知道,如果我


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

  1. fill my list before wrapping it by synchronizedList,

然后把它包起来,

然后用不同的线程

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

, - is there a possibility, that different thread will see this list filled only partially or not filled at all? Might JVM postpone (1) till after (3)? Is there a correct and fast way to make (big) List being non-thread-safe to become thread-safe?

推荐答案

当您通过线程安全方式将列表提供给另一个线程时(例如使用synchronized块,volatile变量或 AtomicReference ),保证第二个线程看到整个列表处于转移时的状态(或任何后来的状态,但不是早期的状态)。

When you give your list to another thread by thread-safe means (for example using a synchronized block, a volatile variable or an AtomicReference), it is guaranteed that the second thread sees the whole list in the state it was when transferring (or any later state, but not an earlier state).

如果你没有之后更改它,您也不需要您的synchronizedList。

If you don't change it afterwards, you also don't need your synchronizedList.

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

Edit (after some comments, to backup my claim):

我假设以下内容:


  • 我们有一个不稳定的变量 list

volatile List<String> list = null;


  • 线程A:

  • Thread A:


    1. 创建一个List L并用元素填充L.

    2. 设置 list 指向L(这意味着将L写入列表

    3. 对L没有进一步修改。

    1. creates a List L and fills L with elements.
    2. sets list to point to L (this means writes L to list)
    3. does no further modifications on L.

    示例来源:

    public void threadA() {
       List<String> L = new ArrayList<String>();
       L.add("Hello");
       L.add("World");
       list = l;
    }
    


  • 线程B:

  • Thread B:


    1. 列表中读取K

    2. 迭代K,打印元素。

    示例来源:

    public void threadB() {
         List<String> K = list;
         for(String s : K) {
             System.out.println(s);
         }
    }
    


  • 所有其他线程都不会触及列表。

  • All other threads do not touch the list.

    现在我们有了这个:


    • 线程A中的操作1-A和2-A按程序订单因此1来自2。

    • 线程B中的动作1-B和2-B按程序订单因此1来自2。

    • 线程A中的操作2-A和线程中的操作1-B按同步订单,因此2-A来自1-B,因为

    • The actions 1-A and 2-A in Thread A are ordered by program order so 1 comes before 2.
    • The action 1-B and 2-B in Thread B are ordered by program order so 1 comes before 2.
    • The action 2-A in Thread A and action 1-B in Thread are ordered by synchronization order, so 2-A comes before 1-B, since

    写入易失性变量(§8.3.1.4) v 与任何线程的所有后续 v 读取同步(后续定义为accor订阅同步订单。)

    A write to a volatile variable (§8.3.1.4) v synchronizes-with all subsequent reads of v by any thread (where subsequent is defined according to the synchronization order).


  • 发生在之前 -order是各个线程和同步顺序的程序顺序的传递闭包。所以我们有:

  • The happens-before-order is the transitive closure of the program orders of the individual threads and the synchronization order. So we have:

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

    1-A happens-before 2-A happens-before 1-B happens-before 2-B

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

    and thus 1-A happens-before 2-B.


    如果一个动作发生在另一个动作之前,则第一个动作在第二个动作之前可见并在第二个动作之前订购。

    If one action happens-before another, then the first is visible to and ordered before the second.


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

    So our iterating thread really can see the whole list, and not only some parts of it. So, transmitting the list with a single volatile variable is sufficient, and we don't need synchronization in this simple case.

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

    One more edit (here, since I have more formatting freedom than in the comments) about the program order of Thread A. (I also added some sample Code above.)

    来自JLS(计划部分订单):


    在每个线程t执行的所有线程间操作中,程序订单
    of t是一个总顺序,它反映了根据t的线程内语义执行这些操作
    的顺序。

    Among all the inter-thread actions performed by each thread t, the program order of t is a total order that reflects the order in which these actions would be performed according to the intra-thread semantics of t.

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

    So, what are the intra-thread semantics of thread A?

    一些上面的段落


    内存模型确定程序中每个点可以读取的值。
    隔离的每个线程的操作必须受该线程的语义
    的控制,但每次读取所看到的值都是由内存模型确定的
    。当我们提到这个时,我们说程序
    服从线程内语义。线程内语义是
    单线程程序的语义,允许根据线程中读取操作所看到的值完整预测
    线程的行为。如果执行中的线程t的动作是合法的,那么确定
    我们只是评估线程t的
    实现,因为它将在单线程上下文中执行,其余为
    。此规范。

    The memory model determines what values can be read at every point in the program. The actions of each thread in isolation must behave as governed by the semantics of that thread, with the exception that the values seen by each read are determined by the memory model. When we refer to this, we say that the program obeys intra-thread semantics. Intra-thread semantics are the semantics for single threaded programs, and allow the complete prediction of the behavior of a thread based on the values seen by read actions within the thread. To determine if the actions of thread t in an execution are legal, we simply evaluate the implementation of thread t as it would be performed in a single threaded context, as defined in the rest of this specification.

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

    通过从头到尾(从左到右)依次执行每个局部变量声明
    语句和其他语句来执行块。

    A block is executed by executing each of the local variable declaration statements and other statements in order from first to last (left to right).

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

    So, the program order is indeed the order in which the statements/expressions are given in the program source code.

    因此,在我们的示例源中,内存操作创建一个新的ArrayList 添加Hello添加世界分配给列表 (确实在这个程序顺序中,前三个包含更多的子行为。

    Thus, in our example source, the memory actions create a new ArrayList, add "Hello", add "World", and assign to list (the first three consist of more subactions) indeed are in this program order.

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

    (The VM does not have to execute the actions in this order, but this program order still contributes to the happens-before order, and thus to the visibility to other threads.)

    这篇关于Java线程安全列表的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆