java线程池ThreadPoolExecutor

本文详细介绍了Java线程池ThreadPoolExecutor的使用,包括核心参数(如核心线程数、最大线程数、存活时间等)、执行流程、线程池状态及核心方法。线程池通过减少线程创建销毁的开销,提高系统资源利用率。文章还讨论了四种预定义的线程池类型,并解析了线程池的任务处理流程和线程池状态变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、ThreadPoolExecutor应用

为什么使用线程池?答:降低线程创建和关闭的消耗。当我们想同时开启200个线程,自己手动创建线程,使用后再销毁,会导致资源占用过多,所以应考虑使用线程池。线程池里有很多提前备好的可用线程资源,如果需要就直接从线程池里拿;不用的时候,线程池会自动帮我们管理。

使用线程池主要有以下两个好处:

  • 减少在创建和销毁线程上所花的时间以及系统资源的开销
  • 不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存

JDK提供了一个类Executors,还有好多包装好的线程池,但是参数是固定的,所以常常要自己通过类ThreadPoolExecutor自定义线程池的属性。java提供的定义好的线程池:

  1. FixedThreadPool:创建固定大小的线程池,数量通过传入的参数决定。
  2. SingleThreadExecutor:创建一个线程容量的线程池,所有的线程依次执行,相当于创建固定数量为 1 的线程池。
  3. CachedThreadPool:创建可缓存的线程池,没有最大线程限制(实际上是 Integer.MAX_VALUE)。如果有空闲线程等待时间超过一分钟,就关闭该线程。
  4. ScheduledThreadPool:创建计划 (延迟) 任务线程池, 线程池中的线程可以被设置为在特定的延迟时间之后开始执行,也可以以固定的时间重复执行(周期性执行)。
  5. SingleThreadScheduledExecutor:创建线程池延迟任务,创建一个线程容量的计划任务。

简单理解一下线程池的工作流:池内包括核心线程、非核心线程、阻塞队列、线程工厂。

  1. 当一个任务被分配给线程池,池会优先找到一个空闲的核心线程来完成这个任务。
  2. 如果没有空闲的核心线程,就创建或找到一个非核心线程来进行这个任务。一般我们会设置核心线程一直在池内存在,但非核心线程不会,当一个核心线程的空闲时间达到了我们设置的一个时间长度,该非核心线程就会被回收。
  3. 如果所有线程都在忙,并且已经达到了池内最大线程数的限制,那么就将此任务放入等待队列,等待线程空闲下来后,再来处理这些等待中的任务。
  4. 如果这三个地方都放满了,就要使用拒绝策略了,也就是以一定的策略来处理这个无处安放的任务。拒绝策略一般包括直接抛弃这个任务,或者将队头等待时间过长的一个任务删除,把当前新任务加入队列,后面会有详细介绍。
    在这里插入图片描述

一个简单使用的例子:

	ThreadPoolExecutor executor = new ThreadPoolExecutor( // 1.创建线程池对象
                1, // 核心线程数
                2, // 最大线程数量,即核心和非核心线程数量之和
                500, // 非核心线程的最大空闲时间,查过这个时间,相应非核心线程就会被回收
                TimeUnit.SECONDS, // 上一个参数的时间单位
                new LinkedBlockingDeque<>(), // 阻塞队列设置
                new ThreadFactory() { // 生产线程的工厂,实现接口ThreadFactory
                    @Override // 重写创建新线程的方法
                    public Thread newThread(Runnable r) {
                        Thread t = new Thread(r);
                        t.setName("测试-"); // 可以自定义设置新线程的名字
                        return t;
                    }
                },
                new ThreadPoolExecutor.AbortPolicy() // 设置拒绝策略
        );

        executor.execute(new Runnable() { // 2.调用execute或submit方法执行Runnable任务或Callable任务
            @Override
            public void run() {
                System.out.println("love hy");
            }
        });

        Future<Object> future = executor.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                return null;
            }
        });
        future.get();

二、ThreadPoolExecutor核心参数

    public ThreadPoolExecutor(int corePoolSize, // 核心线程数
                              int maximumPoolSize, // 最大线程数(非核心线程数)
                              long keepAliveTime, // 非核心线程的最大空闲时间
                              TimeUnit unit, // keepAliveTime的单位
                              BlockingQueue<Runnable> workQueue, // 等待队列,可以使用阻塞队列或工作队列
                              ThreadFactory threadFactory, // 线程工厂
                              RejectedExecutionHandler handler) // 拒绝策略

