Java线程池


使用线程池的优点:

  • 重用已经创建好的线程,避免频繁创建和销毁的系统开销
  • 控制线程并发数,合理使用系统资源,提高应用的性能
  • 手动管理线程,比如定时执行、取消执行等

Executors线程池工厂类

Excutors是个工厂类,里面提供多了创建多种不同类型池的方法,常用的有 :

  • FixedThreadPool: 指定核心线程数目的一个线程池,缓冲池无限大
  • CacheThreadPool:无核心线程,非核心线程可创建N个。
  • SingleThreadPool: 创建只有一个核心线程的线程池,缓冲池无限大
  • ScheduleThreadPool: 创建一个可以延迟且定期执行任务的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

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

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

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

ThreadPoolExecutor线程池具体实现类

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
//参数判断,不满足条件抛exception
//corePoolSize需大于等于0,maximumPoolSize需大于0,且大于等于corePoolSize,keepAliveTime需大于等于0
//workQueue 、threadFactory 、handler 不能为null
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

corePoolSize 核心线程数大小: 提交一个任务时,如果当前线程数小于corePoolSize,就会创建一个线程(即使其他有可用的空闲线程)。默认情况下,核心线程会一直存活(即使处于空闲状态)。如果ThreadPoolExecutor的 allowCoreThreadTimeout设置为true,则核心线程也会存在超时策略,超时时长由keepAliveTime决定,当等待时间超过keepAliveTime,核心线程就会终止。

blockingQueue< Runnable> 阻塞队列: 用于存储等待执行的任务列表。不同的blockingQueue< Runnable>对线程池运行逻辑有很大影响,可以选择以下几个阻塞队列:

 - ArrayBlockingQueue:基于数组的有界阻塞队列, FIFO(先进先出)原则,创建时必须指定大小;

 - LinkedBlockingQueue:基于链表的阻塞队列,FIFO (先进先出)原则。
 如果创建时没有指定容量,则默认为Integer.MAX_VALUE(无限);

 - SynchronousQueue:一个不存储元素的阻塞队列,相当于缓冲区只有1个位置
  每个插入操作必须等上一个元素被移除之后,否则插入操作一直处于阻塞状态

 -  PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

maximumPoolSize 线程池最大容量: 线程池允许创建的最大线程数。

keepAliveTime 保活时间: 线程执行结束后,保持存活的时间。

ThreadFactory 线程工厂: 统一创建管理

RejectedExecutionHandler: 线程池队列饱和之后的执行策略,默认是采用AbortPolicy。JDK提供四种实现方式:

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

TimeUnit: keepalive的时间单位,可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

线程池状态

成员变量ctl,AtomicInteger这个类可以通过CAS达到无锁并发,效率比较高,这个变量有双重身份,它的高三位表示线程池的状态,低29位表示线程池中现有的线程数,这也是Doug Lea一个天才的设计,用最少的变量来减少锁竞争,提高并发效率。

线程池的状态,有5种,

  • RUNNING, 运行状态,值也是最小的,刚创建的线程池就是此状态
  • SHUTDOWN,停工状态,不再接收新任务,已经接收的会继续执行
  • STOP,停止状态,不再接收新任务,已经接收正在执行的,也会中断
  • TIDYING,清空状态,所有任务都停止了,工作的线程也全部结束了
  • TERMINATED,终止状态,线程池已销毁
	// //CAS,无锁并发
	private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
	//表示线程池线程数的bit数,29位
    private static final int COUNT_BITS = Integer.SIZE - 3; 
    //最大的线程数量,低29位都是1
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1; 

    // runState is stored in the high-order bits
     //1110 0000 0000 0000 0000 0000 0000 0000
    private static final int RUNNING    = -1 << COUNT_BITS; 
    //0000 0000 0000 0000 0000 0000 0000 0000
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    //0010 0000 0000 0000 0000 0000 0000 0000
    private static final int STOP       =  1 << COUNT_BITS;
     //0100 0000 0000 0000 0000 0000 0000 0000
    private static final int TIDYING    =  2 << COUNT_BITS;
    //0110 0000 0000 0000 0000 0000 0000 0000
    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing and unpacking ctl
     //获取线程池的状态,高3位表示
    private static int runStateOf(int c)     { return c & ~CAPACITY; } 
    //获取线程的数量,低29位表示
    private static int workerCountOf(int c)  { return c & CAPACITY; } 
    //组装状态和数量,成为ctl
    private static int ctlOf(int rs, int wc) { return rs | wc; }

    private static boolean isRunning(int c) {
        return c < SHUTDOWN;
    }

