线程池工作原理理解

——好久没更新,想起来有点存货,所以拿出来发一发


简单说说线程,程序运行的最小单位!一个程序一般会同时运行好多个进程,像QQ(一个线程等待点击事件,一个线程等待消息输入等等)
那么在Java中启动一个新线程如下

new Thread().start();


一般人都知道CPU,每个核同时只能运行一个线程。
在这样一个前提下,会有两个问题:

  1.  Java 中new是需要消耗CPU去创建,线程运行完之后就需要内存回收、线程回收都需要消耗CPU
  2.  当CPU从当前线程切换到另外一个线程时,需要加载重线程栈的信息需要消耗CPU时间分片

为了解决上面的问题,所以就出现了线程池

线程池——管理线程的工具

https://www.bilibili.com/video/av29951334/?p=21&t=1721
痛点,某些线程 参数业务 不受外界影响,只需要new->start,但是一次new不能做到多次start
newcachethreadpool 比较灵活,会回收线程池
newfixthreadpool 配置并发数
newsinglethreadpool 相当于单线程
newScheduleThreadPool 定时任务
ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue) public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory,
                           RejectedExecutionHandler handler)

corePoolSize
核心线程数,不会被回收
maximumPoolSize
最大线程数
keepAliveTime
 在默认情况下比较准确。非核心线程的线程空闲时间达到keepAliveTime的话就会被回收直至线程数为corePoolSize,开启allowCoreThreadTimeOut的话,所有线程空闲时间达到keepAliveTime的话会被回收直至线程数为0.
unit
keepAliveTime的时间单位
threadFactory
线程工程,主要定义了newThread方法,可参考Executors.defaultThreadFactory()源码
BlockingQueue
用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。选型(SynchronousQueue,LinkedBlockingQueue,ArrayBlockingQueue)
RejectedExecutionHandler 
超过maximumPoolSize再添加线程的策略:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

先来一段测试代码,这段代码大概逻辑是 建一个线程池,然后往里面丢线程并记录线程名+开始结束时间,添加前后的时间,以此来观察线程的执行情况

public static void main(String[] args) throws InterruptedException {
//        ExecutorService service = new ThreadPoolExecutor(2,4,3000L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>());
//        ExecutorService service = new ThreadPoolExecutor(2,4,3000L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(),Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy());
//        ExecutorService service = new ThreadPoolExecutor(1, 3, 3000L, TimeUnit.MILLISECONDS, new SynchronousQueue<>());
        ExecutorService service = new ThreadPoolExecutor(1, 3, 3000L, TimeUnit.MILLISECONDS, new SynchronousQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
//        ExecutorService service = new ThreadPoolExecutor(2, 4, 30000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        try {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName());
                for (int i = 1; i <= 15; i++) {
                    int j = i;
                    try {
                        System.out.println("添加" + j + "开始");
                        service.submit(() -> {
                            try {
                                System.out.println(Thread.currentThread().getName() + ":" + j + "   start=" + new Date());
                                Thread.sleep(1000);
//                                if (j < 3) Thread.sleep(10000);
                                System.out.println(Thread.currentThread().getName() + ":" + j + "   =============end=" + new Date());
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        });
                        System.out.println("添加" + j + "结束");
                    } catch (Exception e) {
                        e.printStackTrace();
                        System.out.println("添加" + j + "失败");
                    }
                }
            }).start();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
//            System.out.println(((ThreadPoolExecutor) service).getActiveCount());
            Thread.sleep(15000);
            service.shutdown();
        }
    }


先来看看线程池运行流程(假设所有runnable都是死循环直到switch=off)

 

①submit(runnable4);当核心线程数corePoolSize>当前runnable数,通过工厂new一个线程来跑runnable4

②submit(runnable5);核心线程数corePoolSize<=当前runnable数,runnable5放入BlockingQueue的task1等待线程1至4执行完成


③如果thread1中的runnable完成了,则runnable5出队列,放入核心线程的thread1中

 

