使用线程池的shutdown()使主线程等待子线程执行完所有任务是错误的

问题

public void test() {
	for (Data data : dataList) {
	  executorService.submit(() -> {
	     	handle(data);//执行业务代码
	   });
	}
	//关闭线程池
	repaireExecutorService.shutdown();
	try {
	  //想要让主线程等待子线程执行完
	  executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
	
	} catch (InterruptedException e) {
	   LOGGER.error("销毁修复任务线程失败!", e);
	   throw new IllegalStateException(e);
	}
}

结果

子线程可能执行不完所以的任务就会被关闭。shutdown源码,如下会先广播状态,并执行中断任务。在这个过程中子线程只可能完成手上的任务然后就被关闭,没法再去阻塞队列中取出任务。可以看interruptIdleWorkers()源码

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);//广播状态
        interruptIdleWorkers();//中断任务
        onShutdown();//默认是空方法
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

主线程给所有的子线程发送中断信号,而子线程当执行完手上的任务的时候就会先去检测是否已经收到了中断信号(其实就是一个布尔型的值,是否被设置了),如果已经检测到中断信号了,则不会去阻塞队列中取出新的任务,所以所有任务是不会执行完毕的。

private void interruptIdleWorkers(boolean onlyOne) {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers) {
            Thread t = w.thread;
            if (!t.isInterrupted() && w.tryLock()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                } finally {
                    w.unlock();
                }
            }
            if (onlyOne)
                break;
        }
    } finally {
        mainLock.unlock();
    }
}

解决方法

使用CountDownLatch

CountDownLatch有一个计数器,子线程负责将计数器减一,主线程负责调用await()等待,当某个子线程将计数器减为0的时候,就会唤醒主线程

public void test() {
	//创建dataList任务总数的countDownLatch计数器
	CountDownLatch countDownLatch = new CountDownLatch(dataList.size());        
	for (Data data : dataList) {
	   executorService.submit(() -> {
	   		try {
	   		   handle(data);//执行业务代码
	   		} finally {
	         countDownLatch.countDown();
	        }
	      	
	    });
	}
	try {
	    //等待计数器归零
	    countDownLatch.await();
	} catch (InterruptedException e) {
	    e.printStackTrace();
	}
}

使用Future.get()

Future.get()时是阻塞的,会等待子线程执行完毕主线程才能获取到结果,相当于主线程在等待该子线程。

public void test() {
	//创建Future列表,用于存放执行完的结果获取器
	List<Future<String>>  list = new ArrayList();
	for (Data data : dataList) {
	   executorService.submit(() -> {
	        Future<String>> future = handle(data);//执行业务代码
	   		list.add(future);
	    });
	}
	
	for (Future<String> future : list) {
		String str = future.get();//在这里开始主线程就会进行等待
	}
}

你可能感兴趣的:(线程池,并发编程,java,多线程,线程池)