java线程池

119 阅读5分钟

基本概念

image.png corepollSize:核心线程数,指线程池运行期间维持的的线程数,也就是线程池的基本大小。线程池启动后来了任务后就会创建线程,如果线程还没执行完任务又来了新任务,就会创建新的线程运行。直至创建的线程数到达核心线程数。
maxpollsize:核心线程数都满了以后还有新的任务到来,就会放入到workqueue中,当workkqueue也满了之后就会创建新的线程直到到达maaxpollsize的大小。maxpollsize就是线程池运行的最大线程数。
keepAliveTime:一个多的线程可以在闲置时存活的最大时间。超过这个时间就会被回收。
workqueue:当核心线程满了且都在运行后又来了新的任务,就会放在任务队列中,当线程处理完后再从队列中取出。
threadfactory:线程工厂,创建线程的工厂,可以设定线程名、线程编号等。
handler:拒绝策略,当线程池线程数已满,并且工作队列达到限制,新提交的任务使用拒绝策略处理。可以自定义拒绝策略,拒绝策略需要实现RejectedExecutionHandler接口。

最大线程数用于处理那些任务量变多的情况,因为一个业务中任务的数量不可能一直一样。

创建线程的过程 image.png

增减线程池的特点:
1.通过设置corePoolSize和maximumPoolSize相同,就可以创建固定大小的线程池。
2.线程池希望保持较少的线程数,并且只有在负载变得很大时才增加它。
3.通过设置maximumPoolSize为很高的值,可以允许线程池容纳任意数量的并发任务。
4.只有在队列填满时才创建多于corePoolSize的线程,如果使用的是无界队列,那么线程数就不会超过corePoolSize。

为什么使用线程池

1.降低资源消耗。创建和销毁一个线程都需要消耗资源,所以可以重复使用已创建的线程来减少相关的资源消耗

2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。否则就会来一个任务创建一个线程,这个过程需要等待。

3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

线程池的不同

FixedThreadPool:核心线程数和最大线程数相同

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

从源码中看到FixedThreadPoll中创建线程核心线程和最大线程数相同,最大存活时间为0,因为都是核心线程所以最大存活时间没必要设置。后面的参数分别是时间单位和队列。
newSingleExecutor:单线程线程池,只有一个线程在工作。

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

newCacheTreadPool:有缓存的线程池,如果线程池长度超过处理需要,可以灵活回收空闲线程,没回收的话就新建线程。可以回收多余的线程。

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

没有核心线程,最大线程设置为上限,存活60s,使用锁队列。
newScheduledThreadPool:支持定时以及周期性任务执行的线程池。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

类定义

public class ScheduledThreadPoolExecutor
        extends ThreadPoolExecutor
        implements ScheduledExecutorService

由上看到不同的线程池有不同的特性,但不一定很契合我们的要求,所以我们要自己实现线程池。

线程池的关闭

shutdown:停止线程,不接受新任务,但已有的任务会全部执行。
shutdownNow:立刻停止正在执行的线程,对正在执行的线程会调用interrupt方法,对队列中的任务会返回一个列表,后续的处理由程序员决定。

shutdown源码:

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

加锁然后尝试执行四个方法最后解锁。 shutdownnow

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

加锁,定义一个队列用于接收还在任务队列里的任务。

线程池的拒绝

什么时候拒绝?
时机:

  1. 所有线程都在工作并且等待队列已满
  2. 线程池停止运行

拒绝策略

  1. AbortPolicy,默认方法,满了以后会抛出异常
  2. CallerRunsPolicy,调用者运行策略,线程池无法运行,就由任务的提出者运行任务。
  3. DiscardPolicy,丢弃策略,会丢弃新来的任务。不会有通知
  4. DiscardOldestPolicy,丢弃最早未处理请求策略,丢弃最先进入阻塞队列的任务以腾出空间让新的任务入队列。

钩子函数

增强线程池的方法

实现原理

线程池的组成:

  • 线程池管理器:用于管理线程
  • 工作线程:用于执行任务的线程
  • 任务队列:暂时未被执行的任务存储,支持并发
  • 任务接口:被执行的任务

线程创建的源码:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
   //获取当前状态
    int c = ctl.get();
    //小于核心线程数,新建线程
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //判断线程池是否处于运行状态,是,就将任务放入队列中
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    //由于线程停止或队列已满,就执行增加线程的方法,如果失败就执行拒绝逻辑
    else if (!addWorker(command, false))
        reject(command);
}

暂写至此。