当前位置: 首页 > 面试题库 >

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

颜英博
2023-03-14
问题内容

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

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

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次。


问题答案:

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

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

Java内存模型

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

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

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



 类似资料:
  • 问题内容: 我有两个线程,我想确保我在LinkedBlockingQueue上正确进行了同步。这正确吗?还是不需要在(messageToCommsQueue)上进行显式同步? 宣言: 方法一: 方法二: 问题答案: 是的,没有必要。JavaDoc说: BlockingQueue实现是线程安全的。

  • 我对同步块有一些疑问。在提问之前,我想分享另一个相关帖子链接的答案,以回答相关问题。我引用彼得·劳里的同一个答案。 > <块引号> 同步确保您对数据有一致的视图。这意味着您将读取最新值,其他缓存将获得最新值。缓存足够智能,可以通过特殊总线相互通信(JLS不需要,但允许)该总线意味着它不必触摸主存储器即可获得一致的视图。 如果您只使用同步,则不需要Volatile。如果您有一个非常简单的操作,而同步

  • 我在网上搜索一个生产者和消费者的问题,我得到了这个链接。程序员在这里使用了的向量。 我想为什么我需要一个同步块,因为Vector已经是线程安全的了。它必须自己处理线程。 但当我试图移除同步块时。它给我一个。下面是Product方法的代码段 我的问题是为什么我们需要同步或锁定一个已经是线程安全的对象?

  • 问题内容: 假设我有这个课程: 一个线程可以在不访问该对象的情况下写入这些字段,而另一个线程可以读取它们吗? 注意:这些值可以彼此分开对待,因此在读取时更改没关系。 编辑 : 为了澄清起见,这些字段是 可变的 。只有它们引用的对象是 不可变的 。 问题答案: 从技术上讲,你需要让他们,或读取 和 使用写出来,以保证读者会读的最先进的最新值。正如您现在所拥有的,如果一个线程写入一个值,则不能保证另一

  • 问题内容: 假设我有这个课程: 一个线程可以在不访问该对象的情况下写入这些字段,而另一个线程可以读取它们吗? 注意:这些值可以彼此分开对待,因此在读取时更改没关系。 编辑 : 为了澄清起见,这些字段是 可变的 。只有它们引用的对象是 不可变的 。 问题答案: 从技术上讲,你需要让他们,或读取 和 使用写出来,以保证读者会读的最先进的最新值。正如您现在所拥有的,如果一个线程写入一个值,则不能保证另一

  • 问题内容: 这样做,即ConcurrentHashMap(所有非retreival操作,等)需要在被包裹块?我知道所有这些操作都是线程安全的,因此这样做有真正的好处/需要吗?使用的唯一操作是和。 问题答案: 不,这样做会失去您的利益。您也可以使用with 或锁定整个表(这是在中包装操作时要执行的操作,因为隐含的监视器是整个对象实例。) 目的是通过允许在表上进行并发读/写而不锁定整个表来提高并发代码