线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

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

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

  • shutdown方法会将正在执行的任务继续执行完,而shutdownNow会直接中断正在执行的任务。调用了这两个方法的任意一个,isShutdown方法都会返回true,当所有的线程都关闭成功,才表示线程池成功关闭,这时调用isTerminated方法才会返回true。

提交任务

execute/submit
向线程池提交任务有这2种方式,execute是ExecutorService接口定义的,submit有三种方法重载都在AbstractExecutorService中定义,都是将要执行的任务包装为FutureTask来提交,使用者可以通过FutureTask来拿到任务的执行状态和执行最终的结果,最终调用的都是execute方法,其实对于线程池来说,它并不关心你是哪种方式提交的,因为任务的状态是由FutureTask自己维护的,对线程池透明。

execute(runnable)提交任务
  • 如果目前运行线程数少于核心线程数,则addWorker(command, true)创建核心线程并启动

  • 判断线程池是否在运行,如果在,任务队列是否允许插入,插入成功再次验证线程池是否在运行状态

1、如果非运行状态,移除插入的任务,然后执行拒绝策略。
2、如果在运行状态,或者非运行状态但移除失败
3、再次判断,如果当前线程总数为0,则添加新线程(但不指定firstTask)并执行
  • 如果线程池非运行状态或者缓存队列已满,addWorker(command, false) 尝试新建非核心线程,进行处理。如果添加非核心线程失败,执行拒绝策略。

线程没有核心非核心之分,只是在addWorker里判断(允许创建最大线程数)

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
   
    int c = ctl.get();
    //检查当前线程数是否达到核心线程数的限制,注意线程本身是不区分核心还是非核心
    if (workerCountOf(c) < corePoolSize) { 
        if (addWorker(command, true)) 
            return;  //如果线程总数小于核心线程总数,则直接创建线程,执行任务
        //如果添加任务失败,刷新ctl,进入下一步
        c = ctl.get();
    }
    //检查线程池是否是运行状态,然后将任务添加到等待队列,注意offer是不会阻塞的
    if (isRunning(c) && workQueue.offer(command)) {
     //任务成功添加到等待队列,再次刷新ctl
        int recheck = ctl.get();
        //如果线程池不是运行状态,则将刚添加的任务从队列移除并执行拒绝策略
        if (! isRunning(recheck) && remove(command))
            reject(command);
        //判断当前线程数量,如果线程数量为0,则添加一个非核心线程,并且不指定首次执行任务
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false); 
    }
      //如果线程池非运行状态,或者缓冲池已满
     //添加非核心线程,指定首次执行任务,如果添加失败,执行异常策略
    else if (!addWorker(command, false))
        reject(command);
  }

在这里插入图片描述

添加worker
  • execute方法虽然没有加锁,但是在addWorker方法内部,加锁了,这样可以保证不会创建超过我们预期的线程数,在最小的范围内加锁,尽量减少锁竞争
  • core参数,只是用来判断当前线程数是否超量的时候跟corePoolSize还是maxPoolSize比较,Worker本身无核心或者非核心的概念。
