java使用多线程时,主线程等待子现场执行完再执行
1.问题演示
public static void main(String[] args) {
// 创建Runnable实例对象
Runnable runnable = ()->{
System.out.println("我要一个教练");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教练回到了游泳池");
};
for(int i=0;i<20;i++){
Thread thread = new Thread(runnable);
thread.start();
}
System.out.println("************************");
}
执行效果
可以看到,子线程还没全部执行完,主线程已经打印出*********了,这是由于多线程里,主线程与创建的20个子线程是异步执行的。但在web项目中,我们需要主线程等待子线程执行完了,再执行主线程,避免拿到的返回结果不对。
2.Thread直接启动的线程执行方式–采用join,阻塞主线程
public static void main(String[] args) {
// 创建Runnable实例对象
Runnable runnable = ()->{
System.out.println("我要一个教练");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教练回到了游泳池");
};
List<Thread> threads = new ArrayList<>();
for(int i=0;i<20;i++){
Thread thread = new Thread(runnable);
thread.start();
threads.add(thread);
}
//将所有子线程join进主线程
for (Thread t:threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("************************");
}
执行效果:
可以看到*********,在最后才打印了。达到预期效果
注意,千万不要写出这种形式
for(int i=0;i<20;i++){
Thread thread = new Thread(runnable);
thread.start();
thread.join();
}
因为这个for循环也是主线程管理的,在for循环里面执行thread.join();会导致子线程执行完了,才执行第二个for,起第二个子线程,从执行结果来看,子线程就变成同步执行的了;
3.采用线程池–ExecutorService线程池
3.1采用Future的get()方法
public static void main(String[] args) throws Exception {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(5);//包含2个线程对象
// 创建Runnable实例对象
Runnable runnable = ()->{
System.out.println("我要一个教练");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教练回到了游泳池");
};
List<Future> futures = new ArrayList<>();
for(int i=0;i<20;i++){
Future<?> future = service.submit(runnable);
futures.add(future);
}
for (Future f:futures) {
f.get();
}
System.out.println("************************");
}
执行结果:
从执行结果可以发现,主线程的确是等待子线程都执行完了,再执行的主线程,各个子线程之间也是异步执行。
注意:和直接用Thread一样,Future 的get方法也不能用在第一个for里面,以免阻塞了第一个for的正常循环,把子线程搞成了同步执行的效果
for(int i=0;i<5;i++){
Future<?> future = service.submit(runnable);
//这种写法是错误的,这个for循环不能被阻塞,不然会导致会导致第一个子线程执行完了,才执行第二个for,起第二个子线程,
// 从执行结果来看,子线程就变成同步执行的了
future.get();
}
3.2 CountDownLatch
public static void main(String[] args) throws Exception {
final CountDownLatch cdl = new CountDownLatch(5);//参数为线程个数
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(5);//包含2个线程对象
// 创建Runnable实例对象
Runnable runnable = ()->{
System.out.println("我要一个教练");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,交完后,教练回到了游泳池");
cdl.countDown();//此方法是CountDownLatch的线程数-1
};
for(int i=0;i<5;i++){
Future<?> future = service.submit(runnable);
}
try {
cdl.await();//需要捕获异常,当其中线程数为0时这里才会继续运行
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("************************");
}
执行结果:
主线程的*******是最后打印的,子线程也是异步执行的,这种方法可行,但有个缺点,就是需要提前确定线程数,不等同与线程池初始化的线程数,这个线程数需要与for循环的执行次数对应,当然大部分情况下,这个数字是可以确认好的。但极少情况下无法确认