我正在尝试同时运行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
答案 0 :(得分:4)
因为您使用new Thread(this).start();
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.916806ZDEMO-在
main
中,要求executorService关闭()。 2018-08-18T08:20:42.986941ZDEMO-结尾
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偏移量的概念,该类不能表示实际时刻。而是使用Instant
或ZonedDateTime
。
我了解您只是在进行一些快速而肮脏的日志记录,因此这并不重要。但是暂时使用LocalDateTime
是一个坏习惯,以后可能会再次困扰您。
Instant.now() // Capture current moment in UTC.
ZonedDateTime.now() // Capture the current moment in the JVM’s current default time zone.
通常最好养成使用UTC进行大多数日志记录,存储和交换时刻的习惯。