当我的基于 akka 的应用程序运行时,有时我会看到此错误:
[ERROR] [2024-01-02 07:30:41.524] [xxxx.dispatcher-22680] Uncaught error from thread [xxxx.dispatcher-22680]: unable to create new native thread, shutting down JVM since 'akka.jvm-exit-on-fatal-error' is enabled for ActorSystem[....]
java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:719)
....
当远程服务不可用时会发生这种情况。
将错误日志映射到代码,我看到在 Actor 内部正在创建一个线程:
val refreshThread = new Thread() {....}
refreshThread.start()
这就是错误发生的地方。
我知道 akka actor 在一个线程上执行,该线程的调度和所有其他管理都由 akka 调度程序负责,从而使开发人员免于陷入线程相关的代码。 但这里在 actor 本身内部创建了一个线程。
akka 是如何管理这些线程的?
我想知道在 akka 上下文中创建这样的自定义线程是否是可接受的做法。
另外,处理这种情况的正确方法是什么?
此类线程根本不会由 Akka 管理,就像 Actor 中的任何资源一样。
这在 Akka 中是可以接受的做法吗?几乎肯定不会:通常有更好的抽象可用。选择哪种抽象取决于您到底希望线程执行什么操作。
例如,
Thread
是一个Runnable
:如果调用start()
,它只会分叉为新线程。您可以从 ActorSystem
获取调度程序(线程池),并让该调度程序在其中一个线程上运行 Runnable
,而不是在新线程上:Akka Classic 和 Akka 之间的确切机制会略有不同打字。请注意,如果沿着这条路径走下去,Thread
不应该创建新线程(因为那时我们基本上回到这里),也不应该执行长时间运行的任务。您可能会发现配置一个调度程序(例如,由 thread-pool-executor
支持的用于阻塞任务的调度程序)对于运行这样的线程很有用。请注意,在线程池上执行事物意味着它们可能要经过一段时间才能真正执行。
另一种选择(可能更理想),是将线程正在执行的操作重构为更小的工作单元,并使用 future 或 actor 等抽象来管理工作单元,但这可能是一次实质性的重构。