1、核心线程数

核心线程在池中的数量,核心线程会一直在池中等待任务到来,即使空闲也会一直存在,除非设置了allowCoreThreadTimeOut,核心线程才会在空闲一段时间后被回收。

到来的任务会优先分配给核心线程执行。

2、最大线程数

例如此参数设置为3,核心线程数为2,等待队列最大容量为2,此时来了5个任务,2个在被核心线程执行,2个进入等待队列,还有一个会被非核心线程执行,最大线程数就规定了最多有几个核心和非核心线程

3、keepAliveTime

非核心线程不会一直存在,此参数就是非核心线程的最大空闲时间,超过这个时间,非核心线程就会消失

4、阻塞队列

最常用的是ArrayBlockingQueue、LinkedBlockingQueue

5、线程工厂

线程池创建后,默认是空的,任务进入线程池后,线程才被创建。线程工厂使得我们程序员可以控制对于线程的创建,从而方便后续对这些线程的操作(如通过线程名字找到线程对象)

6、拒绝策略

当核心线程、非核心线程都在运行,且等待队列已经满了的时候,又来了新任务,如何拒绝这个任务。RejectedExecutionHandler是一个接口,只有一个抽象方法rejectedExecution(Runnable r, ThreadPoolExecutor executor),实现类有四个。实现类AbortPolicy对rejectedExecution的实现直接抛出了一个异常,意思是线程池无法处理多出的这个任务。实现类CallerRunsPolicy对rejectedExecution的实现是让main线程执行这个新线程:

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }

实现类DiscardOldestPolicy重写的rejectedExecution方法将等待队列的队头弹出,将新线程推入等待队列:

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll(); // 把等待队列对头弹出
                e.execute(r); // 新线程加入线程池
            }
        }

实现类DiscardPolicy更绝,重写方法啥也不做:

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}

可以看出,拒绝策略比较好实现,业务中常常由程序员自己实现。

三、ThreadPoolExecutor执行流程

即业务线程提交任务到线程池后,任务的处理流程。

源码:

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)) // 等待队列已经满了,且工作线程数大于最大线程数,addWorker方法执行失败(addWorker的第二个参数为false,表示把任务添加到非核心线程)
            reject(command); // 拒绝此任务
    }

在这里插入图片描述
图源:马士兵教育在b站的公开课

四、ThreadPoolExecutor线程池状态

4.1 核心属性ctl

// ctl就是个AtomicInteger类型变量,长度4字节,保证了原子性
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// ctl的结构:
// 1. 标识线程池当前状态(高三位)
// 2. 标识线程池当前的工作线程个数(低29位)
private static final int COUNT_BITS = Integer.SIZE - 3; // 32-3=29
private static final int CAPACITY   = (1 << COUNT_BITS) - 1; // 工作线程最大数量:00011111 11111111 11111111 11111111
// 线程池的五种状态:
private static final int RUNNING    = -1 << COUNT_BITS; // -1 = 11111111 11111111 11111111 11111111, RUNNING = 11100000 00000000 00000000 00000000
private static final int SHUTDOWN   =  0 << COUNT_BITS; // 000
private static final int STOP       =  1 << COUNT_BITS; // 001
private static final int TIDYING    =  2 << COUNT_BITS; // 010,过渡状态
private static final int TERMINATED =  3 << COUNT_BITS; // 011

可以将ctl简单了解为一个int类型变量,但它的所有++和–操作都基于CAS实现了线程安全

// 传入ctl,计算线程池状态
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 传入ctl,计算当前线程池中的工作线程个数
private static int workerCountOf(int c)  { return c & CAPACITY; }

4.2 线程池状态变换

在这里插入图片描述
图源:马士兵教育在b站的公开课

五、ThreadPoolExecutor核心方法

1、execute方法

源码:

