【问题标题】:Why does ScheduledExecutorService.shutdown() uses 100% of my CPU?为什么 ScheduledExecutorService.shutdown() 使用 100% 的 CPU?
【发布时间】:2012-05-19 23:50:41
【问题描述】:

我有以下简单的代码:

package main;

import java.util.concurrent.*;

public class Main {

    public static void main(String[] args) throws InterruptedException {
        new Main();
    }

    public Main() throws InterruptedException {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        executor.schedule(new MyRunnable(), 10, TimeUnit.SECONDS);
        System.out.println("Shutting down...");
        executor.shutdown();
        System.out.println("Awaiting termination...");
        executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES);
        System.out.println("Main finished!");
    }

    private class MyRunnable implements Runnable {
        public void run() {
            System.out.println("Finished running!");
        }
    }

}

实际上,虽然我的真实代码比这要复杂一些,但我可以在这些行中找出问题所在。代码基本上是等待10秒运行runnable然后通知主程序结束。

但是,我注意到在 10 秒的时间里,我的一个核心以 100% 的速度使用。

如果我评论这一行:

executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES);

cpu 核心也以 100% 使用,并且主程序在 Runnable 之前完成。

如果我评论这一行:

executor.shutdown();

cpu 使用正常,但程序无法完成。

如果我将前两行都注释掉,cpu 使用正常,但主程序无法完成。

  1. 我的代码有问题吗?
  2. executor.shutdown(); 做某种忙碌的等待,而不是仅仅禁用提交 新任务?
  3. 还是应该责怪 JVM?

其他细节:

$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) Server VM (build 20.1-b02, mixed mode)

$ uname -a
Linux XPSG 2.6.32-5-686-bigmem #1 SMP Sun May 6 04:39:05 UTC 2012 i686 GNU/Linux

PS:请不要让我使用CountDownLatchnewSingleThreadScheduledExecutor。这与我要问的问题无关。谢谢。

编辑:

这里是java转储:

Full thread dump Java HotSpot(TM) Server VM (20.1-b02 mixed mode):

"pool-1-thread-1" prio=10 tid=0x08780c00 nid=0x32ee runnable [0x6fdcc000]
   java.lang.Thread.State: RUNNABLE
    at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:943)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
    at java.lang.Thread.run(Thread.java:662)

"Low Memory Detector" daemon prio=10 tid=0x0874dc00 nid=0x32ec runnable [0x00000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" daemon prio=10 tid=0x0874c000 nid=0x32eb waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" daemon prio=10 tid=0x0874a000 nid=0x32ea waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=10 tid=0x08748800 nid=0x32e9 waiting on condition [0x00000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=10 tid=0x0873a000 nid=0x32e8 in Object.wait() [0x70360000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x9e8f1150> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)
    - locked <0x9e8f1150> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:134)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)

"Reference Handler" daemon prio=10 tid=0x08735400 nid=0x32e7 in Object.wait() [0x703b1000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x9e8f1050> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:485)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
    - locked <0x9e8f1050> (a java.lang.ref.Reference$Lock)

"main" prio=10 tid=0x086b5c00 nid=0x32e3 waiting on condition [0xb6927000]
   java.lang.Thread.State: TIMED_WAITING (parking)
    at sun.misc.Unsafe.park(Native Method)
    - parking to wait for  <0x9e958998> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
    at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:198)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2025)
    at java.util.concurrent.ThreadPoolExecutor.awaitTermination(ThreadPoolExecutor.java:1253)
    at main.Main.<init>(Main.java:19)
    at main.Main.main(Main.java:10)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:58)

"VM Thread" prio=10 tid=0x08731800 nid=0x32e6 runnable 

"GC task thread#0 (ParallelGC)" prio=10 tid=0x086bd000 nid=0x32e4 runnable 

"GC task thread#1 (ParallelGC)" prio=10 tid=0x086be400 nid=0x32e5 runnable 

"VM Periodic Task Thread" prio=10 tid=0x0874fc00 nid=0x32ed waiting on condition 

JNI global references: 931

