在java
创建对象的过程中,创建对象,是在 JVM 的堆里分配一块内存;而创建一个线程,需要调用操作系统内核的 API,然后操作系统要为线程分配一系资源
池化资源与线程池的区别
- 池化资源是指把资源加载到内存当中,通过创建一个池化类统一获取和释放资源
class XXXPool{
// 获取池化资源
XXX acquire() {
}
// 释放池化资源
void release(XXX x){
}
}
- 采用一般意义上池化资源的设计方法
class ThreadPool{
// 获取空闲线程
Thread acquire() {
}
// 释放线程
void release(Thread t){
}
}
// 期望的使用
ThreadPool pool;
Thread T1=pool.acquire();
// 传入 Runnable 对象
T1.execute(()->{
// 具体业务逻辑
......
});
假设我们获取到一个空闲线程T1,然后该如何使用T1呢?通过调用T1的execute方法,传入一个runnable对象来执行具体的业务逻辑,但实际上Thead对象中并没有execute这样的方法,所以只能放弃这种做法。采用之前在FutrueTask一文中提到的阻塞队列为主的生产者-消费者模式
线程池作为一个生产和释放线程的地方,必然是属于消费线程的一方,而线程的使用方则是生产者。实现一个简单的线程池如下
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() {
// 循环取任务并执行
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");
});
图中的 product 是往内部队列里写消息的生产者,并不是往这个 Consumer 所在的线程池中写任务的生产者。因为即便 Consumer 是一个单线程的线程池,它依然具有一个常规线程池所具备的所有条件:
- Worker 调度线程,也就是线程池运行的线程;虽然只有一个。
- 内部的阻塞队列;虽然长度只有1。
没有生产者往里边丢任务是指consumer放大后的那一块,也就是内部队列并没有其他线程往里边丢任务执行 execute() 方法。
而一旦发生未捕获的异常后,Worker1 被回收,顺带的它所调度的线程 task1(这个task1 也就是在执行一个 while 循环消费左图中的那个队列) 也会被回收掉。
新创建的 Worker2 会取代 Worker1 继续执行 while 循环从内部队列里获取任务,但此时这个队列就一直会是空的,所以也就是处于 Waiting 状态。
所以在高负载条件下,当队列内的任务一多,就会发生内存溢出。建议使用有界队列,当任务过多时,线程池会采用拒绝策略。线程池默认的拒绝策略会 throw RejectedExecutionException 这是个运行时异常,对于运行时异常编译器并不强制 catch 。所以尽量使用自定义异常策略
此外,通过 ThreadPoolExecutor 对象的ThreadPoolExecutor的execute方法提交任务时,如果任务执行过程中抛出异常,会导致线程终止
故做如下处理:
try {
// 业务逻辑
} catch (RuntimeException x) {
// 按需处理
} catch (Throwable x) {
// 按需处理
}