④submit(runnable10);核心线程都在运行,BlockingQueue也满,但是maximumPoolSize>当前运行的线程数,ThreadFactory.newThread,得到thread5,跑runnable10(只是其中一种模型,也有可能跑runnable5,或者BlockingQueue其它runnable)
⑤如果thread5执行完毕,则获取下一个runnable,如果60s内没有获取到runnable,thread5被回收(销毁?)【只有thread5执行完毕,所以thread5被回收,也有可能是其它thread被回收,但是剩下的thread总数=corePoolSize;thread总数<=corePoolSize是不会回收thread的】

⑥submit(runnable14);如果thread总数=maximumPoolSize,等待runnable总数=BlockingQueue.size,这时会触发拒绝策略RejectedExecutionHandler

⑦输入switch=off,所有线程结束,60s后,非核心线程回收

在ThreadPoolExecuter中存在一个内部类Worker(核心线程和非核心线程),而Worker本身是runnable

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    //...
    /** worker持续运行此thread.  Null if factory fails. */
    final Thread thread;
    /** 要运行的第一个任务.  Possibly null. 就是上面步骤①和④的runnable*/
    Runnable firstTask;
    /** Per-thread task counter */
    volatile long completedTasks;
    //...
}

这个Worker就是前面图上的thread的原形,也就是Worker里面的thread.start()后运行的是worker.run(),内部逻辑简单理解就是死循环去获取runnable再去执行runnable.run()【runnable.run()就是普通的方法调用,这样子thread的死循环就可以让线程一直活着】
添加代码worker的代码在哪?
从上面代码中点进方法提里面
service.submit(runnable)->
(AbstractExecutorService.submit)execute(ftask)->
(ThreadPoolExecuter.execute)addWorker(command, true)
看源码重点部分,new worker传入runnable作为要运行的第一个任务,而在worker中

