执行器执行两次

时间:2018-08-18 03:18:49

标签: java multithreading executorservice

我正在尝试同时运行2个线程以实现多线程,并且我的程序正在运行,但是我不确定为什么我的程序两次打印到标准输出。

这是我的代码库:

public class SimpleExec {
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(5);
        CountDownLatch countDownLatch6 = new CountDownLatch(5);
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        System.out.println("start" + LocalDateTime.now());
        executorService.execute(new MyThread("first ", countDownLatch));
        executorService.execute(new MyThread("Second", countDownLatch6));

        try {
            countDownLatch.await();
            countDownLatch6.await();
        } catch (InterruptedException e) {
            System.out.println(e);
        }
        System.out.println("end" + LocalDateTime.now());
        executorService.shutdown();

    }
}

class MyThread implements Runnable {
    String name;
    CountDownLatch cdl;

    public MyThread(String name, CountDownLatch cdl) {
        this.cdl = cdl;
        this.name = name;
        new Thread(this).start();
    }

    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + " " + i);
            cdl.countDown();
        }
    }
}

这是程序输出的示例:

start 2018-08-18T08:41:51.867
first  0 // first time thread labeled 'first' prints 0 through 4
first  1
first  2
first  3
first  4
Second 0
Second 1
Second 2
first  0   // second time thread labeled 'first' prints 0 through 4 - why does it print again here?
first  1
Second 3
first  2
Second 4
first  3
first  4
end2018-08-18T08:41:51.870
Second 0
Second 1
Second 2
Second 3
Second 4

3 个答案:

答案 0 :(得分:4)

因为您使用new Thread(this).start();

为构造函数中的每个Runnables启动了第二个线程

Runnables是从ExecutorService开始的,不需要额外的Thread.start()即可将其删除。

答案 1 :(得分:2)

之所以会发生这种情况,是因为您在ExecutorService外部生成了两个线程,并在这些上执行了Runnable实现,此外还提交了它们以便在单个线程上执行与您的ExecutorService 相关联。

删除new Thread(this).start();,您只会看到一次打印输出。但是,由于您使用newFixedThreadPool(1),因此有效地意味着您的程序将按顺序运行。

答案 2 :(得分:0)

让执行程序服务管理线程

正如其他答案所说的那样,您不必要地创建和管理线程。这正是执行服务的工作,它通过代表您管理线程的创建,使用和销毁来简化并发性。

示例应用

简化您的main方法:

ExecutorService executorService = Executors.newFixedThreadPool( 1 );
executorService.execute( new ZeroToFourCounter( "first " ) );
executorService.execute( new ZeroToFourCounter( "Second" ) );
executorService.shutdown();

我相信您无需使用CountDownLatch。该类用于使一个或多个线程等待,直到其他线程中的一组操作完成。但是,由于只有一个线程的线程池,任务将按照提交的顺序完成。因此无需协调。

将您的Runnable实现的run方法更改为仅完成其预期的工作,循环计算1-4。

for ( int i = 0 ; i < 5 ; i++ ) {
    System.out.println( this.name + " " + i + " at " + Instant.now() );
}

无需在您的Runnable的构造函数中产生线程。您要求执行者服务为您管理线程池。

完整的应用程序代码:

打包com.basilbourque.example;

import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecDemo {

    public static void main ( String[] args ) {
        System.out.println( "DEMO - Running `main`. " + Instant.now() );
        ExecutorService executorService = Executors.newFixedThreadPool( 1 );
        executorService.execute( new ZeroToFourCounter( "first " ) );
        executorService.execute( new ZeroToFourCounter( "Second" ) );
        System.out.println( "DEMO - In `main`, asking executorService to shutdown(). " + Instant.now() );
        executorService.shutdown();
        System.out.println( "DEMO - Ending `main`. " + Instant.now() );
    }
}

class ZeroToFourCounter implements Runnable {
    private String name;

    public ZeroToFourCounter ( String name ) {
        this.name = name;
    }

    @Override
    public void run () {
        for ( int i = 0 ; i < 5 ; i++ ) {
            System.out.println( this.name + " " + i + " at " + Instant.now() );
        }
    }
}

运行时:

  

DEMO-运行main。 2018-08-18T08:20:42.916806Z

     

DEMO-在main中,要求executorService关闭()。 2018-08-18T08:20:42.986941Z

     

DEMO-结尾main。 2018-08-18T08:20:42.987724Z

     

在2018-08-18T08:20:42.987058Z前0个

     

2018年1月18日第一08:20:43.027013Z

     

2018年2月18日第一08:20:43.027090Z

     

2018年3月18日前08:08:20:43.027184Z

     

在2018-08-18T08:20:43.027259Z前4个

     

第二个0于2018-08-18T08:20:43.027413Z

     

第二个日期为2018-08-18T08:20:43.027544Z

     

第二秒于2018-08-18T08:20:43.027673Z

     

第二个3于2018-08-18T08:20:43.027784Z

     

第二次更新于2018-08-18T08:20:43.027891Z

有趣的是……请注意这些行是不是的时间顺序! first 0行比上一行捕获了一个更早时刻。

请注意,调用ExecutorService::shutdown不会中断工作线程。该调用是对执行者的请求,要求(a)停止接受新工作,并且(b)完成当前工作。引用文档:

  

启动有序关闭,在该关闭中执行先前提交的任务,但不接受任何新任务。如果已关闭,则调用不会产生其他影响。

     

此方法不等待先前提交的任务完成执行。使用awaitTermination可以做到这一点。

注意第二部分。对ExecutorService::shutdown的呼叫不会阻塞。它也不检查执行程序服务是否关闭。考虑是否最好按照建议的方法调用awaitTermination以获得阻塞的行为,直到执行程序服务在超时之前完成或未能完成。

不是LocalDateTime

顺便说一句,LocalDateTime类是在此处使用错误的日期时间类。缺少任何时区或UTC偏移量的概念,该类不能表示实际时刻。而是使用InstantZonedDateTime

我了解您只是在进行一些快速而肮脏的日志记录,因此这并不重要。但是暂时使用LocalDateTime是一个坏习惯,以后可能会再次困扰您。

Instant.now()       // Capture current moment in UTC.
ZonedDateTime.now() // Capture the current moment in the JVM’s current default time zone.

通常最好养成使用UTC进行大多数日志记录,存储和交换时刻的习惯。