当前位置: 首页 > 知识库问答 >
问题:

未初始化的对象泄漏到另一个线程,尽管没有代码显式泄漏它?

连成益
2023-03-14

让我们看看这个简单的Java程序:

import java.util.*;

class A {
    static B b;
    static class B {
        int x;
        B(int x) {
            this.x = x;
        }
    }
    public static void main(String[] args) {
        new Thread() {
            void f(B q) {
                int x = q.x;
                if (x != 1) {
                    System.out.println(x);
                    System.exit(1);
                }
            }
            @Override
            public void run() {
                while (b == null);
                while (true) f(b);
            }
        }.start();
        for (int x = 0;;x++)
            b = new B(Math.max(x%2,1));
    }
}

主Thread

主线程创建一个x设置为1的B实例,然后将该实例写入静态字段A.B。它永远重复这个动作。

轮询线程

生成的线程轮询,直到发现A.b.x不是1。

?!?

一半的时间它像预期的那样进入无限循环,但一半的时间我得到这个输出:

$ java A
0

为什么轮询线程能够看到x未设置为1的B

这里出现的是x%2而不是仅仅是x,因为这个问题是可以重复的。

我在linux x64上运行openjdk 6。


共有3个答案

齐建安
2023-03-14

在我看来,B. x上可能存在一个竞争条件,这样在B的构造函数中的this. x=x之前可能存在一个B. x已创建且B. x=0的瞬间。一系列事件类似于:

B is created (x defaults to 0) -> Constructor is ran -> this.x = x

您的线程在B. x创建后但在构造函数运行之前的某个时间访问它。但是,我无法在本地重新创建问题。

申浩广
2023-03-14

围绕并发的考虑通常集中在状态的错误更改或死锁上。但是来自不同线程的状态可见性同样重要。现代计算机中有许多地方可以缓存状态。在寄存器中,处理器上的L1缓存,处理器和内存之间的L2缓存,等等。JIT编译器和Java内存模型旨在尽可能或合法地利用缓存,因为它可以加快速度。

它也会产生意想不到的、违反直觉的结果。我相信这种情况正在发生。

当创建B的实例时,实例变量x在被设置为传递给构造函数的任何值之前短暂地设置为0。在这种情况下,1。如果另一个线程尝试读取x的值,则即使x已设置为1,它也可能看到值0。它可能会看到一个过时的缓存值。

为了确保看到x的最新值,您可以做几件事。您可以使x易失性,或者您可以在B实例上通过同步来保护x的读取(例如,通过添加同步getX()方法)。您甚至可以将x从int更改为java.util.concurrent.atomic.原子整数

但是到目前为止,纠正这个问题最简单的方法是使x最终。无论如何,它在B的生命周期内永远不会改变。Java对最终字段做出了特殊的保证,其中之一是构造函数完成后,构造函数中设置的最终字段将对任何其他线程可见。也就是说,没有其他线程会看到该字段的陈旧值。

使字段不可变也有许多其他好处,但这是一个很好的好处。

另见杰里米·曼森的原子性、可见性和排序。特别是他说的部分:

容鸿畴
2023-03-14

我的想法是这样的:因为b不是最终的,编译器可以随意重新排序操作,对吗?因此,这基本上是一个重新排序问题,因此,将变量标记为final的不安全出版物问题将解决问题。

或多或少,这与Java内存模型文档中提供的示例相同。

真正的问题是这怎么可能。我也可以在这里推测(因为我不知道编译器将如何重新排序),但对B的引用可能会在写入x之前写入主存(另一个线程可以看到)。在这两个操作之间进行读取,因此为零值

 类似资料:
  • 我有一个在tomcat7上运行的Spring3网络应用程序。http://www.mkyong.com/mongodb/spring-data-mongodb-hello-world-example/,我遵循了这个教程,然后我有一个@Scheduled方法,每10秒运行一次。在这个方法中,它使用MongoOperations MongoOperations=(MongoOperations)ctx

  • 我正在尝试运行这个简单的类,并且对于每个cicle,我正在计算java进程线程的数量。 ps huH p pid wc-l。 对于每个cicle,线程数是availableProcessors()数的增加。 当ExecutorService在其他线程中运行时,garbace收集器似乎不会释放僵尸线程。 如果我创建了一个静态ExecutorService,它就不会发生

  • 问题内容: 在多个帖子中都提到了这一点:不当使用会导致内存泄漏。我正在努力了解使用内存泄漏将如何发生。 我发现的唯一情况如下: Web服务器维护一个线程池(例如,用于servlet)。如果未删除其中的变量,则这些线程可能会导致内存泄漏,因为线程不会死亡。 这种情况下没有提到“ Perm Space”内存泄漏。那是内存泄漏的唯一(主要)用例吗? 问题答案: PermGen的exhaustions 与

  • 我有一个系统,其中许多线程产生的日志将被插入到一个NoSql后端。为了减少网络流量,我在服务器和后端之间引入了一个缓冲区。 环境是: Java,JSP,Spring MVC,JDK 1.7 Apache-tomcat-6 使用的缓冲区是java中的ConcurrentLinkedQueue。还实现了一个DBPushThread来每5秒从队列中获取日志,并将它们插入backened。我们使用offe

  • 我的Optaplanner似乎出现了内存泄漏。要复制,只需下载Quarkus示例: 然后通过增加终止时间(例如,将)和取消注释表示的行来更改以启用多线程。 然后,运行手册中的请求: 内存使用量开始稳步增加,同时生成了数百万个PhreakPropagationContext对象。我认为这不是预期的行为。我应该为此制造问题还是遗漏了什么?

  • 我正在使用Eclipse,并执行下面的函数,碰巧我打开了一个扫描器,然后,最后我关闭了它,但Eclipse一直说它没有关闭“资源泄漏:'Scanner'没有关闭”。我可以用try with resources来完成,警告消失了,但我想知道为什么我在这里尝试的方式不起作用