public void execute(Runnable command) {
        if (command == null) // 非空判断
            throw new NullPointerException();
        int c = ctl.get(); // 获取ctl属性
        if (workerCountOf(c) < corePoolSize) { // 当前池内的工作线程数小于核心线程数
        // 获取核心线程并执行任务
            if (addWorker(command, true)) // true就是获取核心线程
                return; // 直接返回
            c = ctl.get(); // 获取核心线程失败,重新获取ctl
        } // 当前工作线程数已经>=核心线程数,或,核心线程获取失败
        if (isRunning(c) && workQueue.offer(command)) { // 线程池状态为running且向工作队列添加成功
            int recheck = ctl.get(); // 再检查一下ctl
            if (! isRunning(recheck) && remove(command)) // 如果线程池状态不是running,就将新任务从工作队列移除
                reject(command); // 执行拒绝策略
            else if (workerCountOf(recheck) == 0) // 是running或移除失败,判断工作线程是否为0
                // 工作线程数为0,但工作队列不为空
                // 添加一个空任务非核心线程,是为了处理工作队列中的任务,避免Shutdown状态一直无法转换为Terminate状态
                addWorker(null, false);
        }
        // 任务添加到工作队列失败,需要添加非核心线程去执行新任务
        else if (!addWorker(command, false)) // 添加非核心线程失败(addWorker的第二个参数为false,表示把任务添加到非核心线程)
            reject(command); // 拒绝此任务
    }

重点:1、execute方法包含了线程池的整体执行流程,包含了避免并发出错情况的设计
2、为什么线程池会添加空任务到非核心线程中去

2、addWorker方法

