Java:引用同步对象需要volatile/final吗?

Goz*_*Man 15 java multithreading volatile synchronized

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

假设我有一个类本身正确同步:

public class SyncClass {

   private int field;

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

   public synchronized void doSomethingElse() {
       field = field * 3;
   }
}
Run Code Online (Sandbox Code Playgroud)

如果我需要引用该类的实例,在线程之间共享,我仍然需要声明该实例是volatile还是final,我是对的吗?如:

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();
    }
}        
Run Code Online (Sandbox Code Playgroud)

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

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();
    }
}        
Run Code Online (Sandbox Code Playgroud)

final或volatile是强制性的,将MyClass访问权限同步到自己的成员这一事实并不豁免确保在线程之间共享引用.是对的吗?

Java中的volatile和synchronized之间的差异的差异

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

2-换句话说,所引用的问题/答案是关于锁定/易变的同一个对象,我的问题是:我如何确定不同的线程实际上看到相同的对象?在锁定/访问它之前.

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

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

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

Jan*_*fer 6

是的,你是对的.您还必须访问变量也是线程安全的.你可以做,要么通过使它final或者volatile,或者你确保所有线程再次进入一个同步块内部的变量.如果你不这样做,可能是一个线程'看到'已经是变量的新值,但另一个线程可能仍然'看' null,例如.

因此,关于您的示例,您有时可以获得NullPointerException线程访问mySharedObject变量的时间.但这可能只发生在具有多个缓存的多核机器上.

Java内存模型

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

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

请注意,还有一些其他方法可以强制实现内存更改的可见性,例如,使用静态初始化程序.此外,新创建的线程始终可以看到其父线程的当前内存,而无需进一步同步.因此,您的示例甚至可以在没有任何同步的情况下工作,因为在初始化字段之后,以某种方式强制创建线程.然而,依赖于这样一个微妙的事实是非常危险的,并且如果您稍后重构代码而没有记住这些细节,则很容易破坏.在Java语言规范中描述了关于先发生关系的更多细节(但很难理解).

  • 许多其他答案似乎并不理解同步的工作原理.Synchronized将当前线程的变量与主内存同步,但不保证其他线程的变量同步.除非你能保证其他线程会遇到"同步",否则应该在可以跨线程使用的变量上使用volatile.在线程编程中,偏执是有益的. (2认同)
  • @Necreaux我必须完全不同意.螺纹安全应该100%通过设计确保,不希望通过在您认为可能需要它的任何地方撒上"volatile".在Java中,`volatile`具有精确的语义,并且肯定可以通过设计实现这种100%保证的一部分,但不能通过在偏执中消除它. (2认同)