java面试——线程池

相关问题

1、工作流程是怎么样的。
2、核心参数。
3、任务结束之后如何恢复到核心线程数。
4、线程池运行时有一个线程报错了会怎么样。
5、如何合理设置核心线程数的大小
6、不同子线程之间怎么传递数据
7、5个核心线程数,30个任务队列,20个最大线程数,200s存活时间,一个for循环加入20个任务,
这个时候线程池中有多少个。

1、工作流程是怎么样的。

execute方法

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         * 1.如果运行的线程少于核心线程数,尝试新建一个线程作为第一个任务。
         *
         * 2. 如果有任务可以成功排队,那就加入队列
         *
         * 3. 如果无法加入队列,那么尝试添加一个新线程。
         * 要是失败了就知道被关闭或包和了,因此拒绝这项任务
         */
        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);
    }

image.png

2、核心参数。如何合理设置核心线程数的大小

  1. 最⼤线程数maximumPoolSize

  2. 核⼼线程数corePoolSize

    任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为 所有核心线程每秒处理任务数 * 每个任务响应时间 = 每秒任务总响应时间 ,即(corePoolSizethreadtasks)responsetime: (2010)2=400,即队列长度可设置为400。

  3. 活跃时间keepAliveTime

    当负载降低时,可减少线程数量,当线程的空闲时间超过keepAliveTime,会自动释放线程资源。默认情况下线程池停止多余的线程并最少会保持corePoolSize个线程。

  4. 阻塞队列workQueue

    BlockingQueue workQueue = null; workQueue = new ArrayBlockingQueue<>(5);//基于数组的先进先出队列,有界 workQueue = new LinkedBlockingQueue<>();//基于链表的先进先出队列,无界 workQueue = new SynchronousQueue<>();//无缓冲的等待队列,无界

  5. 拒绝策略RejectedExecutionHandler

可继承后进行自定义。

提供的拒绝策略有:

  1. AbortPolicy:直接丢弃任务,抛出异常,这是默认策略
  2. CallerRunsPolicy:只⽤调⽤者所在的线程来处理任务
  3. DiscardOldestPolicy:丢弃等待队列中最旧的任务,并执⾏当前任务
  4. DiscardPolicy:直接丢弃任务,也不抛出异常
  5. allowCoreThreadTimeout

默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。

一般说来,大家认为线程池的大小经验值应该这样设置:(其中N为CPU的个数)

  • 如果是CPU密集型应用,则线程池大小设置为N+1
  • 如果是IO密集型应用,则线程池大小设置为2N+1

3、任务结束之后如何恢复到核心线程数/线程池中多余的线程是如何回收的?

什么时候回收线程?

ThreadPoolExecutor回收工作线程,一条线程getTask()返回null,就会被回收。

分为两种情况

一、未调用shutdown() ,RUNNING状态下全部任务执行完成的场景

线程数量大于corePoolSize,线程超时阻塞,超时唤醒后CAS减少工作线程数,如果CAS成功,返回null,线程回收。否则进入下一次循环。当工作者线程数量小于等于corePoolSize,就可以一直阻塞了。

二、调用shutdown() ,全部任务执行完成的场景

2.1 所有线程都在阻塞

中断唤醒,进入循环,都符合第一个if判断条件,都返回null,所有线程回收。

2.2 任务还没有完全执行完

至少会有一条线程被回收。在processWorkerExit(Worker w, boolean completedAbruptly)方法里会调用tryTerminate(),向任意空闲线程发出中断信号。所有被阻塞的线程,最终都会被一个个唤醒,回收。

如何被回收的。

private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
//这里就是回收线程的主要操作了,移除线程池对该线程的引用,使其可以被JVM正常地回收
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

在runWorker()方法中被执行

image.png

ThreadPoolExecutor是怎么回收线程的:

https://juejin.cn/post/6922069411981426702

4、线程池运行时有一个线程报错了会怎么样。

当一个线程池里面的线程异常后:

1、当执行方式是execute时,可以看到堆栈异常的输出

ThreadPoolExecutor.runWorker()方法中,task.run(),即执行我们的方法,如果异常的话会throw x;所以可以看到异常。

2、当执行方式是submit时,堆栈异常没有输出。但是调用Future.get()方法时,可以捕获到异常

ThreadPoolExecutor.runWorker()方法中,task.run(),其实还会继续执行FutureTask.run()方法,再在此方法中c.call()调用我们的方法,如果报错是setException(),并没有抛出异常。当我们去get()时,会将异常抛出。

3、不会影响线程池里面其他线程的正常执行

4、线程池会把这个线程移除掉,并创建一个新的线程放到线程池中

当线程异常,会调用ThreadPoolExecutor.runWorker()方法最后面的finally中的processWorkerExit(),会将此线程remove,并重新addworker()一个线程。

execute的入参是Runnable, 没有返回值。任务通过execute提交后就基本和主线程脱离关系了。

而submit的入参可以是Callable(也可以是Runnable),并且有返回值,返回的是一个Future对象,然后通过对象的get方法获取任务执行的结果。

5、不同子线程之间怎么传递数据

6、5个核心线程数,30个任务队列,20个最大线程数,200s存活时间,一个for循环加入20个任务,这个时候线程池中有多少个。

结合线程池的工作原理就能知道有5个活跃线程

代码.png
运行结果.png
最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容