CompletableFuture.thenAccept 中使用的垃圾回收对象的可用性

Availability of objects for garbage collection used inside CompletableFuture.thenAccept

我正在尝试从使用 CompletableFuture 的方法执行异步调用。完成该任务后,我尝试打印对象 DummyObject 的值,这些值对于调用 Async 调用的方法是本地的。

我想知道它是如何工作的?线程(myThread)在 3 秒后死亡并且 DummyObject 超出范围,thenAccept 中的异步回调仍在打印正确的值。线程是否锁定了 DummyObject?或者发生了其他事情?

请注意,我只是想模拟一个场景。所以我正在使用 Thread.stop().

编辑 - 问题是关于 thenAccept Consumer 如何处理范围?请保留与此相关的答案。

以下程序的输出:

Starting Async stuff
Thread Alive?  false
Reached Main end and waiting for 8 more seconds
James T. Kirk
United Federation of Planets

AsyncTest.java

public class AsyncTest {

    public static void main(String args[]) {

        Thread myThread = new Thread() {
            @Override
            public void run() {
                DummyObject dummyObj = new DummyObject();
                dummyObj.setObjectName("James T. Kirk");
                dummyObj.setObjectNationality("United Federation of Planets");
                System.out.println("Starting Async stuff");
                new AsyncTaskExecuter().executeAsync().thenAccept(taskStatus -> {
                    if(taskStatus.booleanValue()) {
                        System.out.println(dummyObj.getObjectName());
                        System.out.println(dummyObj.getObjectNationality());
                    }
                });
            }
        };

        myThread.start();
        try {
            Thread.sleep(3 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        myThread.stop();
        System.out.println("Thread Alive?  "+ myThread.isAlive());

        System.out.println("Reached Main end and waiting for 8 more seconds");

        try {
            Thread.sleep(8 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class AsyncTaskExecuter {
    public CompletableFuture<Boolean> executeAsync() {
        return  (CompletableFuture<Boolean>) CompletableFuture.supplyAsync(this::theTask);
    }

    public Boolean theTask() {
        try {
            Thread.sleep(6 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }
}

class DummyObject {
    private String objectName;
    private String objectNationality;
    // getters and setters
}

在您所说的那一点上,dummyObj 变量仍在您传递给 thenAccept.[=15 的 lambda 中 范围内 =]

事实上,带有原始变量的栈帧已经消失了。但是,Java 编译器安排表示 lambda 的对象具有原始变量值的副本(即对 DummyObject 实例的引用)保存在只读合成变量中。

(如果变量 effectively final,lambda 只能在封闭方法中使用局部变量这一限制背后的原因。如果变量实际上是 final,它的值可以复制到另一个 final 变量,而不会因变量更改而导致不一致的风险。)

无论如何,lambda 通过合成变量使用 DummyObject。只要 lambda 可访问,DummyObject 实例将保持可访问。

第一件事。请不要使用 stop() 方法。它已被弃用。线程在执行完其 运行 方法后自行终止。

现在回答您的问题,当您创建 CompletableFuture 时,它​​还会在 java 的 ForkJoinCommonPool 中创建一个线程,并在将来的某个时间在那里执行它。如此有效地在您的应用程序中创建了 3 个线程。所以你的对象不会死,因为它会被其中一个线程使用。

  1. 主线程
  2. 我的线程
  3. 最后一个是由 CompletableFuture 创建的。 (这将使用虚拟对象)

所以即使线程1、2死掉了。线程 3 仍将执行。

P.S。我已将您的线程命名为 Thread-MyThread 以向您展示这些线程。您可以通过调用线程的构造函数来为线程命名,该构造函数将线程的名称作为参数。我使用的工具是JVisualVM。

如果局部变量是原始类型,栈中存储的就是它的值。在引用类型局部变量的情况下,引用也存储在堆栈中,但实际对象存储在堆中。

因此,如果在匿名内部 class(代码中的 lambda)中使用局部变量,则堆栈中保存的内容的副本将被带到匿名 class,如果是原始类型变量副本是变量的值,在引用类型变量的情况下,副本是引用本身。因此,在堆栈帧消失后,匿名 class 要么拥有原始类型的副本,要么拥有实际引用的副本,其值存储在堆中,并且当存在对对象的引用时,垃圾收集器将不会回收它。