Heap
 PSYoungGen      total 18752K, used 645K [0x9e8f0000, 0x9fdd0000, 0xb3790000)
  eden space 16128K, 4% used [0x9e8f0000,0x9e991510,0x9f8b0000)
  from space 2624K, 0% used [0x9fb40000,0x9fb40000,0x9fdd0000)
  to   space 2624K, 0% used [0x9f8b0000,0x9f8b0000,0x9fb40000)
 PSOldGen        total 42880K, used 0K [0x74b90000, 0x77570000, 0x9e8f0000)
  object space 42880K, 0% used [0x74b90000,0x74b90000,0x77570000)
 PSPermGen       total 16384K, used 2216K [0x70b90000, 0x71b90000, 0x74b90000)
  object space 16384K, 13% used [0x70b90000,0x70dba198,0x71b90000)

【问题讨论】:

  • 你能在cpu 100%的时候进行线程转储吗?
  • 我知道这并不能回答您的问题,但是当我使用 OpenJDK 1.7.0_03 运行您的程序时,我的核心使用率约为 1%。
  • @JohnVint 我刚刚添加了它。
  • @Jeffrey 哦,这很有趣。看来这可能是某种 JVM 问题。可能 JohnVint 可以通过线程转储帮助解决这个问题(对我来说看起来像中文:)
  • @MostyMostacho 你确定线程转储发生在 10 秒结束之前吗? pool-1-thread-1 在等待其任务时应该处于 TIMED_WAITING 状态,而不是 RUNNABLE(至少对我来说是这样)。

标签: java linux concurrency executor busy-waiting


【解决方案1】:

实际上是在忙着等待。 ThreadPoolExecutor 似乎没有退避逻辑来等待所有任务完成(注意这仅在您 shutdown() 时发生,否则它将正确挂起线程)。

它会不断检查一个任务是否准备好执行,如果没有,它会再次尝试,直到该任务被调度的时间过去。

关闭计划线程池需要权衡(这种权衡是由实现强加的)。它是忙于旋转,直到任务准备好调度或shutdownNow 不会执行队列任务。但是,您可以获取 Runnable 返回的列表并自己执行它们。

【讨论】:

  • 哇。令人难以置信的是,它实际上是通过忙等待来实现的。看起来我的选择是更新到 Java7 或重新实现轮子。后者似乎更有趣:)谢谢约翰!
  • @MostyMostacho 是的,我也有点。似乎它委托给 TPE 的默认实现,它像那样旋转但不会很忙,因为在普通线程池中没有调度概念。很高兴看到它在 Java 7 中得到修复
【解决方案2】:

这是一个特定于平台的问题。当我在我的机器上运行你的测试程序时,在 10 秒的关机时间内 CPU 使用率几乎为零……根据我机器的 CPU 使用率监控。

$ java -version
java version "1.7.0_03"
Java(TM) SE Runtime Environment (build 1.7.0_03-b04)
Java HotSpot(TM) Client VM (build 22.1-b02, mixed mode, sharing)

我粗略地搜索了 Java 错误数据库,但没有出现任何相关信息。

查看网上(Google)上可以找到的不同版本的源代码,很明显getTask方法和朋友在(早期)Java 1.6和(当前) Java 1.7。

我建议您尝试将 JVM 升级到最新的 Java 1.6 或 Java 1.7。或者至少为您的测试程序尝试一下。 (或者只是忍受它。这几乎不是表演的终结者......)


仅供参考,this page 包含有关如何在 Ubuntu 上安装各种 Java 版本的说明。

  • 一个选项(对于 Java 7)是使用“duinsoft”安装程序,这是一个从 Oracle 站点拉取安装程序的脚本。他们甚至设置了一个 deb 存储库来托管安装程序。

  • 另一种选择是安装 11.10 存储库中的 openjdk-7-jdk 或 openjdk-7-jre 软件包。

当您在该地区时,不要忘记对此 RFE 投票至provide the debian package/installer for Java 7.

为了记录,这个混乱主要是 Oracle withdrawing licensing for OEM redistibution 的结果,这意味着“sun-java-6”包必须被撤回。当然,还有 Oracle 不提供 DEB 的“小问题”。

【讨论】:

  • 您好斯蒂芬,感谢您的意见。事实上,这一定是 JVM 的一些问题。但是,我宁愿继续使用当前的 VM,因为我不想离开 Debian 稳定的存储库(无论如何,Java 7 甚至没有进入实验性存储库,AFAIK)。再次感谢!
  • 没有表情来表达我的惊讶:“Oracle 自己将使用 OpenJDK as the basis for their own future releases.!”。
  • @MostyMostacho - 没什么新鲜的。这只是 Oracle 试图“通过”Java “货币化”的另一个例子。 (但如果你需要一个新的表情来表达自己......试试这个...... :-{u )
猜你喜欢
  • 2012-12-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多