添加任务到核心线程或非核心线程

    private boolean addWorker(Runnable firstTask, boolean core) {
        // 1、判断线程池状态
        retry: // 外层for循环的标识
        for (;;) {
            int c = ctl.get(); // 获取ctl
            int rs = runStateOf(c); // 获取池状态

            if (rs >= SHUTDOWN && // 状态不为RUNNING
                // 状态为shutdown,且,第一个线程对应一个空任务,且,工作队列不为空
                // 同时满足上述3个条件时,需要处理队列中的任务(结合execute中的添加空任务到非核心线程)
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false; //不能添加任务到工作线程

            for (;;) {
                int wc = workerCountOf(c); // 获取工作线程数
                if (wc >= CAPACITY || // 工作线程数>=最大值
                    wc >= (core ? corePoolSize : maximumPoolSize)) // 如果是核心线程,是否大于核心线程最大数;如果是非核心线程,是否大于最大工作线程数
                    return false; // 不可获取参数(core)要求的核心或非核心线程
                if (compareAndIncrementWorkerCount(c)) // 以CAS方式对工作线程数+1,若成功就跳出外层for循环
                    break retry; // 跳出外层for循环
                c = ctl.get();  // 重新获取ctl
                if (runStateOf(c) != rs) // 判断和之前的状态是否一致
                    continue retry; // 若不一致,需要重新判断池状态
                }
        }

        // 2、添加工作线程,并启动线程
        boolean workerStarted = false; // 工作线程是否启动
        boolean workerAdded = false; // 工作线程是否添加
        Worker w = null;
        try {
            w = new Worker(firstTask); // new工作线程,并将任务扔进去
            final Thread t = w.thread; // Worker中的Thread属性,获取时已经初始化过了
            if (t != null) { // 如果new Worker成功了,t肯定不为null(健壮性判断)
                final ReentrantLock mainLock = this.mainLock; // 对线程池加锁,因为如果正在添加w时,线程池被shutdown,就会造成并发问题(2个shutdown方法也会对池加锁,再shutdown)
                mainLock.lock();
                try {
                    // 重新获取池状态,如果再加锁之前池被关闭,就直接返回,下面的if不执行
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN || // 池状态为RUNNING
                        (rs == SHUTDOWN && firstTask == null)) { // 线程池状态为shutdown,但队列中有空任务
                        
                        // 开始添加工作线程
                        if (t.isAlive()) // 当前线程是否正在执行(健壮性判断)
                            throw new IllegalThreadStateException();
                        workers.add(w); // workers是个HashSet,工作线程添加好了
                        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; // 返回线程是否启动
    }

	// 补救措施
	private void addWorkerFailed(Worker w) {
	    final ReentrantLock mainLock = this.mainLock; // 加锁
	    mainLock.lock();
	    try {
	        if (w != null)
	            // 创建工作线程成功(代表w被加进了HashSet中)
	            workers.remove(w); // 从HashSet中移除
	        decrementWorkerCount(); // 减少计数
	        tryTerminate(); // 尝试关闭池
	    } finally {
	        mainLock.unlock(); // 解锁
	    }
	}

3、Worker对象

    private final class Worker
        extends AbstractQueuedSynchronizer // 针对不同状态的线程池进行中断
        implements Runnable // 存储任务
    {
        private static final long serialVersionUID = 6138294804551838833L;

        final Thread thread; // 工作线程的Thread对象,初始化时构建出来的
        
        Runnable firstTask; // 需要执行的任务
        
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // 工作线程刚刚被初始化出来,还不允许被中断,之后将state归为0的时候,才可以中断
            this.firstTask = firstTask; // Worker被new的时候,会将任务赋值给firstTask
            this.thread = getThreadFactory().newThread(this); // 给Worker构建Thread对象
        }

        public void run() { // 实现了Runnable接口的run方法
            runWorker(this);
        }

        // Lock methods
        // Worker自己实现了基于AQS的锁机制,而不是直接使用java中的锁
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

4、runWorker方法

final void runWorker(Worker w) { // 执行任务
    Thread wt = Thread.currentThread(); // 拿到当前工作线程
    Runnable task = w.firstTask; // 拿到Worker中封装的任务
    w.firstTask = null;
    w.unlock(); // 调用了Worker中的tryRelease方法,将状态归为0,线程之后就可以被中断了
    boolean completedAbruptly = true; // 标识
    try {
        // 两种获取任务的方式:1、第一次获取的firstTask不为空;2、execute方法里面加入的空Worker,就对应这种方式,getTask,从工作队列获取任务
        while (task != null || (task = getTask()) != null) {
            w.lock(); // 在shutdown状态下,线程不允许被中断(stop的时候才可以中断工作线程的运行),注意中断线程的时候也需要获取w的锁

            // 下面这个if做的事就是,只要池状态为stop,就一定要中断wt
            if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) // 判断线程池状态是否为stop
                && !wt.isInterrupted()) // wt尚未被中断
                wt.interrupt(); // 中断wt

            try {
                beforeExecute(wt, task); // 空实现,需要我们实现钩子函数
                Throwable thrown = null;
                try {
                    task.run(); // 执行任务
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown); // 后置增强(并非动态代理),空实现,需要我们实现钩子函数
                }
            } finally {
                task = null;
                w.completedTasks++; // 执行成功的任务个数+1
                w.unlock(); // state标记设为0
            }
        } // 整个while循环不停地从等待队列拿出任务,循环结束
        completedAbruptly = false; // 异常在afterExecute中处理,如果无异常,就会来到这一句,否则仍未true,就是个标记是否出现异常的标记位
    } finally {
        processWorkerExit(w, completedAbruptly); // 线程退出
    }
}

5、getTask方法

// 从工作队列取出任务
private Runnable getTask() {
    boolean timedOut = false; // 

    for (;;) { // 死循环

        // ====================判断线程池状态=====================
        int c = ctl.get();
        int rs = runStateOf(c);

        // 池不为RUNNING 且 池状态为STOP
        // 或
        // 池为shutdown 且 工作队列为空
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount(); // 移除当前工作线程
            return null; // 这里只做了数量的扣除操作,真正将task从工作队列移除在后续进行
        }

        // ====================判断工作线程数量=====================
        int wc = workerCountOf(c);

        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c)) // 基于CAS方式移除当前线程
                return null;
            continue;
        }

        // ====================从工作队列获取任务=====================
        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // 非核心走这个方法
                workQueue.take(); // 核心走这个方法
            if (r != null)
                return r; // 拿到了任务,直接返回
            timedOut = true; // 获取任务时,达到了当前工作线程的最大空闲时间,那么下次循环当前线程就会被移除
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

6、processWorkerExit方法

// 移除当前工作线程(任务)
private void processWorkerExit(Worker w, boolean completedAbruptly) {
    // 正常走getTask方法,参数completedAbruptly为false,正常就会执行decrementWorkerCount
    // 除此以外,会在勾子函数抛出异常,参数completedAbruptly为true,需要手动执行decrementWorkerCount
    if (completedAbruptly)
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock; // 加锁
    mainLock.lock();
    try {
        // 当前线程池一共处理了多少任务
        completedTaskCount += w.completedTasks;
        // 移除工作线程(任务)
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }
    // 尝试将线程池状态转移为过渡状态,然后是TERMINATED状态
    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        // 池状态为RUNING或SHUTDOWN
        if (!completedAbruptly) { // 如果正常方式移除了当前工作线程
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize; // 核心线程数允许的最小值
            if (min == 0 && ! workQueue.isEmpty()) // 若工作队列不为空,设置工作线程最小值为1
                min = 1;
            // 仍然有工作线程在线程池中
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        // 不正常方式移除任务,添加一个空任务
        // 或者 工作队列不为空且没有工作线程时,再添加一个工作线程
        addWorker(null, false);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值