问:为什么要使用线程池?
答:线程的创建和销毁,包括不同线程之间争抢时间片的操作,中间设计到用户态和内核态之间的切换,是一个非常消耗资源的操作。线程池可以看作是一个将线程缓存下来的地方,使这些线程可以被重复利用,从而大大减少资源的消耗。同时也能提高响应速度,当任务来后无需等待线程创建就能立即执行。
Java四种线程池
名称 | 特点 | 使用场景 |
---|---|---|
ThreadPoolExccutor(默认线程池) | 最基础的线程池,所有其他线程池都直接获简介通过它实现 | |
FixedThreadPool(固定线程池) | 每次提交一个任务就创建一个线程,直到线程达到线程池最大值 | 用于负载比较重的服务器,为了资源的合理利用,需要限制线程的数量 |
CachedThreadPool(弹性缓存线程池) | 创建之初里面一个线程都没有,当execute方法获submit方法向线程池提交任务时,会自动创建新线程 | 用于并发执行大量短期的小任务,或者是负载较轻的服务器。 |
SingleThreadPool(单线程线程池) | 池中只有一个线程,作用是保证任务的顺序执行 | 用于串行执行任务的场景,每个任务必须按顺序执行,不需要并发执行。 |
ScheduledThreadpool(调度线程池) | 线程池能按时间来执行任务,允许用户设定计划执行任务的时间 | 用于需要多个后台线程执行周期任务,同时需要限制线程数量的场景 |
创建线程池:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
}
corePoolSize:线程池的核心线程数;
maximumPoolSize:线程池的最大线程数;
keepAliveTime:线程池空闲时线程的存活时长;
unit:线程存活时长大单位,结合上个参数使用;
workQueue:存放任务的队列,使用的是阻塞队列:
- ArrayBlockingQueue:是一个基于数组结构的有届阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
- LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞度量高于LinkedBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
- SynchornousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一只处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂Executors.newCacheThreadPool使用了这个队列。
- PriorityBlocingQueue:一个具有优先级的无限阻塞队列。
threadFactory: 线程创建工厂;
handler: 拒绝策略:
- AbortPolicy:直接抛出异常。
- CallerRunsPolicy:只用调用者所在线程来运行任务。
- DiscardOldestPolicy:丢弃队列最近的一个任务,并执行当前任务。
- DiscardPolicy:不处理,丢弃掉。
一个简单的线程池接口定义:
public interface ThreadPool<Job extends Runnable>{
//执行一个Job,这个job需要实现Runnable
void execute(Job job);
//关闭线程池
void shutdown();
//增加工作者线程
void addWorkers(int num);
//减少工作者线程
void removeWorker(int num);
//得到正在等待执行的任务数量
int getJobSize();
}
//工作列表,会向里面插入工作
private final LinkedList<Job> jobs = new LinkedList<Job>();
public void execute(Job job){
if (job != null){
//添加一个工作,然后通知
synchronized (jobs){
jobs.assLast(job);
jobs.notify();
}
}
}
上面添加一个工作后开始调用notify方法,而不是notifyAll方法,因为能够去确定有工作者线程被唤醒,使用notify比notifyAll有更小的开销,因为notifyAll可能会将等待队列中的线程全部移动到阻塞队列中。
线程池的本质就是使用了一个线程安全的工作队列连接工作者线程和客户端线程,客户端线程将任务放入工作队列后便返回,而工作者线程则不断地从工作队列上取出工作并执行。
-- 摘自《Java并发编程艺术》