本文介绍了Java:引用同步对象需要volatile / final吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这似乎是一个非常基本的问题,但我找不到明确的确认。

This seems a pretty basic issue, but I cannot find a clear confirmation.

假设我的课程本身已正确同步:

Let's say I have a class properly synchronized in itself:

public class SyncClass {

   private int field;

   public synchronized void doSomething() {
       field = field * 2;
   }

   public synchronized void doSomethingElse() {
       field = field * 3;
   }
}

如果我需要参考到该类的实例,在线程之间共享,我仍然需要声明该实例是volatile还是最终,我是对的吗?如:

If I need to have a reference to an instance of that class, shared between threads, I do still need to declare that instance volatile or final, am I right? As in:

public class MainClass { // previously OuterClass

    public static void main(String [ ] args) {

        final SyncClass mySharedObject = new SyncClass();

        new Thread(new Runnable() {
            public void run() {
                mySharedObject.doSomething();
            }
       }).start();

       new Thread(new Runnable() {
            public void run() {
                mySharedObject.doSomethingElse();
            }
       }).start();
    }
}

或者,如果 mySharedObject 不能是最终的,因为它的实例化取决于一些其他条件(与GUI的交互,来自套接字的信息等),事先不知道:

Or, if mySharedObject cannot be final, because its instantiation depends on some other conditions (interaction with GUI, info from socket, etc.), not known beforehand:

public class MainClass { // previously OuterClass

    public static void main(String [ ] args) {

        volatile SyncClass mySharedObject;

        Thread initThread = new Thread(new Runnable() {
            public void run() {

            // just to represent that there are cases in which
            //   mySharedObject cannot be final
            // [...]
            // interaction with GUI, info from socket, etc.
            // on which instantation of mySharedObject depends

            if(whateverInfo)
                mySharedObject = new SyncClass();
            else
               mySharedObject = new SyncClass() {
                   public void someOtherThing() {
                     // ...
                   }
               }
            }
       });

       initThread.start();

       // This guarantees mySharedObject has been instantied in the
       //  past, but that still happened in ANOTHER thread
       initThread.join();

       new Thread(new Runnable() {
            public void run() {
                mySharedObject.doSomething();
            }
       }).start();

       new Thread(new Runnable() {
            public void run() {
                mySharedObject.doSomethingElse();
            }
       }).start();
    }
}

最终或不稳定 MyClass 同步对其自己成员的访问这一事实,并不豁免确保在线程之间共享引用。是吗?

Final or volatile are mandatory, the fact that MyClass synchronizes the access to its own members, does NOT exempt to take care in ensuring that the reference is shared among threads. Is that right?

1-引用的问题是关于synchronized和volatile作为替代,相同的字段/变量,我的问题是如何正确使用已经正确同步的类(即已选择同步),考虑调用者需要考虑的含义,可能在已经同步的类的引用上使用volatile / final 。

1- The referred question is about synchronized and volatile as alternatives, for the same field/variable, my question is about how to correctly use an already properly synchronized class (i.e. synchronized has been choosen), considering implications needed to be considered by the caller, possibly using volatile/final on a reference of an already synchronized class.

2-换句话说,提到的问题/答案是关于锁定/挥发相同的对象,我的问题是:我怎样才能确定不同的线程实际上看到了同一个对象?锁定/访问它之前。

2- In other words, the referred question/answers are about locking/volatile THE SAME OBJECT, my question is: how can I be sure different threads actually SEE THE SAME OBJECT? BEFORE locking/accessing it.

当引用问题的第一个答案明确提到易变参考时,它是关于不可变对象没有同步。第二个答案限于原始类型。
我发现它们很有用(见下文),但还不够完整,不足以对我在这里给出的案件表示怀疑。

When the first answer of referred question refers explicitly to a volatile reference, it's about an immutable object without synchronization. The second answer limits itself to primitive types.I DID find them useful (see below), but not complete enough to shed any doubts on the case I'm giving here.

