Vaadin 14 - 如何确保会话相关对象在 SessionDestroy 之后得到清理

Vaadin 14 - How to make sure of session related objects get cleaned up after SessionDestroy

这是我在 Vaadin 14.1.18 SpringBoot 应用程序中的 MainView Class。

@Route
public class MainView extends VerticalLayout implements SessionDestroyListener{
    Logger logger = LoggerFactory.getLogger(MainView.class);

    public MainView() {
        // To make sure of doing some Houskeeping after Session Timeout
        VaadinService.getCurrent().addSessionDestroyListener(this);

        String sessionId = VaadinSession.getCurrent().getSession().getId();
        Zombie zombie = new Zombie(sessionId);
        zombie.start();

        add(new Span("Hey There! I'm at your disposal!!"));
    }

    @Override
    public void sessionDestroy(SessionDestroyEvent sessionDestroyEvent) {
        logger.warn("Just received a SessionDestroy Event with SessionId :: [{}]", sessionDestroyEvent.getSession().getSession().getId());
        // Performing the HouseKeeping stuff
    }
}

我还实现了一个 Zombie 进程,如下所示,我在我的 MainView Class 中对其进行了实例化。

public class Zombie extends Thread {
    Logger logger = LoggerFactory.getLogger(Zombie.class);
    private String sessionId;

    public Zombie(String sessionId) {
        this.sessionId = sessionId;
    }

    public void run() {
        while (true) {
            try {
                Thread.sleep(60000);
                Calendar cal1 = Calendar.getInstance();
                logger.info("Session [{}] :: Hey, I'm still alive at {}", sessionId, cal1.getTime());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

问题是,当相应的会话被销毁时,为什么附加到它的对象仍然存在?下面的日志显示,即使在 SessionDestroy 之后,僵尸进程仍然是 运行。我是否必须采取一些特定措施来显式清理这些对象,或者 Vaadin 应该以某种方式自动处理它?

2020-03-07 02:32:02.891  INFO 6336 --- com.mypackage.Zombie : Session [B2CBB897208717EAE264C739E2D565BE] :: Hey, I'm still alive at Sat Mar 07 02:32:02 IRST 2020  
2020-03-07 02:32:56.580  WARN 6336 --- com.mypackage.MainView : Just received a SessionDestroy Event with SessionId :: [B2CBB897208717EAE264C739E2D565BE]  
2020-03-07 02:33:02.891  INFO 6336 --- com.mypackage.Zombie : Session [B2CBB897208717EAE264C739E2D565BE] :: Hey, I'm still alive at Sat Mar 07 02:33:02 IRST 2020  
2020-03-07 02:34:02.892  INFO 6336 --- com.mypackage.Zombie : Session [B2CBB897208717EAE264C739E2D565BE] :: Hey, I'm still alive at Sat Mar 07 02:34:02 IRST 2020

您在另一个问题中提到了垃圾收集。在大多数情况下,会话被销毁后,它将被垃圾收集。

当 Java GC 运行时,它可能会垃圾收集无法通过 垃圾收集根 访问的所有内容。线程是一种根,只要它们是 运行.

就永远不会被垃圾回收

Vaadin 无法知道您对线程的意图,也不知道如何安全地停止它们。因此,您有责任阻止他们。

通常有两种方法可以停止线程,使用 interrupt() 或使用您自己的标志。

您应该使用哪一个取决于您的用例。如果您的线程正在休眠或等待,中断将立即中断它(顾名思义)并抛出 InterruptedException

如果您采用这种方法,请将您的 while(true) 更改为 while(!Thread.interrupted()),然后只需调用 zombie.interrupt() 即可停止它。如果线程当前正在休眠,将到达您的 catch 块,因此如果它是正常行为,您可能不需要记录异常。

注意Thread.interrupted() 会在调用后重置中断标志,因此您不应该,例如,在 catch 块中调用它。如果你想读取状态而不重置它,你可以使用 isInterrupted()

另一种选择是使用您自己的标志。最简单的方法是添加一个 private volatile boolean stopped = false,然后将 while (true) 更改为 while(!stopped)

volatile 关键字确保值不会缓存在 CPU 中,这可能会延迟您的线程注意到值更改所需的时间。

然后您可以添加一个方法来停止它,例如public void stop() { this.stopped = true; },并在会话中调用销毁侦听器。由于这种方法不会中断正在等待的线程,因此它将始终完成循环的当前迭代,如果当前正在休眠,则包括休眠。