线程池的使用及原理浅析

什么是线程池

线程池是一个存放线程并利用存放的线程来执行业务线程的容器,降低了频繁创建和销毁线程的性能开销和时间开销。

线程池的使用

ExecutorService service=Executors.newFixedThreadPool(3);//1
ExecutorService service=Executors.newCachedThreadPool();//2
ExecutorService service=Executors.newSingleThreadExecutor();//3
ExecutorService service=Executors.newScheduledThreadPool(3);//4
  1. 返回一个固定数量的线程池,线程数不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个任务队列中,等待有空闲的线程去执行。
  2. 一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若用空闲的线程则执行任务,若无任务则不创建线程。并且每一个空闲线程会在 60 秒后自动回收。
  3. 创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中。
  4. 创建一个可以指定线程的数量的线程池,但是这个线程池还带有延迟和周期性执行任务的功能,类似定时器。

而这4中创建线程池的方式,都是基于ThreadpoolExecutor实现的

public ThreadPoolExecutor(int corePoolSize, //核心线程数量
                          int maximumPoolSize, //最大线程数
                          long keepAliveTime, //超时时间,超出核心线程数量以外的线程空余存活时间
                          TimeUnit unit, //存活时间单位
                          BlockingQueue<Runnable> workQueue, //保存执行任务的队列
                          ThreadFactory threadFactory,//创建新线程使用的工厂
                          RejectedExecutionHandler handler //当任务无法执行的时候的处理方式)

执行过程

线程池在创建之初是没有初始化线程的,在第一次执行业务线程时才会创建,任务完成之后,将不超过核心线程数的线程挂起供下一次执行任务时使用。

在执行任务时,当线程数大于等于核心线程数时,会将新进入的线程放入队列,如果队列也满了,那就创建新的线程去执行任务,直到线程数打到所设置的最大线程数时,执行拒绝策略,或者实现 RejectedExecutionHandler 接口自定义饱和策略。

源码浅谈

execute

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {// 1
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {// 2
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))//3
                reject(command);
            else if (workerCountOf(recheck) == 0)//4
                addWorker(null, false);
        }
        else if (!addWorker(command, false))//5
            reject(command);
    }
  1. 如果工作线程数小于核心线程数,则新建线程执行任务。
  2. 否则将任务加入队列。
  3. 如果线程池状态为非运行,则将任务从队列中删除,并拒绝任务。
  4. 如果线程数量为0,则新建线程。
  5. 若工作线程数已达到核心线程数,并且队列也已经满了,则继续添加新的线程来执行任务,若失败则拒绝任务。

addWorker

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {      //  1
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {          //        2
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
  1. 这段双重自旋做了以下操作,如果线程状态是shutdown,则不添加新任务;如果线程数达到了核心线程数,则返回上层添加至队列;上树条件都没达到,则将线程数原子加一。
  2. 构建一个worker线程。加重入锁将将线程加入workers集合,若添加成功启动线程执行任务。

拒绝策略

  1. AbortPolicy:直接抛出异常,默认策略;
  2. CallerRunsPolicy:用调用者所在的线程来执行任务;
  3. DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  4. DiscardPolicy:直接丢弃任务;
  5. 实现 RejectedExecutionHandler 接口,自定义饱和策略

注意事项

shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

shutdown()并不会等待线程全部执行完毕,若想等待线程执行完毕,可以使用线程计数器CountDownLatch

线程池的监控

在实际业务中,可以继承ThreadPoolExecutor重写beforeExecute和afterExecute方法来自定义任务执行前后的逻辑,或记录日志来进行统计分析。

public class DemoThreadPoolExecutor extends ThreadPoolExecutor {
    // 保存任务开始执行的时间 , 当任务结束时 , 用任务结束时间减去开始时间计算任务执行时间
    private ConcurrentHashMap<String, Date> startTimes;
    public DemoThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long
            keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit,
                workQueue);
        this.startTimes=new ConcurrentHashMap<>();
    }
    @Override
    public void shutdown() {
        System.out.println(" 已经执行的任务数: "+this.getCompletedTaskCount()+"," +
                " 当前活动线程数:"+this.getActiveCount()+", 当前排队线程数:"+this.getQueue().size());
        System.out.println();
        super.shutdown();
    }
    // 任务开始之前记录任务开始时间
    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        startTimes.put(String.valueOf(r.hashCode()),new Date());
        super.beforeExecute(t, r);
    }
    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        Date startDate = startTimes.remove(String.valueOf(r.hashCode()));
        Date finishDate = new Date();
        long diff = finishDate.getTime() - startDate.getTime();
// 统计任务耗时、初始线程数、核心线程数、正在执行的任务数量、
// 已完成任务数量、任务总数、队列里缓存的任务数量、
// 池中存在的最大线程数、最大允许的线程数、线程空闲时间、线程池是否关闭、线程池是否终止
        System.out.print(" 任务耗时:"+diff+"\n");
        System.out.print(" 初始线程数:"+this.getPoolSize()+"\n");
        System.out.print(" 核心线程数:"+this.getCorePoolSize()+"\n");
        System.out.print(" 正在执行的任务数量:"+this.getActiveCount()+"\n");
        System.out.print(" 已经执行的任务数:"+this.getCompletedTaskCount()+"\n");
        System.out.print(" 任务总数:"+this.getTaskCount()+"\n");
        System.out.print(" 最大允许的线程数:"+this.getMaximumPoolSize()+"\n");
        System.out.print(" 线程空闲时间:"+this.getKeepAliveTime(TimeUnit.MILLISECONDS)+"\n");
        System.out.println();
        super.afterExecute(r, t);
    }
    public static ExecutorService newCachedThreadPool() {
        return new DemoThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new
                SynchronousQueue ());
    }
}
public class test1 implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName());
    }

    static ExecutorService executorService = DemoThreadPoolExecutor.newCachedThreadPool();

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            executorService.execute(new test1());
        }
        executorService.shutdown();
    }


}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值