线程池的前世今生

背景

我们知道 Thread 的创建、启动以及销毁都是比较耗费系统资源的,比如创建一个线程时,系统需要为该线程创建局部变量表、程序计数器,以及独立的生命周期,过多的线程会占用很多内存,同时过多的线程会导致cpu占用过高,如果线程数量到达一定数目时,cpu仅仅在切换线程上下文就会占很多时间,而不去做别的事情。
我们举一个无线创建线程的例子:

public class InfiniteThreadTest {
    public static void main(String[] args) {
        int threadName = 0;
        while (true){
            new Thread (()->{
                try {
                    String[] strArray = new String[1000];
                    System.out.println (Thread.currentThread ().getName ());
                    TimeUnit.MINUTES.sleep (5);
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
            },"thread name : "+ threadName++).start ();
        }
    }
}

可以看到,cpu和内存瞬间飙升直到机器卡死~~

原理

线程池的出现就是为了避免无限创建线程,同时避免重复创建线程,以节约系统资源,那么应该如何实现呢?
可以把线程池类比为存放线程的池子,当有任务提交给线程池时,池子中的某个线程会主动执行该任务。当池子中的线程都已经使用时,就需要把新提交的任务放入缓存队列中,当池子中出现新的空闲线程时,便从缓存队列中取出任务执行。
因此线程池需要以下要素:

  • 任务队列:缓存提交的任务
  • 线程数量:包括 线程池中的最大线程数量,和最小活跃线程数量
  • 任务拒绝策略: 线程池中数量满时以及任务队列满时,拒绝接受新的线程

提交新任务到线程池流程图:

线程池的实现需要包含以下几个关键成员:

  1. 首先 需要实现线程池接口:
public interface ThreadPool {

    //提交任务到线程池
    void execute(Runnable runnable);

    //关闭线程池
    void shutdown();

}

接下来我们来实现这个线程池,需要包含生产线程的ThreadFactory、存放线程队列的RunnableQueue、和用来拒绝接受新线程的DenyPolicy

线程池的整体实现:

package executorservice;

import java.util.ArrayDeque;
import java.util.Queue;

public class MyThreadPool extends Thread implements ThreadPool {

    // 初始化线程数量
    private final int initSize;

    // 线程池最大线程数量
    private final int maxSize;

    // 线程池核心线程数量
    private final int coreSize;

    // 活跃线程数量
    private int activeCount;

    // 线程工厂
    private ThreadFactory threadFactory;

    // 任务队列
    private final RunnableQueue runnableQueue;

    // 线程池是否已经被关闭
    private volatile boolean isShutdown = false;

    // 工作线程队列
    private final Queue<ThreadTask> threadQueue = new ArrayDeque<> ();

    private final static DenyPolicy DEFAULT_DENY_POLICY = new DenyPolicy.AbortDenyPolicy ();

    // 初始化时设置线程池属性
    public MyThreadPool(int initSize, int maxSize, int coreSize, ThreadFactory threadFactory) {
        this.initSize = initSize;
        this.maxSize = maxSize;
        this.coreSize = coreSize;
        this.threadFactory = threadFactory;
        this.runnableQueue = new LinkedRunnableQueue ();
        init();
    }

    // 提交任务: 任务放入Runnable queue即可
    @Override
    public void execute(Runnable runnable) {
        if(this.isShutdown){
            throw new IllegalStateException (" The thread pool is destroy ");
        }
        this.runnableQueue.offer (runnable);
    }

    @Override
    public void shutdown() {
        //TODO
    }

    @Override
    public void run() {
        //TODO
    }

    private static class ThreadTask{
        // TODO
    }

    private void init(){
       // TODO
    }

}

execute方法只需要把被Runnable的具体实现放入 线程队列中,等待被创建为线程,那么线程是如何创建的呢?线程数量如何运行已经线程数量又是如何维护的呢?

