微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

如何解决Java:引用同步对象是否需要volatile / final?

是的,你是对的。必须使访问变量也是线程安全的。您可以通过使其成为final或来实现volatile,或者确保所有线程在同步块内再次访问该变量。如果您不这样做,则可能是例如一个线程已“看到”变量的新值,而另一个线程可能仍“看到”null了。

因此,对于您的示例,NullPointerException当线程访问该mySharedobject变量时,有时可能会出现一个。但这可能仅在具有多个缓存的多核计算机上发生。

这里的重点是Java内存模型。它指出,只有在某个线程在所谓 的before-before关系中 读取该状态之前发生更新时,才保证该线程看到另一个线程的内存更新。在之前发生关系可以通过强制执行finalvolatilesynchronized。如果不使用这些构造中的任何一个,则永远不能保证一个线程对变量的赋值不会被其他任何线程可见。

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

请注意,还有其他一些方法可以增强内存更改的可见性,例如,使用静态初始化程序。此外,新创建的线程始终会看到其父线程的当前内存,而无需进一步同步。因此,您的示例甚至可以在不进行任何同步的情况下工作,因为在初始化字段之后,将以某种方式强制执行线程的创建。但是, 依靠这样一个微妙的事实会带来很大的风险,并且如果您以后重构代码而又不考虑细节的话,很容易破坏。Java语言规范中描述了(但很难理解)有关事前发生关系的更多详细信息。

解决方法

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

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

public class SyncClass {

   private int field;

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

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

如果我需要 引用 在线程之间共享的该类的实例, 我仍然需要将该实例声明为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();
    }
}

或者,如果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();
    }
}

final或volatile是强制性的 ,将MyClass访问权限同步到其自身成员的事实并不能免于确保在线程之间共享引用。那正确吗?

1-提到的问题是关于同步和易变的,对于同一字段/变量,我的问题是关于如何正确地使用已经正确同步的类(即已选择了同步),同时考虑调用者需要考虑的含义,可能在已经同步的类的引用上使用volatile
/ final。

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

当所引用问题的第一个答案明确引用易失性引用时,它是关于 不同步不可变
对象。第二个答案将自身限制为原始类型。我发现它们很有用(请参阅下文),但还不够完善,无法对我在这里提出的情况产生任何疑问。 __

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

考虑到在多线程中,您不能“仅仅尝试一下”,就需要适当的了解并确保细节,因为竞争条件可以正确执行1000次,然后严重错误执行1000 + 1次。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。