private boolean addWorker(Runnable firstTask, boolean core) {
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

//如果线程池的状态到了SHUTDOWN或者之上的状态时候,只有一种情况还需要继续添加线程,
//那就是线程池已经SHUTDOWN,但是队列中还有任务在排队,而且不接受新任务(所以firstTask必须为null)
//这里还继续添加线程的初衷是,加快执行等待队列中的任务,尽快让线程池关闭
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        for (;;) {
            int wc = workerCountOf(c);
//传入的core的参数,如果线程数超过理论最大容量,如果core是true跟最大核心线程数比较,否则跟最大线程数比较
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
//通过CAS自旋,增加线程数+1,增加成功跳出双层循环,继续往下执行
            if (compareAndIncrementWorkerCount(c))
                break retry;
//检测当前线程状态如果发生了变化,则继续回到retry,重新开始循环
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
    }
//走到这里,说明我们已经成功的将线程数+1了,但是真正的线程还没有被添加
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        w = new Worker(firstTask); //添加线程,Worker是继承了AQS,实现了Runnable接口的包装类
        final Thread t = w.thread; //得到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());
//检查线程状态,只有当线程池处于RUNNING
//或者处于SHUTDOWN并且firstTask==null的时候,这时候创建Worker来加速处理队列中的任务
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) //线程只能被start一次
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            if (workerAdded) {
                t.start();  //启动Worker
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

启动任务
//Worker的run方法调用的是ThreadPoolExecutor的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 { 
     //如果task不是null,或者去队列中取任务,注意这里会阻塞,后面会分析getTask方法
        while (task != null || (task = getTask()) != null) {
            w.lock(); //如果线程被中断,那么会抛出InterruptedException,而退出循环,结束线程
            //判断线程是否需要中断
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                wt.interrupt();
            try {
            //任务开始执行前的hook方法
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    task.run();  //执行这个新创建的task,或者是从workQueue中取出一个任务,进程执行
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                //任务开始执行后的hook方法
                    afterExecute(task, thrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
    	 //Worker退出
        processWorkerExit(w, completedAbruptly);
    }
}


private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?

    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
//检查线程池的状态,如果已经是STOP及以上的状态,或者已经SHUTDOWN,队列也是空的时候
//直接return null,并将Worker数量-1
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

  		//是否存在超时策略
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }
//如果存在超时策略(非核心线程,核心线程且allowCoreThreadTimeOut = true),阻塞指定时间后结束线程
//如果不存在超时策略(核心线程,且allowCoreThreadTimeOut = false),则无限等待,一直阻塞
        try {
            Runnable r = timed ? 
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take(); //一直阻塞
            if (r != null)
                return r; // 在保活时间内,如果workQueue中有新的任务到来,复用此线程,减少任务的创建和销毁开销。
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

线程池的线程是如何做到复用的

线程池中的线程在循环中尝试取任务执行,这一步会被阻塞,如果设置了allowCoreThreadTimeOut为true,则线程池中的所有线程都会在keepAliveTime时间超时后还未取到任务而退出。或者线程池已经STOP,那么所有线程都会被中断,然后退出。

线程池是如何做到高效并发的
看整个线程池的工作流程,有以下几个需要特别关注的并发点.

  • 线程池状态和工作线程数量的变更。这个由一个AtomicInteger变量 ctl来解决原子性问题。
  • 向工作Worker容器workers中添加新的Worker的时候。这个线程池本身已经加锁了。
  • 工作线程Worker从等待队列中取任务的时候。这个由工作队列本身来保证线程安全,比如LinkedBlockingQueue等。

参考:
彻底理解Java线程池原理篇
线程池的工作原理与源码解读
深入源码分析Java线程池的实现原理

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java线程池是一种用于管理和复用线程的机制,它可以提高多线程应用程序的性能和效率。线程池中的线程可以被重复使用,避免了频繁创建和销毁线程的开销。 在Java中,线程池可以通过`ExecutorService`接口来创建和管理。线程池中的线程可以执行提交给它的任务,并且可以根据需要自动创建新的线程或销毁闲置的线程。 嵌套线程池是指在一个线程池中创建另一个线程池。这种情况通常发生在需要处理一些复杂的任务,其中每个任务本身也需要使用线程池来执行。 下面是一个示例代码,演示了如何在Java中嵌套使用线程池: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class NestedThreadPoolExample { public static void main(String[] args) { // 创建外层线程池 ExecutorService outerThreadPool = Executors.newFixedThreadPool(5); // 提交任务给外层线程池 outerThreadPool.execute(() -> { // 创建内层线程池 ExecutorService innerThreadPool = Executors.newFixedThreadPool(3); // 提交任务给内层线程池 innerThreadPool.execute(() -> { // 内层线程池执行的任务 System.out.println("Inner thread pool task executed"); }); // 关闭内层线程池 innerThreadPool.shutdown(); }); // 关闭外层线程池 outerThreadPool.shutdown(); } } ``` 在上面的示例中,我们首先创建了一个外层线程池`outerThreadPool`,它使用`Executors.newFixedThreadPool()`方法创建了一个固定大小的线程池。然后,我们向外层线程池提交了一个任务,该任务在执行时创建了一个内层线程池`innerThreadPool`,并向内层线程池提交了一个任务。最后,我们分别关闭了内层线程池和外层线程池

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值