第八章、线程池的原理及自定义线程池
8.1 线程池的原理
我们知道,Thread是一个重量级的资源,创建、启动以及销毁都是比较耗费系统资源的,因此对于线程的重复利用是一种非常好的程序设计习惯,加上系统可创建的线程数量是有限的,线程数量和系统性能是一种抛物线的关系,所以我们要学会对线程进行管理。
- JDK1.5起,utils包提供了ExecutorService线程池的实现,主要的目的就是为了重复利用线程,提高系统效率。
那到底什么是线程池呢?如何实现一个线程池呢?
线程池的概念
线程池就是一个池子,里面存放已经创建好的线程,当有任务提交给线程池执行任务时,池子中的某个线程会主动执行该任务。如果池子里面的线程池数量不够而无法应付数量众多的任务时,则需要自动扩充新的线程到池里,但是该数量时有限的,它存在一个最大的线程数量值,不允许超过。当任务比较少时,池子里的线程能够自动回收,释放资源。那么要实现这个线程管理需要什么呢?
- 任务队列:用于缓存提交的任务
- 线程数量管理功能:一个线程池必须能很好地管理和控制线程的数量,可以使用三个参数来控制——1、创建线程池的初始线程数量init;2、线程池自动扩充时最大容许的线程数量max;3、线程池空闲时需要释放线程但是又要维护一定数量的活跃线程作为核心线程数量core。三者的关系如图所示:
- 任务拒接策略:如果线程数量到达了上限而且任务队列已满,则需要有相应的拒绝策略来通知任务提交者。
- 线程工厂:主要用于个性化定制线程,比如将线程设置为守护线程及设置线程名称等。
- QueueSize:任务队列主要存放提交的Runnable(封装了线程任务的),但是为了防止内存溢出,需要有limit数量对其进行控制。
- Keepedalive时间:该时间主要决定了线程各个重要参数自动维护的时间间隔。
8.2 线程池的实现
我们必须掌握线程池如何开发的,这里实现一个简单的ThreadPool,该有的功能都有。我们先来看实现类图:
每个类的解析和代码:
-
ThreadPool类:线程池接口,定义了线程池应该具备的操作和方法。
package chapter8_ThreadPool; /** * 线程池接口,定义了线程池应该具备的操作和方法。 */ public interface ThreadPool { //提交任务到线程池 void execute(Runnable runnable); //关闭线程池并销毁线程 void shutdown(); //获取初始化线程池的大小 int getInitSize(); //获取线程池最大数量 int getMaxSize(); //获取线程池核心线程数量 int getCoreSize(); //获取线程池中用于缓存任务队列的大小 int getQueueSize(); //获取线程池中活跃线程数量 int getActiveCount(); //判断是否关闭 boolean isShutdown(); }
-
RunnableQueue类:RunnableQueue用于存放提交的Runnable 即任务。它必须有一个limit数量限制。而且是一个BlockedQueue
package chapter8_ThreadPool;
/**
* RunnableQueue用于存放提交的Runnable 即任务。
* 它必须有一个limit数量限制。而且是一个BlockedQueue
*/
public interface RunnableQueue {
//当有新的任务进来时首先会offer到队列里面。提交任务函数
void offer(Runnable runnable);
//工作的线程通过take方法获取Runnable
Runnable take();
//返回当前队列的Runnable的数量。
int size();
}
-
ThreadFactory类:ThreadFactory为一个提供创建线程的接口,以便于个性化定制Thread,如守护线程,优先级,线程名字,对于的线程组等。
package chapter8_ThreadPool; /** * ThreadFactory为一个提供创建线程的接口,以便于个性化定制Thread,如守护线程,优先级,线程名字,对于的线程组等。 */ public interface ThreadFactory { Thread createThread(Runnable runnable);//创建线程 }
-
DenyPolicy接口:用于当Queue的任务达到了上限时决定采取什么策略来通知执行者。
package chapter8_ThreadPool; public interface DenyPolicy { //拒绝策略 void reject(Runnable runnable, ThreadPool threadPool); //策略一,放弃策略 class DiscardDenyPolicy implements DenyPolicy { @Override public void reject(Runnable runnable, ThreadPool threadPool) { //拒绝的策略,做法 } } //策略二:抛出异常信息 throw new RunnableDenyException("The runnable "+runnable+" will be abort!"); class AbortDenyPolicy implements DenyPolicy { @Override public void reject(Runnable runnable, ThreadPool threadPool){ throw new RunnableDenyException("The runnable "+runnable+" will be abort!"); } } //策略三:交给调用者的线程直接运行runnable,而不是被加入线程池中。 class RunnerDenyPolicy implements DenyPolicy { @Override public void reject(Runnable runnable, ThreadPool threadPool){ if(!threadPool.isShutdown()){ runnable.run(); } } } }
-
RunnableDenyException类:自定义异常类,表示无法接受新任务。
package chapter8_ThreadPool; /** * 自定义异常类,表示无法接受新任务。 */ public class RunnableDenyException extends RuntimeException { public RunnableDenyException(String message) { super(message); } }
-
InternalTask类:是Runnable的一个子类,用于线程池内部,该类会使用到RunnableQueue,然后不断地从queue取出某个runnable,并运行其run方法
package chapter8_ThreadPool; /** * InternalTask是Runnable的一个实现,主要用于线程池内部,该类会使用到RunnableQueue, * 然后不断地从queue取出某个runnable,并运行其run方法 */ public class InternalTask implements Runnable { private final RunnableQueue runnableQueue; private volatile boolean running = true; public InternalTask(RunnableQueue runnableQueue){ this.runnableQueue = runnableQueue; } @Override public void run() { //如果当前任务为running而且没有被中断,则其将不断地从queue中获取runnable,然后执行其run方法 while(running && !Thread.currentThread().isInterrupted()){ try{ Runnable task = runnableQueue.take(); task.run(); }catch (InterruptedException e){ running = false; break; } } } //开关方法,停止当前任务,主要在线程池的shutdown中使用 public void stop(){ this.running = false; } }
8.2.1 线程池的详细实现
-
LinkedRunnableQueue任务队列:
package chapter8_ThreadPool; import java.util.LinkedList; public class LinkedRunnableQueue implements RunnableQueue { //任务队列的最大容量,构造时传入 private final int limit; //若任务队列中的任务已经满了,则需要执行拒绝策略 private final DenyPolicy denyPolicy; //存放任务的队列 private final LinkedList<Runnable> runnableLinkedList = new LinkedList<>(); private final ThreadPool threadPool; public LinkedRunnableQueue(int limit, DenyPolicy denyPolicy, ThreadPool threadPool) { this.limit = limit; this.denyPolicy = denyPolicy; this.threadPool = threadPool; } @Override public void offer(Runnable runnable) { synchronized (runnableLinkedList){ if(runnableLinkedList.size()>=limit){ //无法容纳新的任务了。执行拒绝策略 denyPolicy.reject(runnable,threadPool); }else { //将任务加入队尾,并唤醒阻塞中的线程 runnableLinkedList.addLast(runnable); runnableLinkedList.notifyAll(); } } } @Override public Runnable take() throws InterruptedException { synchronized (runnableLinkedList){ while(runnableLinkedList.isEmpty()){ try{ //如果队列没有可执行的任务,则当前线程挂起,进入runnbleList关联的monitor wait set中等待唤醒 runnableLinkedList.wait(); } catch (InterruptedException e) { //被中断的时候要抛出 throw e; //e.printStackTrace(); } } //否则从任务队头取出一个任务 return runnableLinkedList.removeFirst(); } } @Override public int size() { //返回当前任务数 return runnableLinkedList.size(); } }
-
初始化线程池类BasicThreadPool:根据前面的讲解,线程池需要有数量控制属性,创建线程工厂、任务队列策略等功能,所以需要一个初始化的类。
package chapter8_ThreadPool; import java.util.ArrayDeque; import java.util.Queue; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; /** * BasicThreadPool是Thread的子类,采用继承的方法 * 但是这并不是一个很好的方法,因为这会暴露Thread的方法,我们应该修改为组合的方法。 */ public class BasicThreadPool extends Thread implements ThreadPool { //初始化线程数量 private final int initSize; //线程池核心线程数量 private final int coreSize; //当前活跃的线程数量 private int activeCount; //创建线程所需要的工厂 private final ThreadFactory threadFactory; //线程池最大的线程数量 private final int maxSize; private volatile boolean isShutdown = false; //任务队列 private final RunnableQueue runnableQueue; //工作线程队列,该队列用于存放活动线程。 private final Queue<ThreadTask> threadQueue = new ArrayDeque<>(); private final static DenyPolicy DEFAULT_DENY_POLICY = new DenyPolicy.DiscardDenyPolicy(); private final static ThreadFactory DEFAULT_THREAD_FACTORY = new DefaultThreadFactory(); private final long keepAliveTime; private final TimeUnit timeUnit; //构造器1,传入参数:初始化线程数量,最大线程数量,核心线程数量,任务队列的最大数量 public BasicThreadPool(int initSize,int maxSize,int coreSize, int queueSize){ this(initSize,maxSize,coreSize,DEFAULT_THREAD_FACTORY, queueSize,DEFAULT_DENY_POLICY,10,TimeUnit.SECONDS); } public BasicThreadPool(int initSize,int maxSize,int coreSize,ThreadFactory threadFactory, int queueSize,DenyPolicy denyPolicy, long keepAliveTime, TimeUnit timeUnit){ this.initSize = initSize; this.maxSize = maxSize; this.coreSize = coreSize; this.threadFactory = threadFactory; this.runnableQueue = new LinkedRunnableQueue(queueSize,denyPolicy,this); this.keepAliveTime = keepAliveTime; this.timeUnit = timeUnit; this.init(); } //初始化函数,先创建init个线程数量 private void init(){ start(); for(int i=0;i<initSize;i++){ newThread(); } } /** * 线程的自动维护,线程池中线程的数量维护主要通过run来维护, * 这也是为什么BasicThreadPool继承自Thread的原因。 */ private void newThread() { //创建线程任务 InternalTask internalTask = new InternalTask(runnableQueue); Thread thread = this.threadFactory.createThread(internalTask); ThreadTask threadTask = new ThreadTask(thread,internalTask); threadQueue.offer(threadTask); this.activeCount++; thread.start(); } //从线程池中移除某个线程 private void removeThread(){ ThreadTask threadTask = threadQueue.remove(); threadTask.internalTask.stop(); this.activeCount--; } @Override public void run(){ //用于维护线程的数量,如扩充,回收 while(!isShutdown && !isInterrupted()){ try{ timeUnit.sleep(keepAliveTime); }catch (InterruptedException e){ isShutdown = true; break; } synchronized (this){ if(isShutdown){ break; } //当前队列有任务尚未处理,并且activeCount<coreSize则继续扩充 if(runnableQueue.size()>0 && activeCount <coreSize){ for(int i=initSize;i<coreSize;i++){ newThread(); } //continue的目的是在于不想让线程的扩充直接达到maxSize continue; } //当前队列有任务尚未处理,并且activeCount<maxSize则继续扩充 if(runnableQueue.size()>0 && activeCount <maxSize){ for(int i=coreSize;i<maxSize;i++){ newThread(); } } //如果任务队列中没有任务需要处理,则需要回收,回收到coreSize if(runnableQueue.size()==0 && activeCount>coreSize){ for(int i=coreSize;i<activeCount;i++){ removeThread(); } } //如果回收的线程刚好从Runnable任务里面取出了某个任务,则会继续保持该任务的运行。知道完成任务的运行为止。 } } } //ThreadTask只是InternalTask和Thread的一个组合 private static class ThreadTask{ public ThreadTask(Thread thread,InternalTask internalTask){ this.thread = thread; this.internalTask = internalTask; } Thread thread; InternalTask internalTask; } @Override public void execute(Runnable runnable) { if(this.isShutdown()){ throw new IllegalStateException("The thread pool is destory"); } //提交任务只是简单的往任务队列里面插入runnable this.runnableQueue.offer(runnable); } @Override public void shutdown() { synchronized (this){ if(isShutdown) return; isShutdown = true; threadQueue.forEach(threadTask -> { threadTask.internalTask.stop(); threadTask.thread.interrupt();//每个线程都销毁 }); this.interrupt();//销毁线程池,停止BasicThreadPool的运行 } } @Override public int getInitSize() { if(isShutdown){ throw new IllegalStateException("The thread pool is destroy!"); } return this.initSize; } @Override public int getMaxSize() { if(isShutdown){ throw new IllegalStateException("The thread pool is destroy!"); } return this.maxSize; } @Override public int getCoreSize() { if(isShutdown){ throw new IllegalStateException("The thread pool is destroy!"); } return this.coreSize; } @Override public int getQueueSize() { if(isShutdown){ throw new IllegalStateException("The thread pool is destroy!"); } return this.runnableQueue.size(); } @Override public int getActiveCount() { synchronized (this){ return this.activeCount; } } @Override public boolean isShutdown() { return this.isShutdown; } private static class DefaultThreadFactory implements ThreadFactory{ private static final AtomicInteger GROUP_COUNTER = new AtomicInteger(1); private static final ThreadGroup group = new ThreadGroup("MyThreadPool-" +GROUP_COUNTER.getAndDecrement()); private static final AtomicInteger COUNTER = new AtomicInteger(0); @Override public Thread createThread(Runnable runnable) { return new Thread(group,runnable,"thread-pool-"+COUNTER.getAndDecrement()); } } }
运行测试代码
package chapter8_ThreadPool; import java.util.concurrent.TimeUnit; /** * 自定义线程池的测试代码,测试 * 1、任务提交 * 2、线程池的数量动态扩充 * 3、线程池的销毁功能。 */ public class ThreadPoolTest { public static void main(String[] args) throws InterruptedException { //定义一个线程池,初始化线程数2,最大6,核心线程数量4.支持的最大任务队列10000. final ThreadPool threadPool = new BasicThreadPool(2,6,4,1000); //同时提交20个任务到线程池里面。 for(int i = 0;i<20;i++){ threadPool.execute(()->{ try{ TimeUnit.SECONDS.sleep(10); System.out.println(Thread.currentThread().getName()+" is running and done."); }catch (InterruptedException e){ e.printStackTrace(); } }); } for(;;){ //不断输出线程池的 信息 System.out.println("getActiveCount "+threadPool.getActiveCount()); System.out.println("getQueueSize "+threadPool.getQueueSize()); System.out.println("getCoreSize "+threadPool.getCoreSize()); // System.out.println("getInitSize "+threadPool.getInitSize()); System.out.println("getMaxSize "+threadPool.getMaxSize()); TimeUnit.SECONDS.sleep(5); } } }
运行结果:
观察发现:我们看出线程池中线程的动态扩展状况以及任务的执行情况,在输出的最后,发现activeCount停留在了coreSize的值的位置,也就是说自动回收保留核心线程的设计生效。为了确定线程池中的活跃线程数量,可以使用JVM工具进行验证:
先使用jps打印当前的进程id,再使用jstack打印对应的线程相关信息