线程池高频面试题(ThreadPoolExecutor)

为什么使用线程池?

线程池的优势

  • 线程复用,减少线程创建、销毁的开销,提高性能

  • 提高响应速度,当任务到达时,无需等待线程创建就能立即执行。

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

什么时候用线程池?

  • 多个任务操作之间互不影响,或者多个操作需要汇总之类的场景。

  • 需要处理的任务数量很大

线程池状态

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;//线程池容量

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

    // Packing and unpacking ctl
    private static int runStateOf(int c)  	{ return c & ~COUNT_MASK; }
    private static int workerCountOf(int c)  { return c & COUNT_MASK; }
    private static int ctlOf(int rs, int wc) { return rs | wc; }

 

线程池源码

execute()

public void execute(Runnable command) {
     //健壮性分析
        if (command == null)
            throw new NullPointerException();
     //拿到32位的原子INT 前三位状态  后29位存储工作线程数
        int c = ctl.get();
    //workerCountOf(c): return c & COUNT_MASK;
        if (workerCountOf(c) < corePoolSize) {//获取 工作线程数 <核心线程数
            if (addWorker(command, true))
                return;
            //防止并发不安全
            c = ctl.get();
        }
     //判断线程池是不是running,如果是将任务添加到阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
            //防止并发不安全
            int recheck = ctl.get();
            //再次判断是否running,若不是 则移除任务
            if (! isRunning(recheck) && remove(command))
                reject(command);//拒绝策略
            //如果线程池处在running但是工作线程为0,
            else if (workerCountOf(recheck) == 0)
			//阻塞队列有任务,但是没有工作线程,则添加一个空任务工作线程处理阻塞队列里的任务
                addWorker(null, false);
        }
     //创建非核心线程,处理任务
        else if (!addWorker(command, false))
            reject(command);//拒绝策略
    }

阻塞队列类型

addWorker()

