一.单独创建线程带来的问题
对于服务端的程序,经常面对的是客户端传入的短小(执行时间短、工作内容较为单一)
任务,需要服务端快速处理并返回结果。如果服务端每次接受到一个任务,创建一个线程,然后进行执行,这在原型阶段是个不错的选择,但是面对成千上万的任务递交进服务器时,如果还是采用一个任务一个线程的方式,那么将会创建数以万记的线程,这不是一个好的选择。因为这会使操作系统频繁的进行线程上下文切换,无故增加系统的负载,而线程的创建和消亡都是需要耗费系统资源的,也无疑浪费了系统资源。
线程池技术能够很好地解决这个问题,它预先创建了若干数量的线程,并且不能由用户
直接对线程的创建进行控制,在这个前提下重复使用固定或较为固定数目的线程来完成任务的执行。这样做的好处是,一方面,消除了频繁创建和消亡线程的系统资源开销,另一方面,面对过量任务的提交能够平缓的劣化。
二.实现线程池
先看一个简单的线程池接口定义
//实现一个简单版的线程池
public interface ThreadPool<Job extends Runnable> {
//执行Job,job需要Runnable
void execute(Job job);
//关闭线程池
void shutdown();
//添加工作者线程
void addWorkers(int addNum);
//减少工作者线程
void removeWorker(int removeNum);
//获取任务数量
int getJobSize();
}
客户端可以通过execute(Job)方法将Job提交入线程池执行,而客户端自身不用等待Job的
执行完成。除了execute(Job)方法以外,线程池接口提供了增大/减少工作者线程以及关闭线程池的方法。这里工作者线程代表着一个重复执行Job的线程,而每个由客户端提交的Job都将进入到一个工作队列中等待工作者线程的处理。
接下来是线程池接口的默认实现
public class DefaultThreadPool<Job extends Runnable> implements ThreadPool<Job> {
private static final int MAX_POOL_NUM=10; //线程次最大线程数
private static final int MIN_POOL_NUM=1; //线程池最小线程数
private static final int DEFAULT_POOL_NUM=5; //默认线程数
//工作者列表
private CopyOnWriteArrayList<Worker> workers=new CopyOnWriteArrayList<Worker>();
//一个工作列表,将会向里面插入工作
private LinkedList<Job> jobs=new LinkedList<Job>();
private AtomicLong incr=new AtomicLong(0);
public DefaultThreadPool(){
initThreadPool(DEFAULT_POOL_NUM);
}
public DefaultThreadPool(int threadPoolNum){
initThreadPool(threadPoolNum);
}
//初始化线程工作者
private void initThreadPool(int threadPoolNum) {
//确定线程数量
int initThreadNum=threadPoolNum>=MIN_POOL_NUM&&threadPoolNum<=MAX_POOL_NUM?
threadPoolNum:(threadPoolNum<0?MIN_POOL_NUM:MAX_POOL_NUM);
for (int i=0;i<initThreadNum;i++){
Worker worker=new Worker();
workers.add(worker);
new Thread(worker,"ThreadPool-Work-"+incr.incrementAndGet()).start();
}
}
public void execute(Job job) {
if (job==null){
throw new IllegalArgumentException();
}
//添加一个工作,然后通知
synchronized (workers){
jobs.addLast(job);
workers.notify();
}
}
public void shutdown() {
for(Worker worker:workers){
worker.shutdown();
}
}
public void addWorkers(int addNum) {
synchronized (workers){
//限制新增的Worker数量不能超过最大值
addNum=addNum+workers.size()>MAX_POOL_NUM?MAX_POOL_NUM-workers.size():addNum;
initThreadPool(addNum);
}
}
public void removeWorker(int removeNum) {
if (removeNum>=workers.size()){
throw new IllegalArgumentException("beyond workNum");
}
//按照给定的数量停止Worker
synchronized (workers){
for (int i=0;i<removeNum;i++){
Worker worker = workers.get(i);
boolean isSuccess = workers.remove(worker);
if (isSuccess){
worker.shutdown();
}
}
}
}
public int getJobSize() {
return jobs.size();
}
//工作者,负责消费任务
class Worker implements Runnable{
// 是否工作
private volatile boolean running=true;
public void run() {
while (running){
synchronized (workers){
// 如果工作列表是空的,那么就wait
while (jobs.isEmpty()){
try {
workers.wait();
} catch (InterruptedException e) {
// 感知到外部对WorkerThread的中断操作,返回
Thread.currentThread().interrupt();
return;
}
}
try {
//取出一个job
Job job=jobs.removeFirst();
if (job!=null){
job.run();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//停止线程
public void shutdown(){
running=false;
}
}
}
从线程池的实现可以看到,当客户端调用execute(Job)方法时,会不断地向任务列表jobs中添加Job,而每个工作者线程会不断地从jobs上取出一个Job进行执行,当jobs为空时,工作者线程进入等待状态。
添加一个Job后,对工作队列jobs调用了其notify()方法,而不是notifyAll()方法,因为能够
确定有工作者线程被唤醒,这时使用notify()方法将会比notifyAll()方法获得更小的开销(避免将等待队列中的线程全部移动到阻塞队列中)。
可以看到,线程池的本质就是使用了一个线程安全的工作队列连接工作者线程和客户端
线程,客户端线程将任务放入工作队列后便返回,而工作者线程则不断地从工作队列上取出工作并执行。当工作队列为空时,所有的工作者线程均等待在工作队列上,当有客户端提交了一个任务之后会通知任意一个工作者线程,随着大量的任务被提交,更多的工作者线程会被唤醒。