编程语言
首页 > 编程语言> > 非易失性字段首先从另一个线程访问对象(java)

非易失性字段首先从另一个线程访问对象(java)

作者:互联网

我已经在某个服务器类型的应用程序上工作了一段时间,我发现它的设计挑战了我在Java中看到内存一致性(可以这么说)的方式.

此应用程序使用NIO,因此I / O线程数量有限(它们仅用于网络I / O,而没有其他功能;它们永远不会终止,但可能会阻塞以等待更多工作).

每个连接在内部都表示为特定类型的对象,在本示例中,我们将其称为ClientCon. ClientCon具有与会话相关的各种字段,这些字段都不是可变的.关于获取/设置这些字段的值,没有任何形式的同步.

接收的数据由具有固定最大大小的逻辑单元组成.每个此类单元都有一些元数据,可以确定处理类型(类).完成后,将创建该类型的新对象.所有这些处理程序都有字段,这些字段都不是易失的.然后,一个I / O线程(为每个ClientCon分配一个具体的I / O线程)然后在新的处理程序对象上调用一个具有剩余缓冲区内容(在读取元数据之后)的受保护的读取方法.

此后,将相同的处理程序对象放入一个特殊的队列中,然后将其(队列)提交到线程池中执行(在此线程池中,调用每个处理程序的run方法以根据读取的数据执行操作).就本例而言,我们可以说TP线程永不终止.

因此,TP线程将使用以前从未访问过的对象.该对象的所有字段都是非易失性的(大多数/全部字段都是非最终的,因为它们是在构造函数外部进行修改的).

处理程序的run方法可以基于ClientCon中特定于会话的字段进行操作,也可以对其进行设置和/或处理其值在read方法中设置的处理程序对象自己的字段.

根据CPJ(Java并行编程:设计和原理):

The first time a thread accesses a field of an object, it sees either the initial value of the field or a value since written by some other thread.

可以在JLS 17.5中找到此报价的更全面的示例:

class FinalFieldExample { 
    final int x;
    int y; 
    static FinalFieldExample f;

    public FinalFieldExample() {
        x = 3; 
        y = 4; 
    } 

    static void writer() {
        f = new FinalFieldExample();
    } 

    static void reader() {
        if (f != null) {
            int i = f.x;  // guaranteed to see 3  
            int j = f.y;  // could see 0
        } 
    } 
}

The class FinalFieldExample has a final int field x and a non-final
int field y. One thread might execute the method writer and another
might execute the method reader.

Because the writer method writes f after the object’s constructor
finishes,
the reader method will be guaranteed to see the properly
initialized value for f.x: it will read the value 3. However, f.y is
not final; the reader method is therefore not guaranteed to see the
value 4 for it.

该应用程序已经在x86(和x86 / 64)Windows / Unix操作系统(Linux操作系统,Solaris)上运行了多年(Sun / Oracle和OpenJDK JVM,版本1.5至8),并且显然没有相关的内存一致性问题.接收数据处理.为什么?

综上所述,TP线程有没有办法看到在构造后初始化的对象,并且在调用受保护的读取方法时无法看到I / O线程所做的全部或部分更改?如果是这样,那么可以提供一个详细的示例将是很好的.

否则,是否有一些副作用可能导致对象的字段值在其他线程中始终可见(例如,将处理程序对象添加到队列时I / O线程获取监视器)? I / O线程和TP线程都不会在处理程序对象本身上同步.队列也没有做这样的事情(无论如何,这并不有意义).这可能与具体的JVM的实现细节有关吗?

编辑:

It follows from the above definitions that:

An unlock on a monitor happens-before every subsequent lock on that
monitor. – Not applicable: monitor is not acquired on the handler object

A write to a volatile field (§8.3.1.4) happens-before every subsequent
read of that field. – Not applicable: no volatile fields

A call to start() on a thread happens-before any actions in the
started thread. – A TP thread might already exist when the queue with handler object(s) is submitted for execution. A new handler object might be added to queue amidst an execution on an existing TP thread.

All actions in a thread happen-before any other thread successfully
returns from a join() on that thread. – Not applicable: threads do not wait for each other

The default initialization of any object happens-before any other
actions (other than default-writes) of a program. – Not applicable: field writes are after default init AND after constructor finishes

When a program contains two conflicting accesses (§17.4.1) that are
not ordered by a happens-before relationship, it is said to contain a
data race.

Memory that can be shared between threads is called shared memory or
heap memory.

All instance fields, static fields, and array elements are stored in
heap memory
. In this chapter, we use the term variable to refer to
both fields and array elements.

Local variables (§14.4), formal method parameters (§8.4.1), and
exception handler parameters (§14.20) are never shared between threads
and are unaffected by the memory model.

Two accesses to (reads of or writes to) the same variable are said to
be conflicting if at least one of the accesses is a write.

进行了一次写操作,但不强制字段上具有HB关系,后来又再次进行了读取,但不强制在这些字段上具有HB关系.还是我在这里大错特错?也就是说,没有声明该对象的任何内容都可以更改,那么为什么JVM会强制刷新这些字段的缓存值?

TL; DR

线程#1以不允许JVM知道应将这些值传播到其他线程的方式将值写入新对象的字段.

线程#2获取由线程#1构造后修改的对象,并读取这些字段值.

为什么实践中从未发生FinalFieldExample / JLS 17.5中描述的问题?

为什么线程2永远不会只看到默认初始化的对象(或者,替代地,看到的对象是构造后的对象,而是线程1更改字段值之前/之中的对象)?

解决方法:

我非常确定,当线程池启动一个线程/运行一个可调用对象时,它具有hapens-before语义,因此该线程可以使用before-before之前的所有更改.

当您有多个线程在同一对象实例上同时修改数据时(例如,已经有2个线程正在运行并且修改了相同的值(或堆中彼此相邻的值)),您在CPJ中提到的方案是有效的.

在您的情况下,似乎没有并发修改/读取字段.

标签:java-memory-model,multithreading,java,jls
来源: https://codeday.me/bug/20191027/1946681.html