线程池
1. 线程池消耗
Java创建对象,在堆内存中分配一块内存空间即可。而创建线程,需要调用操作系统内核的API,然后操作系统要为线程分配一系列资源,成本很高!
**线程是一个重量级对象,应避免频繁地创建销毁。**所以线程池方案很好。
2. 线程池其实是一种生产者消费者模式
线程池,你第一时间想到的是池化资源,就是需要使用的时候申请资源,用完释放资源,但是线程池不是这样的,也不能这样。
线程池其实是一种生产者-消费者模式,线程的使用方是生产者(生产任务task),线程池本身是消费者(消费任务task)。
2.1手动实现线程池
原理:线程池中添加一个阻塞队列,其中存放要执行的任务Runable,线程池构造方法时创建规定数量的Thread线程放入线程池列表中,并且start启动,内部run()函数while死循环不断从阻塞队列中取出任务执行。
//简化的线程池,仅用来说明工作原理
class MyThreadPool{
//阻塞队列存放Runable任务
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() {
//循环取任务并执行
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");});
3. 如何使用Java的线程池?
Java并发包中提供的线程池比我们上面实现的线程池好的多,核心是ThreadPoolExecutor。
3.1 ThreadPoolExecutor构造方法
下面是ThreadPoolExecutor的构造函数。
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
下面来一一介绍线程池的参数:
-
corePoolSize:线程池中最少保有的最小线程数,即使程序没有需要执行的任务,也需要存在几个线程等待执行,如果当前线程存活较多,相应地减少存活线程数。
扫描二维码关注公众号,回复: 10816272 查看本文章- allowCoreThreadTimeOut(boolean value) 方法:项目很闲时,可以把线程都撤走,不留最小线程maximumPoolSize。
-
maximumPoolSize:表示线程池能创建的最大线程数,如果当前存活线程较少,相应增加线程数。
-
keepAliveTime & unit:上面两个参数根据线程在一段时间的执行情况,相应地增加减少线程,keepAliveTime & unit就是来定义这个时间的,如果一个线程空闲了keepAliveTime & unit这么久,而且线程数>corePoolSize,这个空闲的线程就会被收回。
-
workQueue:工作队列,和上面手动实现线程池的队列同义,存放要执行的任务。
-
threadFactory:可以根据这个自定义如何创建线程,例如给线程指定一个有意义的名字。
-
handler:可以通过这个自定义任务的拒绝策略,当线程池中所有线程都在执行,并且工作队列中的任务也满了,此时在有任务提交时,线程池就可以拒绝接受任务,而拒绝的策略,可以用handler来指定。
ThreadPoolExecutor提供的四种策略:- CallerRunsPolicy:提交任务的线程自己去执行该任务。
- AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。
- DiscardPolicy:直接丢弃任务,没有任何异常抛出。
- DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。
3.2 线程池的使用注意事项
- 开发中尽量使用ThreadPoolExecutor,不要使用Executors快速创建线程池。
- 强烈建议使用有界队列。
- 默认拒绝策略,抛出异常,慎重使用,
- 异常处理:如ThreadPoolExecutor对象的executor方法,提交任务,如果任务执行期间发生异常,会导致线程终止,从而任务异常了,但是接收不到任何异常通知,让你误以为所有任务都正常执行。所以最稳妥的方法还是捕获异常。
try {
//业务逻辑
} catch (RuntimeException x) {
//按需处理
} catch (Throwable x) {
//按需处理
}