private boolean addWorker(Runnable firstTask, boolean core) {
    //标记for循环 从内循环 跳出外部for循环
        retry://经过大量的判断给工作线程数+1
        for (int c = ctl.get();;) {
            // runStateAtLeast(int c, int s) : return c >= s;
            if (runStateAtLeast(c, SHUTDOWN)//SHUTDOWN: 0 大于或等于则不可能为running后边判断shutdown情况
                && (runStateAtLeast(c, STOP)//c >=1 是stop或者更高的状态
                    || firstTask != null //线程任务为不空 联系execute() 
                    //只有是running且阻塞队列有任务但无工作线程时,调用addWorker(null, false); 
                    || workQueue.isEmpty()))//阻塞队列为空
                return false;//构建工作线程失败

            for (;;) {
                if (workerCountOf(c)//是否大于线程池最大的容量
                    //判断当前线程是否超过核心线程或者最大线程池容量
                    >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                    return false;
                //将CAS将工作线程数+1 
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                //若CAS失败 重新获取ctl  可能并发操作了
                c = ctl.get();  // Re-read ctl
                //重新判断线程池状态,若有变化就重新外侧循环,无变化就内侧循环
                if (runStateAtLeast(c, SHUTDOWN))
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false; //worker开始
        boolean workerAdded = false; //worker添加
        Worker w = null;//工作线程就是Worker
        try {
            //创建worker,传入任务
            w = new Worker(firstTask);
            //从worker中获取线程t
            final Thread t = w.thread;
            
            if (t != null) {
                //获取线程池的全局锁,避免添加任务时,其它线程将线程池 shutdown
                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 c = ctl.get();
					//是running状态
                    if (isRunning(c) ||
                        //或者 是shutdown状态且线程任务为空
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                        //线程是否为运行状态?
                        if (t.getState() != Thread.State.NEW)
                            throw new IllegalThreadStateException();
                        //将工作线程添加到集合  一个HashSet的集合
                        workers.add(w);
                        workerAdded = true;
                        int s = workers.size();
                        //如果现在线程工作线程数,大于之前记录的最大工作线程数就替换一下更新最大
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;//返回是否创建工作线程成功
    }

Worker的封装

 private final class Worker extends AbstractQueuedSynchronizer implements Runnable
  { 	
	//继承AQS 实现runnable接口也就重写了run方法,在线程里的runnable 线程.start 就是调用的worker的run()
 }
Worker内部
Worker(Runnable firstTask) {
            setState(-1); // AQS状态  禁止中断,直到runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

 public void run() {
            runWorker(this);
        }

如何确定线程池大小?

CPU密集型

一个计算为主的程序运行过程中对CPU的占用率很高 。

多线程运行的时候,可以充分利用起所有的CPU核心。但是如果线程数远远超出CPU核心数量则会因为频繁的切换线程而导致任务效率下降。 这个时候尽量压榨CPU,所以对于CPU密集型线程任务,线程池大小=CPU数+1 (4核+1)

Nthreads = Ncpu x Ucpu x (1 + W/C)
​
Ncpu = CPU的数量
Ucpu = 目标CPU的使用率, 0 <= Ucpu <= 1
W/C = 等待时间与计算时间的比率
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

最优池的大小=CPU数量×目标CPU使用率×(1+等待时间与计算时间比),对于CPU密集型来说等待时间趋近于0,所以应当将线程池大小设为和CPU数量一样,但是为了确保突发情况下若某线程暂停,而CPU周期不会中断工作 通常多设一个。

IO密集型

有大量的网络和文件操作的程序,这样的程序大多数的 时间都是CPU在等在I/O的读写操作,对CPU的使用率不高。这时一个线程处于等待的时候,另一个还可以在CPU里跑。

所以线程池大小可以设为=2倍的CPU数

FixedThreadPool

一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待

 public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

LinkedBlockingQueue是一个无界队列,就存在当线程池的状态是running时 阻塞队列一直可以接收任务。可能堆积大量的请求,从而导致 OOM。

CachedThreadPool

//底层还是调用ThreadPoolExecutor,不过参数有变化
//corePoolSize 竟然为0,maximumPoolSize为默认的最大值
//当任务队列满时,就会判断maximumPoolSize大小
//keepAliveTime 空闲线程的最大等待时间,,60s后立马销毁线程了
public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
 }

newCachedThreadPool创建一个可缓存的线程池,工作线程数量无上限,若长时间没有往线程池提交任务,则空闲线程等待1分钟就会被终止。允许创建的线程数量为Integer.MAX_VALUE可能会创建大量线程,从而导致 OOM。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是几个可能出现在 Java 线程池相关面试中的问题: 1. 什么是线程池?为什么要使用线程池线程池是一组预先创建的线程,用于执行多个任务,以避免在每个任务中都创建线程的开销。使用线程池可以提高应用程序的性能和可伸缩性。 2. Java 中有哪些线程池实现? Java 中提供了以下线程池实现:FixedThreadPool、CachedThreadPool、SingleThreadPool、ScheduledThreadPool。 3. FixedThreadPool 和 CachedThreadPool 的区别是什么? FixedThreadPool 用于执行固定数量的任务,而 CachedThreadPool 会动态地创建线程来处理任务。CachedThreadPool 可以根据需要创建新线程,因此适用于处理许多短暂的异步任务。 4. 如何设置线程池大小? 线程池大小应根据应用程序的需求和可用资源进行设置。如果线程池大小太小,则可能会导致任务排队等待可用线程执行,从而降低应用程序的性能。如果线程池大小太大,则可能会浪费大量资源。 5. 线程池任务的执行顺序是怎样的? 线程池任务的执行顺序取决于线程池的具体实现和任务的提交顺序。在 FixedThreadPool 中,任务按照它们被提交的顺序执行。在 CachedThreadPool 中,任务的执行顺序是不确定的,因为线程池会动态地创建和销毁线程以处理任务。 6. 如何处理线程池中的异常? 可以使用 Thread.UncaughtExceptionHandler 处理线程池中的异常。该接口定义了一个方法用于处理未捕获的异常。可以在创建线程池时设置 UncaughtExceptionHandler,以便在发生异常时执行相应的操作。 7. 线程池如何处理任务队列中的任务? 线程池会按照任务队列中任务的顺序依次执行任务。如果任务队列已满,则可以将任务拒绝,并执行特定的拒绝策略。可以使用 ThreadPoolExecutor 类的 setRejectedExecutionHandler() 方法设置拒绝策略。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值