简体   繁体   English

在线程1中调用的构造方法,仅在线程2中访问的字段-是否需要volatile?

[英]Constructor called in Thread 1, fields accessed exclusively in Thread 2 - volatile needed?

I have a class that is being instantiated by the main thread. 我有一个由主线程实例化的类。 This class then spawns a second thread, the processing thread. 然后,此类产生第二个线程,即处理线程。 The processing threads calls certain methods (handling methods) of the class, which access/change fields. 处理线程调用类的某些方法(处理方法),这些方法访问/更改字段。 Those methods and fields are never accessed by anything else than the processing thread. 那些方法和字段只能由处理线程访问。 However, the constructor which initializes them runs on the main thread. 但是,初始化它们的构造函数在主线程上运行。

The class extends a generic "protocol" class, which contains the input processing thread, which calls the function for handling received messages. 该类扩展了通用的“协议”类,该类包含输入处理线程,该输入处理线程调用用于处理接收到的消息的函数。 Originally, I was automatically starting the processing thread in the constructor of the generic class, which turned out to be a terribly stupid idea: 最初,我是在通用类的构造函数中自动启动处理线程的,结果发现这是一个非常愚蠢的主意:

  1. The subclass called the super constructor 子类称为超级构造函数
  2. The super constructor started the thread 超级构造函数启动了线程
  3. The thread immediately called the message handling method with an empty message (to make it send the first message in the protocol). 线程立即使用空消息调用消息处理方法(以使其发送协议中的第一条消息)。 The method set a "sent message counter". 该方法设置了“发送消息计数器”。
  4. On the main thread, the super constructor returned, and the subclass initialized the set message counter, resetting it to zero. 在主线程上,超级构造函数返回,子类初始化设置的消息计数器,将其重置为零。

I have now changed it by moving the starting of the processing thread to another method and calling it at the end of the constructor of the subclass: 现在,通过将处理线程的开始移至另一个方法并在子类的构造函数的末尾调用它来更改它:

public ProtocolSubclass() {
    super();
    startProcessingThread();
}

I assume that when I call startProcessingThreads(), the field is guaranteed to be initialized. 我假设当我调用startProcessingThreads()时,一定会初始化该字段。 After startProcessingThread() is called, the field will only be accessed from that thread. 调用startProcessingThread()后,将仅从该线程访问该字段。 Can I do this? 我可以这样做吗? Do I need to mark the field volatile, since it gets initialized on the main thread but read on the processing thread? 我是否需要将该字段标记为volatile,因为它是在主线程上初始化但在处理线程上读取的?

I think I got it right this time, but after hours of debugging the above-mentioned issue, I'd rather ask... 我想这次我做对了,但是经过数小时的调试上述问题,我还是想问一下...

As requested, here is slightly more detailed (still simplified) code. 根据要求,这里是稍微更详细(仍然简化)的代码。 Note that the code above is much more simplified and thus may not exactly match the code below. 请注意,上面的代码要简化得多,因此可能与下面的代码不完全匹配。 The field that was acting up was currentMsg: 正在起作用的字段是currentMsg:

public abstract class ProtocolConnection {
    public ProtocolConnection(/*stuff*/) {
        /*stuff*/
        // DO NOT DO THIS HERE: startProcessingThreads();
    }

    protected void startProcessingThreads() {
        inputProcessingThread.start();
    }

    private final Thread inputProcessingThread = new Thread() {
        public void run() {
            if (isInitiator) initiateConnection();
            while (!closed && !finished) {
                ProtocolMessage msg = new ProtocolMessage(inputStream);
                log("received", Integer.toString(msg.tag), Integer.toString(msg.length));
                ProtocolConnection.this.processMessage(msg);
            }
        }
    };
}


public class SimpleProtocolConnection extends ProtocolConnection {
    private int currentMsg = 0;

    public SimpleProtocolConnection(/*stuff*/) {
        super(/*stuff*/);
        startProcessingThreads();
    }

    @Override
    protected void processMessage(ProtocolMessage msg) {
        if (msg.tag != LAST_MESSAGE) {
            sendNext();
        }
    }

    @Override
    protected void initiateConnection() {
        sendNext();
    }

    private void sendNext() {
        addToSendingQueue(new ProtocolMessage(currentMsg, getData())); // very simplified
        currentMsg++;
    }

}

The field is initialized in thread 1; 该字段在线程1中初始化。 then thread 2 is started; 然后启动线程2; then thread 2 exclusively accesses the field. 然后线程2以独占方式访问该字段。 Correct? 正确? If so, then... 如果是这样,那么...

Volatile/atomic is not needed. 不需要挥发性/原子性。

Based on the JLS , an action performed in some thread A before thread B was started is visible to thread B. This is stated in a few different ways: 基于JLS ,线程B可以在启动线程B之前在某个线程A中执行的操作对线程B是可见的。这用几种不同的方式表示:

17.4.2. 17.4.2。 Actions 动作

An inter-thread action is an action performed by one thread that can be detected or directly influenced by another thread. 线程间操作是由一个线程执行的操作,可以被另一个线程检测或直接影响该操作。 There are several kinds of inter-thread action that a program may perform: 程序可以执行几种类型的线程间操作:

[...] [...]

Actions that start a thread or detect that a thread has terminated (§17.4.4). 启动线程或检测到线程已终止的操作(第17.4.4节)。

-- -

17.4.4. 17.4.4。 Synchronization Order 同步顺序

Every execution has a synchronization order. 每个执行都有一个同步顺序。 A synchronization order is a total order over all of the synchronization actions of an execution. 同步顺序是执行中所有同步动作的总顺序。 For each thread t, the synchronization order of the synchronization actions (§17.4.2) in t is consistent with the program order (§17.4.3) of t. 对于每个线程t,t中的同步动作(第17.4.2节)的同步顺序与t的程序顺序(第17.4.3节)一致。

Synchronization actions induce the synchronized-with relation on actions, defined as follows: 同步动作在动作上引发了同步关系,定义如下:

[...] [...]

An action that starts a thread synchronizes-with the first action in the thread it starts. 启动线程的动作与它启动的线程中的第一个动作同步。

-- -

17.4.5. 17.4.5。 Happens-before Order 发生在订单之前

Two actions can be ordered by a happens-before relationship. 可以通过事前发生关系来排序两个动作。 If one action happens-before another, then the first is visible to and ordered before the second. 如果一个动作发生在另一个动作之前,则第一个动作对第二个动作可见,并在第二个动作之前排序。

[...] [...]

A call to start() on a thread happens-before any actions in the started thread. 在启动线程中的任何操作之前,都会在线程上调用start()。

volatile means that a particular field will me modified by different threads. volatile意味着可以通过不同的线程修改特定字段。 It is not needed if the constructor is marked synchronized , otherwise it is needed. 如果构造函数被标记为synchronized ,则不需要它,否则它是必需的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM