为什么Java ExecutorService的newSingleThreadExecutor会产生两个线程?

9

下面是一段示例Java代码,如果作为控制台应用程序运行,其表现与我的期望相同(产生一个单独的线程来执行可运行对象)。

当我使用Apache的prunsrv64.exe将此示例作为服务应用程序运行时,我看到的奇怪行为是产生了两个线程(如下所示的示例)。

我在64位Windows 7机器上进行测试。

示例输出:

   Thread -28 Current time: 09:50:11 AM
   Thread -52 Current time: 09:50:12 AM
   Thread -28 Current time: 09:50:21 AM
   Thread -52 Current time: 09:50:22 AM
   Thread -28 Current time: 09:50:31 AM
   Thread -52 Current time: 09:50:32 AM

示例代码:

import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorTest{
    public void testIt(){
        ExecutorService ex = Executors.newSingleThreadExecutor();
        ex.execute(new Runnable(){
            public void run() {
                while(true){
                    System.out.printf("Thread -" + Thread.currentThread().getId() + " Current time: %tr%n", new Date());
                    try{
                        Thread.sleep(10000);    
                    }catch(InterruptedException ie){
                        ie.printStackTrace();
                    }                   
                }

            }
        });     
    }
}

感谢您的选择。 更新: 只是为了澄清,我将按照以下方式调用此代码:
    ExecutorTest tester = new ExecutorTest();
    tester.testIt();

当作为控制台应用程序和服务应用程序运行时,相同的代码在没有更改的情况下表现不同,如我上面所提到的。


更新2: 我添加了第二个测试器,它使用ScheduledExecutorService。 行为是相同的。

更新2输出:

Using ScheduledExecutorService.
Thread Id outside Runnable -1
Thread -53 Current time: 10:58:15 AM
Thread -28 Current time: 10:58:24 AM
Thread -53 Current time: 10:58:25 AM
Thread -28 Current time: 10:58:34 AM
Thread -53 Current time: 10:58:35 AM
Thread -28 Current time: 10:58:44 AM
Thread -53 Current time: 10:58:45 AM
Thread -28 Current time: 10:58:54 AM
Thread -53 Current time: 10:58:55 AM
Thread -28 Current time: 10:59:04 AM
Thread -53 Current time: 10:59:05 AM

更新2代码:

public void testItWithScheduled(){
    System.out.println("Using ScheduledExecutorService.");
    ScheduledExecutorService ex = Executors.newSingleThreadScheduledExecutor();
    System.out.println("Thread Id outside Runnable -" + Thread.currentThread().getId());
    ex.scheduleWithFixedDelay(new Runnable(){
        public void run() {
            System.out.printf("Thread -" + Thread.currentThread().getId() + " Current time: %tr%n", new Date());
        }
    },0L, 10, TimeUnit.SECONDS);        
}


called through:

    ExecutorTest tester = new ExecutorTest();
    tester.testItWithScheduled();

更新3: 修改日志记录以添加身份哈希
Using ScheduledExecutorService.
Thread Id outside Runnable 1 with reference: 1370756928
Thread -53 Current time: 11:10:38 AM with reference: 1370756928
Thread -28 Current time: 11:10:47 AM with reference: 1939972532
Thread -53 Current time: 11:10:48 AM with reference: 1370756928
Thread -28 Current time: 11:10:57 AM with reference: 1939972532
Thread -53 Current time: 11:10:58 AM with reference: 1370756928
Thread -28 Current time: 11:11:07 AM with reference: 1939972532
Thread -53 Current time: 11:11:08 AM with reference: 1370756928

你可能只是调用了两次 testIt 函数? - Dmitry Zaytsev
我假设你的主线程正在运行程序,然后你创建了Executors.newSingleThreadExecutor();这将创建另一个线程,从而导致两个线程。 - Josef E.
如果在 while 循环之前放置 getId() 的 println,您会得到 2 个不同的线程 ID 吗? - brummfondel
2
由于这似乎与您运行代码的方式有关,而不是newSingleThreadExecutor(),请告诉我们更多关于当您将其作为“服务”运行时如何打包和运行此应用程序的信息?您的prunsrv64.exe是否引用了Apache Commons Daemon项目?如果您包括所有信息以让其他人能够重现它(这也意味着包括main()方法的完整源代码)。 - nos
2
似乎有两个服务副本正在运行。 - OldCurmudgeon
2个回答

4
唯一合理的结论是你(或框架)创建了两个ExecutorTest的引用并执行了两次。
在日志中添加对象的identityHashCode
System.out.printf("Thread -" + Thread.currentThread().getId() + " Current time: %tr with reference: %s%n ", new Date(), System.identityHashCode(ExecutorTest.this));

上述代码在控制台应用程序和服务应用程序中运行时表现不同,且没有任何更改。

您可以精确地控制创建了多少个项目。


根据您的第三次更新进行编辑。

我的假设是正确的,对象的System.identityHashCode 类似于其内存位置。 如您所见,这两个值是不同的,但是如果 ExecutorService 正在创建两个线程,则这些值将相同。

这意味着您正在创建多个实例。 也许不是直接由您创建,但框架正在创建多个相同的服务并运行它们。

因此,问题从“为什么执行器服务会创建2个线程”变成了“为什么我的框架会创建两个服务实例”。 对于那个问题,我无法回答。

为了更清楚地阐明,想象一下像这样执行您的测试:

ExecutorTest tester1 = new ExecutorTest();
tester1.testIt();
ExecutorTest tester2 = new ExecutorTest();
tester2.testIt();

那与你的应用程序所发生的情况相似。

似乎创建了第二个引用,但不是在我的代码中:引用:1939972532。 - tint si
从ExecutorService的角度来看,这并不重要。你只需要知道另一个实例确实正在被创建。这两个日志记录语句是从两个不同的实例发生的。 - John Vint
John,我明白你的意思。下一个问题是,为什么第二个实例只影响运行内部。正如您从我的输出中看到的那样,testItWithScheduled()方法只被调用一次。这也应该表明我在代码中没有创建两个实例或调用两次。 - tint si
@tintsi,我明白你的问题所在。如果你看到的是真的,那么就很难得出结论了。我想说这些测试中可能有些东西丢失了。因为我很难相信你有两个独立的ExecutorTest实例,而只有一个被调用了方法。 - John Vint
2
John,感谢你的分析。在进一步查看日志时,我注意到testItWithScheduled()方法使用新引用被调用了两次。 - tint si

1
我实际上已经在我的电脑上尝试了这段代码,只得到了一个线程。以下是代码:
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
public class ExecutorTest{ public void testIt(){ ExecutorService ex = Executors.newSingleThreadExecutor(); ex.execute(new Runnable(){ public void run() { while(true){ System.out.printf("Thread -" + Thread.currentThread().getId() + " Current time: %tr%n", new Date()); try{ Thread.sleep(1000); }catch(InterruptedException ie){ ie.printStackTrace(); } }
} }); } public static void main(String[] args) { ExecutorTest x = new ExecutorTest(); x.testIt(); } }
以下是输出结果:
Thread -10 Current time: 09:50:27 PM
Thread -10 Current time: 09:50:28 PM
Thread -10 Current time: 09:50:29 PM
Thread -10 Current time: 09:50:30 PM
Thread -10 Current time: 09:50:31 PM
Thread -10 Current time: 09:50:32 PM
Thread -10 Current time: 09:50:33 PM
大多数情况下,实例化类的方式可能存在错误。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接