背景描述
最近写业务代码时,意外地发现,当CyclicBarrie预定的拦截线程数超过线程池核心数时,由线程池开启且调用了CyclicBarrie对象await()方法的子线程,将一直陷入等待。
简单来说就是,
当new CyclicBarrie(int parties)的parties参数大于线程池的corePoolSize就会出现如上情况。
场景复现
线程池工具类代码
public class ThreadPoolUtil {
private static ExecutorService executorService;
static {
// 初始化加载
initExecuteService();
}
private static void initExecuteService() {
try {
Properties properties = new Properties();
properties.load(ThreadPoolUtil.class.getClassLoader().getResourceAsStream("threadPool.properties"));
if (properties == null) {
throw new RuntimeException("threadPool参数读取异常:配置文件无法加载");
}
// int corePoolSize = Integer.parseInt((String)properties.get("thread.pool.corePoolSize"));
// int maxPoolSize = Integer.parseInt((String)properties.get("thread.pool.maxPoolSize"));
// 直接获取JVM可用核心数
int corePoolSize = Runtime.getRuntime().availableProcessors();
int maxPoolSize = 2 * corePoolSize;
// 非核心线程最大空闲生存时间
long keepAliveSeconds = Long.parseLong((String) properties.get("thread.pool.keepAliveSeconds"));
int queueCapacity = Integer.parseInt((String) properties.get("thread.pool.queueCapacity"));
String threadFactoryName = (String) properties.get("thread.pool.threadFactoryName");
// 线程池创建
executorService = new ThreadPoolExecutor(corePoolSize, maxPoolSize, keepAliveSeconds, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(queueCapacity), new MyThreadFactory(threadFactoryName));
} catch (IOException var1) {
throw new RuntimeException("threadPool参数读取异常:加载配置文件threadPool.properties失败", var1);
}
}
public static ExecutorService getExecutorService() {
if (executorService == null) {
initExecuteService();
}
return executorService;
}
}
ps:线程池核心数采用JVM可用核心数
// 本人本机JVM可用线程数为4
int corePoolSize = Runtime.getRuntime().availableProcessors();
业务实现代码
public class CyclicBarrierTest01 {
private final static Logger LOGGER = LoggerFactory.getLogger(CyclicBarrierTest01.class);
public static void main(String[] args) {
int threadNum = 2;
LOGGER.info("JVM可用核心数【{}】,demo即将启用线程数【{}】", Runtime.getRuntime().availableProcessors(), threadNum);
// 栅栏预定threadNum个线程数量,等待线程达到threadNum个后将执行此runnable
CyclicBarrier cyclicBarrier = new CyclicBarrier(threadNum, ()-> LOGGER.info("等待线程已满足{}个,放行", threadNum));
ExecutorService executorService = ThreadPoolUtil.getExecutorService();
for(int i=0;i<threadNum;i++){
final int j = i+1;
executorService.execute(()->{
try {
LOGGER.info("从线程[{}]正在执行逻辑...1", Thread.currentThread().getName());
Thread.sleep(j*1000);
// 当前线程等待其他子线程到达栅栏
cyclicBarrier.await();
LOGGER.info("从线程[{}]正在执行逻辑...2", Thread.currentThread().getName());
Thread.sleep(j*1000);
// 当前线程等待其他子线程到达栅栏
cyclicBarrier.await();
LOGGER.info("从线程[{}]正在执行逻辑...3", Thread.currentThread().getName());
// 当前线程等待其他子线程到达栅栏
cyclicBarrier.await();
} catch (InterruptedException e) {
LOGGER.error("睡眠异常", e);
} catch (BrokenBarrierException e) {
LOGGER.error("栅栏已被冲破", e);
}
});
}
executorService.shutdown();
}
}
业务实现代码中,开启线程数暂时定为2,本机JVM可用核心数为4,
此时new CyclicBarrier(2),小于线程池核心数(本机JVM可用核心数),模拟正常执行。
启动main方法,查看结果如下:
16:56:05.651 [main] INFO CyclicBarrierTest01 - JVM可用核心数【4】,demo即将启用线程数【2】
16:56:05.766 [shangHaiPool-1-thread-1] INFO CyclicBarrierTest01 - 从线程[shangHaiPool-1-thread-1]正在执行逻辑...1
16:56:05.767 [shangHaiPool-1-thread-2] INFO CyclicBarrierTest01 - 从线程[shangHaiPool-1-thread-2]正在执行逻辑...1
16:56:07.780 [shangHaiPool-1-thread-2] INFO CyclicBarrierTest01 - 等待线程已满足2个,放行
16:56:07.780 [shangHaiPool-1-thread-1] INFO CyclicBarrierTest01 - 从线程[shangHaiPool-1-thread-1]正在执行逻辑...2
16:56:07.780 [shangHaiPool-1-thread-2] INFO CyclicBarrierTest01 - 从线程[shangHaiPool-1-thread-2]正在执行逻辑...2
16:56:09.781 [shangHaiPool-1-thread-2] INFO CyclicBarrierTest01 - 等待线程已满足2个,放行
16:56:09.782 [shangHaiPool-1-thread-1] INFO CyclicBarrierTest01 - 从线程[shangHaiPool-1-thread-1]正在执行逻辑...3
16:56:09.782 [shangHaiPool-1-thread-2] INFO CyclicBarrierTest01 - 从线程[shangHaiPool-1-thread-2]正在执行逻辑...3
16:56:09.782 [shangHaiPool-1-thread-2] INFO CyclicBarrierTest01 - 等待线程已满足2个,放行
程序正常执行,此时将threadNum修改为5,本机JVM可用核心数为4,
此时new CyclicBarrier(5),大于线程池核心数(本机JVM可用核心数),模拟异常执行。
int threadNum = 5;
LOGGER.info("JVM可用核心数【{}】,demo即将启用线程数【{}】", Runtime.getRuntime().availableProcessors(), threadNum);
启动main方法,查看结果如下:
此时,子线程全部陷入等待,线程池由于存在任务未结束则不会被资源关闭。