  1. 我们使用简单工厂模式来创建线程,接受之前放入队列中的Ruunnable并生成Thread对象,同时指定线程组和线程名,调用端不用关心线程创建细节。
public interface ThreadFactory {

    //创建线程接口
    Thread createThread(Runnable runnable);
}

具体实现如下:

//  简单工厂 创建 线程对象,调用端不需要关心创建细节
    private static class MyThreadFactory implements ThreadFactory {

        private static final ThreadGroup myGroup = new ThreadGroup ("MyThreadPool");

        private static final AtomicInteger counter = new AtomicInteger (0);

        @Override
        public Thread createThread(Runnable runnable) {
            return new Thread (myGroup, runnable, "thread-pool-"+counter.getAndDecrement ());
        }
    }

这个线程的具体任务

  1. 接下来我们需要把创建好的Thread 交给线程池去运行,其作用就是用来循环获取 等待队列中的runnable对象,然后运行其run()方法。具体如何实现呢?

在线程池初始化时启动一组线程,数量为定义的initSize,每个线程循环获取等待队列中的任务,新增一个线程实现代码:

    // 线程池中新增一个运行线程
    private void newThread(){

        // 从等待队列中不停的获取任务 ,并放在这个线程中进行执行
        RunnableTask runnableTask = new RunnableTask (waitThreadQueue);

        Thread thread = this.threadFactory.createThread (runnableTask);
        ThreadTask threadTask = new ThreadTask (thread, runnableTask);
        activeThreadQueue.offer (threadTask);
        this.activeCount++;
        thread.start ();
    }

其中RunnableTask的实现:runnableQueue使用一个阻塞队列,take为空时会阻塞,直到队列中有新的runnable对象

public class RunnableTask implements Runnable {

    private final RunnableQueue runnableQueue;

    private volatile boolean running = true;

    public RunnableTask(RunnableQueue runnableQueue) {
        this.runnableQueue = runnableQueue;
    }

    @Override
    public void run() {

        // 循环获取等待队列中的runnable对象,并运行run()方法
        while( running && !Thread.currentThread ().isInterrupted ()) {
            Runnable task = null;
            try {
                task = runnableQueue.take ();
            } catch (InterruptedException e) {
                e.printStackTrace ();
            }
            task.run ();
        }
    }
}
  1. 接下来我们在线程池的init()方法中启动数量为initSize的newThread
private void init(){
        for (int i = 0; i < initSize; i++) {
            newThread ();
        }
        start ();
    }

start()为 启动线程池本身run(),他对等待队列进行监听,如果活跃数量小于核心数量,且等待队列中数量大于0时,新增一个线程

@Override
    public void run() {

        while (!isShutdown && !isInterrupted ()) {

            synchronized (this) {
                // 判断任务队列中是否有等待的队列,同时活跃线程小于定义的核心线程数时,启动新的线程
                if (waitThreadQueue.size () > 0 && activeCount < coreSize) {
                    for (int i = initSize; i < coreSize; i++) {
                        newThread ();
                    }
                }
            }
        }
    }

总结

可以看到我们通过几个参数来制定线程池的初始化线程数量,核心线程数量等,并通过两个队列来维护活跃线程和等待任务。
每个活跃线程轮训获取队列中的任务执行。当任务数较多时,如果没有达到我们指定的最大线程数,便增加新的线程,这样避免的无限创造线程造成的资源浪费。

参考:my github simple executorService demo

平时使用线程池时需要注意的点:

  • 线程池最大数量需要指定
  • 等待线程队列中的数量需要指定,防止无限接受线程,最后达到OOM

最后,给出一个阿里开发手册中线程池的使用建议:

   @Bean
   ExecutorService myExecutorBean(){
       // 使用ThreadFacroty创建线程同时指定线程名
       ThreadFactory namedThreadFactory = new ThreadFactoryBuilder ()
               .setNameFormat("my-thread-pool-%d").build();
       return new ThreadPoolExecutor (20, 200,
               60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable> (1024),namedThreadFactory);
   }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值