3-推荐对于一个非常开放的问题,答案是非常抽象和学术性的解释,完全没有代码;正如我在介绍中所说,我需要明确确认实际代码,引用一个特定的,虽然很常见的问题。当然,它们是相关的,但正如教科书与特定问题有关。 (我在打开这个问题之前实际上已经阅读了它,并且发现它很有用,但我仍然需要讨论一个特定的应用程序。)如果教科书解决了所有人可能已经应用它们的问题/疑问,我们可能根本不需要stackoverflow。

3- The referred answers are very abstract and scholarly explanations to a very open question, with quite no code at all; as I stated in the introduction I need to a clear confirmation to actual code referring a specific, while quite common, issue. They're related, sure, but just as a text book is related to a specific problem. (I actually read it before opening this question, and find it useful, yet I still need to discuss a specific application.) If text books resolved all problems/doubts people may have applying them, we probably wouldn't need stackoverflow at all.

考虑到,在多线程中,你不能只是尝试一下,你需要正确的理解并确保细节,因为竞争条件可能会变成一千次然后在千分之一的时间里发生了可怕的错误。

Consider that, in multithreading, you cannot "just try it out", you need a proper understanding and be sure of details, because race conditions can go right a thousand times and then go horribly wrong the thousand + 1 time.

推荐答案

是的,你是对的。您还必须访问变量也是线程安全的。您可以通过将其设置为 final volatile 来执行此操作,或者确保所有线程再次访问该变量同步块。如果你不这样做,可能是一个线程'看到'已经是变量的新值,但另一个线程可能仍然'看到' null ,例如。

Yes you are right. It is necessary that you make access to the variable also thread-safe. You can do that either by making it final or volatile, or you ensure that all threads access that variable again inside a synchronous block. If you wouldn't do that, it might be that one thread 'sees' already the new value of the variable, but the other thread might still 'see' null, for example.

因此,关于您的示例,当线程访问<$ c $时,有时可能会得到 NullPointerException c> mySharedObject 变量。但这可能只发生在具有多个缓存的多核机器上。

So regarding your example, you could sometimes get a NullPointerException when a thread accesses the mySharedObject variable. But this might only happen on multi-core machines with multiple caches.

Java内存模型

这里的要点是Java内存模型。它声明一个线程只能保证看到另一个线程的内存更新,如果在所谓的发生在关系之前读取该状态之前发生了更新。可以通过使用 final volatile synchronized来强制执行before-before关系。如果你不使用任何这些结构,任何其他线程都不会保证一个线程的变量赋值。

The main point here is the Java Memory Model. It states a thread is only guaranteed to see a memory update of another thread if that update happens before the read of that state in the so-called happens-before relation. The happens-before relation can be enforced by using final, volatile, or synchronized. If you do not use any of these constructs a variable assignment by one thread is never guaranteed to be visible by any other thread.

你可以认为线程在概念上具有本地缓存,只要您不强制同步多个线程的缓存,线程就会读取和写入其本地缓存。这可能会导致两个线程在从同一个字段读取时看到完全不同的值的情况。

You can think of threads to conceptually have local caches and as long as you do not enforce that caches of multiple threads are synchronized, a thread just reads and writes to its local cache. This might lead to the situation where two threads see completely different values when reading from the same field.

请注意,还有一些其他方法可以强制实现内存更改的可见性,例如,使用静态初始化器。此外,新创建的线程始终可以看到其父线程的当前内存,而无需进一步同步。因此,您的示例甚至可以在没有任何同步的情况下工作,因为在初始化字段之后,以某种方式强制创建线程。 然而依赖于这样一个微妙的事实是非常危险的,如果你以后重构你的代码而没有考虑到这些细节,那么很容易破解。在。

Note that there are some additional ways to enforce visibility of memory changes, for example, using static initializers. In addition, a newly created thread always sees the current memory of its parent thread, without further synchronization. So your example might even work without any synchronization, because the creation of your threads are somehow enforced to happen after the field has been initialized. However relying on such a subtle fact is very risky and can easily break if you later refactor your code without having that detail in mind. Further details about the happens-before relation are described (but hard to understand) in the Java Language Specification.

这篇关于Java:引用同步对象需要volatile / final吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-01 16:58
查看更多