22. Executor与线程池:如何创建正确的线程池?- 并发工具类

线程是一个重量级的对象,应该避免频繁创建和销毁。 java SDK提供的线程池不同于其他池化资源,没有提供申请线程和释放线程的方法。

1. 线程池是一种生产者-消费者模式

线程池的使用方是生产者,线程池本身是消费者。以下代码简要说明线程池的原理:

//简化的线程池,仅用来说明工作原理
class MyThreadPool {
	//利用阻塞队列实现生产者-消费者模式
	BlockingQueue<Runnable> workQueue;
	//保存内部工作线程
	List<WorkerThread> threads = new ArrayList<>();

	// 构造方法
	MyThreadPool(int poolSize, BlockingQueue<Runnable> workQueue) {
		this.workQueue = workQueue;
		// 创建工作线程
		for (int idx = 0; idx < poolSize; idx++) {
			WorkerThread work = new WorkerThread();
			work.start();
			threads.add(work);
		}
	}

	// 提交任务
	void execute(Runnable command) {
		workQueue.put(command);
	}

	// 工作线程负责消费任务,并执行任务
	class WorkerThread extends Thread {
		public void run() { // (1)
			// 循环取任务并执行
			while (true) {
				Runnable task = workQueue.take();
				task.run();
			}
		}
	}
}

	/** 下面是使用示例 **/
	// 创建有界阻塞队列
	BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(2);
	// 创建线程池
	MyThreadPool pool = new MyThreadPool(10, workQueue);
	// 提交任务
	pool.execute(()->{
	    System.out.println("hello");
	});

2. 如何使用Java中的线程池

最核心的是ThreadPoolExecutor, 最复杂的构造函数:

ThreadPoolExecutor(int corePoolSize,
                   int maximumPoolSize,
                    long keepAliveTime,
                    TimeUnit unit,
                    BlockingQueue<Runnable> workQueue,
                    ThreadFactory threadFactory,
                    RejectedExecutionHandler handler) 
  • corePoolSize,表示线程池保有的最小线程数;
  • maximumPoolSize,表示线程池创建的最大线程数;
  • keepAliveTime & unit,如果一个线程空闲了keepAliveTime & unit这么久,而且线程池的线程数大于 corePoolSize ,那么这个空闲的线程就要被回收了;
  • workQueue,工作队列,用来储存线程;
  • threadFactory:自定义如何创建线程,例如给线程指定一个有意义的名字;
  • handler:自定义任务的拒绝策略。有四种策略:
    1.CallerRunsPolicy:提交任务的线程自己去执行该任务。
    2.AbortPolicy:默认的拒绝策略,会throws RejectedExecutionException。
    3.DiscardPolicy:直接丢弃任务,没有任何异常抛出。
    4.DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。

3. 使用线程池要注意些什么

不建议使用线程池的静态工厂Executors的最重要的原因是:Executors提供的很多方法默认使用的都是无界的LinkedBlockingQueue,高负载情境下,无界队列很容易导致OOM,而OOM会导致所有请求都无法处理,这是致命问题。所以强烈建议使用有界队列

默认拒绝策略要慎重使用。 如果线程池处理的任务非常重要,建议自定义自己的拒绝策略;并且在实际工作中,自定义的拒绝策略往往和降级策略配合使用。

虽然线程池提供了很多用于异常处理的方法,但是最稳妥和简单的方案还是捕获所有异常并按需处理,你可以参考下面的示例代码。

try {
  //业务逻辑
} catch (RuntimeException x) {
  //按需处理
} catch (Throwable x) {
  //按需处理
} 
发布了97 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_39530821/article/details/102713127