private boolean addWorker(Runnable firstTask, boolean core) {
    //...代码....
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            //...代码...
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

/**
 * Creates with given first task and thread from ThreadFactory.
 * @param firstTask the first task (null if none)
 * 这里是将worker自身传给了属性thread,即thread.start实际会跑runWorker方法
 */
Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker  */
public void run() {
    runWorker(this);
}
final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        //这里先运行firstTask.run,之后会从线程池队列中获取runnable
        while (task != null || (task = getTask()) != null) {
            w.lock();
           //...代码...
            try {
               //...代码...
                try {
                    task.run();
                } catch (RuntimeException x) {
              //...代码...
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

BlockingQueue

BlockingQueue,有几个核心方法:
(put/take)没有超时控制
(offer/poll)提供超时控制
在线程池ThreadPoolExecuter中,并没有使用到put(这个不知道为何);而offer是由runnable发起,take和poll是由worker发起

SynchronousQueue同步队列/栈,实际上它不是一个真正的队列,因为它不会为队列中元素维护存储空间。与其他队列不同的是,它维护一组线程,这些线程在等待着把元素加入或移出队列。
https://www.jianshu.com/p/b7f7eb2bc778
https://segmentfault.com/a/1190000011207824?utm_source=tag-newest(不知道哪个抄哪个的)
这里说了一点,他这个队列存的不是runnable,而是thread

https://www.cnblogs.com/dwlsxj/p/Thread.html
SynchronousQueue有两个模式,一个公平模式用队列,一个不公平模式用栈
内部继承抽象类Transferer

abstract static class Transferer<E> {
    /**
     * 执行put和take方法.
     *
     * @param e 非空时,表示这个元素要传递给消费者(提供者-put);
     *          为空时, 则表示当前操作要请求消费一个数据(消费者-take)。
     *          offered by producer.
     * @param timed 决定是否存在timeout时间。
     * @param nanos 超时时长。
     * @return 如果返回非空, 代表数据已经被消费或者正常提供; 如果为空,
     *         则表示由于超时或中断导致失败。可通过Thread.interrupted来检查是那种。
     */
    abstract E transfer(E e, boolean timed, long nanos);
}

SynchronousQueue的几个属性

/** 用于旋转控制的CPU数量 */
static final int NCPUS = Runtime.getRuntime().availableProcessors();

/**
 * 在阻塞等待时间之前旋转的次数。该值是根据经验得出的-在各种处理器和OS上都可以很好地工作。根据经验,最佳值似乎不会随CPU数量(超过2个)而变化,因此只是一个常数。
 * 有timedout值
 */
static final int maxTimedSpins = (NCPUS < 2) ? 0 : 32;

/**
 * 在阻塞等待时间之前旋转的次数。该值是根据经验得出的-在各种处理器和OS上都可以很好地工作。根据经验,最佳值似乎不会随CPU数量(超过2个)而变化,因此只是一个常数。
 * 无timedout值
 */
static final int maxUntimedSpins = maxTimedSpins * 16;

/**
 * 自旋时间 纳秒,这个值定的比较随意
 */
static final long spinForTimeoutThreshold = 1000L;
SNode s = null; // constructed/reused as needed
int mode = (e == null) ? REQUEST : DATA;

for (;;) {
    SNode h = head;
    if (h == null || h.mode == mode) {  // 空或者模式相同
        if (timed && nanos <= 0) {      // 瞬间超时 ThreadPoolExecuter中添加runnable调用offer(E e)导致线程池的会匹配不到就返回null,使用拒绝策略
            if (h != null && h.isCancelled()) //match==this代表结点取消?
                casHead(h, h.next);     // 弹出取消的节点
            else
                return null;
        } else if (casHead(h, s = snode(s, e, h, mode))) { //多加了一个s节点在栈顶,压栈
            SNode m = awaitFulfill(s, timed, nanos); //自旋/阻塞等待匹配
            if (m == s) {               // 匹配到自身则取消节点,从栈中清理出来,不用casHead是因为有可能在等待的过程中已经不是最上的节点
                clean(s);
                return null;
            }
            if ((h = head) != null && h.next == s) //匹配成功,将顶节点出栈
                casHead(h, s.next);     // help s's fulfiller
            return (E) ((mode == REQUEST) ? m.item : s.item); //返回data模式的item
        }
    } else if (!isFulfilling(h.mode)) { // 如果顶节点不是在匹配中,进行匹配。前提是不同模式,线程池中这里有可能是mode=0未匹配和mode=3匹配中(初始入栈模式肯定是mode=0,此判断后会出现data入栈,此节点mode=2|1=3)
        if (h.isCancelled())            // 出栈下一次循环
            casHead(h, h.next);         // pop and retry
        else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) { // 
            for (;;) { // loop until matched or waiters disappear
                SNode m = s.next;       // m is s's match
                if (m == null) {        // 入栈瞬间发现头节点已经出栈,则清楚新节点s,回到大循环
                    casHead(s, null);   // pop fulfill node
                    s = null;           // use new node next time
                    break;              // restart main loop
                }
                SNode mn = m.next;
                if (m.tryMatch(s)) { //如果节点匹配
                    casHead(s, mn);     // 则出栈两个节点s和m,mn成为栈顶
                    return (E) ((mode == REQUEST) ? m.item : s.item);
                } else                  // 否则就是栈顶已经匹配,s节点需要重新找下一个节点
                    s.casNext(m, mn);   // help unlink
            }
        }
    } else {                            // 顶节点正在匹配,找栈顶第二个节点进行匹配
        SNode m = h.next;               // m is h's match
        if (m == null)                  // 没有第二个节点
            casHead(h, null);           // pop fulfilling node
        else {
            SNode mn = m.next;
            if (m.tryMatch(h))          // 把正在匹配的顶二个节点出栈
                casHead(h, mn);         // pop both h and m
            else                        // 栈顶已经被匹配了再找下一个
                h.casNext(m, mn);       // help unlink
        }
    }
}

RejectedExecutionHandler 

AbortPolicy,DiscardPolicy,DiscardOldestPolicy,CallerRunsPolicy都实现了RejectedExecutionHandler 接口
代码以AbortPolicy,实现了rejectedExecution方法,只有一句抛出语句

public static class AbortPolicy implements RejectedExecutionHandler {
    /**
     * Creates an {@code AbortPolicy}.
     */
    public AbortPolicy() { }

    /**
     * Always throws RejectedExecutionException.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     * @throws RejectedExecutionException always
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

注意:CallerRunsPolicy的核心代码是r.run()是直接在当前线程运行,而不是另起线程
另外,这里可以自行写一个策略,譬如调取某个接口通知管理员。或者记录在某个地方,等时机到重新放入线程池等等

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值