多线程--02--01--线程池常见面试题

一、线程池常见面试题

 

1.1、线程池参数(7-4-4)

1、corePoolSize:线程池的基本大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。说白了就是,即便是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。

2、maximumPoolSize:最大线程数,不管你提交多少任务,线程池里最多工作线程数就是maximumPoolSize。

3、keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,如果等了keepAliveTime时长还没有任务可执行,则多出corePoolSize的线程退出。

4、unit:这个用来指定keepAliveTime的单位,比如秒:TimeUnit.SECONDS。

5、workQueue(4种):用于保存等待执行任务的阻塞队列,提交的任务将会被放到这个队列里。

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列:添加、遍历元素效率高
  • LinkedBlockingQueue:是一个基于双向链表结构的阻塞队列:删除元素效率高
  • SynchronousQueue:是一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列

6、threadFactory:线程工厂,用来创建线程。创建线程时也可以给线程起名字,默认工厂的线程名字:pool-1-thread-3。

new ThreadFactory() {
    private AtomicInteger threadIndex = new AtomicInteger(0);

    @Override
    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncSenderExecutor_" + this.threadIndex.incrementAndGet());
    }
}

7、handler:拒绝策略(4种),即当线程和队列都已经满了的时候,应该采取什么样的策略来处理新提交的任务。默认策略是AbortPolicy(抛出异常),其他的策略还有:CallerRunsPolicy(只用调用者所在线程来运行任务)、DiscardOldestPolicy(丢弃队列里最近的一个任务,并执行当前任务)、DiscardPolicy(不处理,丢弃掉)

美团面试题:

1、阻塞队列是啥?

java.util.concurrent.BlockingQueue的特性是:当队列是空的时,从队列中获取或删除元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。阻塞队列不接受空值,当你尝试向队列中添加空值的时候,它会抛出NullPointerException。

2、无界阻塞队列会有什么问题?

当线程在执行任务时需要调用远程服务,当调用远程服务异常时,就会导致线程处理每个任务都需要等待很长的时间;处理任务的速度慢,产生任务的速度快,而任务队列是没有边界的,就会导致队列变得越来越大,从而导致内存飙升,还可能导致OOM内存溢出。所以往往需要指定阻塞队列大小:

public LinkedBlockingQueue(int capacity)

1.2、线程池运行原理

任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,如果小于则创建线程来执行提交的任务,否则将任务放入workQueue队列,如果workQueue满了,则判断当前线程数量是否小于maximumPoolSize,如果小于则创建线程执行任务,否则就会调用handler,以表示线程池拒绝接收任务。

1.3、线程池中线程设置为多少合适?

线程池中线程设置为多少合适呢?我们采用的计算公式如下:

多线程--02--01--线程池常见面试题_第1张图片

 1、I/O耗时、CPU耗时、现有CPU利用率可以通过APM工具(SkyWalking、zipkin等)获取; 

2、如上计算公式,适合于上线后根据运行数据设置,那么初始上线时如何设置线程池中线程数量呢:

根据经验:

1、cpu密集型:线程数量我们设为2N;

2、IO密集型:线程数量我们设为N+1;

其中N为cpu内核数量,可通过如下代码获取:

int N_CPUS = Runtime.getRuntime().availableProcessors();

3、如何判断是 CPU 密集任务还是 IO 密集任务?

  • CPU 密集型简单理解就是利用 CPU 计算能力的任务,比如你在内存中对大量数据进行排序。
  • 但凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。(web应用为IO密集型)

1.4、如何回收线程?

(1)工作线程启动后,就进入runWorker(Worker w)方法,里面是一个while循环,循环判断任务是否为空:

  • 若不为空,执行任务;
  • 若超过指定时间仍取不到任务,或发生异常,退出循环,执行processWorkerExit(w, completedAbruptly)将工作线程移除掉。
    final void runWorker(Worker w) {

        try {
            /**
            * while循环不断获取任务
            * (1)如果取到任务,则执行其中逻辑;
            *  (2) 如果超过一定时长仍为取到任务或者发生异常,则getTash()返回null,
            *      并执行processWorkerExit方法,processWorkerExit方法用于回收线程
            **/
            while (task != null || (task = getTask()) != null) {
                ...
            }
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }



    //执行线程回收
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }
    }

(2)getTask()源码

private Runnable getTask() {
        for (;;) {
            //线程池中,不会区分核心线程与非核心线程,只会对线程总数进行管理
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            

            try {
                Runnable r = timed ?
                    //poll()会阻塞读取队列中任务,超时后会返回null
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    //take() 是非阻塞读取队列中任务
                    workQueue.take();

                if (r != null)
                    return r;
            } catch (InterruptedException retry) {
                
            }
        }
    }

二、定时线程池使用举例

2.1 示例1

创建一个定时任务,每隔10秒执行一次

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.fuping3.rocketmq.javabasic.JavaBasicApplication;
import lombok.extern.slf4j.Slf4j;
 
 
@RunWith(SpringRunner.class)
@SpringBootTest(classes = JavaBasicApplication.class)
@Slf4j
public class ScheduleThreadTest {
 
    @Test
    public void test1() throws InterruptedException {
        //创建包含一个定时线程的线程池
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        /**
         * 延迟5秒启动
         * 启动后每隔10秒执行
         **/
        scheduledExecutorService.scheduleAtFixedRate(()->{
            log.info("schedule run pre 10 second");
        },5,10, TimeUnit.SECONDS);
 
        //保持程序运行状态
        while(true){
            Thread.sleep(1000);
        }
 
    }
}

打印输出

2021-11-24 22:05:18.519  INFO 9692 --- [pool-2-thread-1] ScheduleThreadTest                       : schedule run pre 10 second
2021-11-24 22:05:28.517  INFO 9692 --- [pool-2-thread-1] ScheduleThreadTest                       : schedule run pre 10 second
2021-11-24 22:05:38.525  INFO 9692 --- [pool-2-thread-1] ScheduleThreadTest                       : schedule run pre 10 second
 
.
.
.

2.2 示例2

springboot中使用定时任务

@Component
@Slf4j
public class A{
 
    @Scheduled(cron = "0 5 0 * * ?")
    public void scheduleMethod() {
        ...
        doScheduleMethod();
        ...
    }
}

三、ExecutorService的使用后注意关闭

1、使用完ExecutorService之后应该关闭它,否则它里面的线程会一直处于运行状态,会阻止JVM关闭。

public class PoolTest {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService excuterService= Executors.newFixedThreadPool(10);
        for (int i=0;i<5;i++){
            System.out.println(i+"...");
            excuterService.submit(new TA());
        }
        //ExecutorService使用之后注意关闭
        excuterService.shutdown();
    }
 
}
 
class TA implements Runnable{
 
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"----");
    }
}

2、我们可以调用ExecutorService.shutdown()方法关闭ExecutorService。在调用shutdown()方法之后,ExecutorService不会立即关闭,但是它不再接收新的任务,直到当前所有线程执行完成才会关闭,所有在shutdown()执行之前提交的任务都会被执行。

如果我们想立即关闭ExecutorService,我们可以调用ExecutorService.shutdownNow()方法。这个动作将跳过所有正在执行的任务和被提交还没有执行的任务。但是它并不对正在执行的任务做任何保证,有可能它们都会停止,也有可能执行完成。

3、web应用程序中,如果ExecutorService是共享的,则不能关闭,不然需要重复创建线程池

你可能感兴趣的:(多线程,多线程,java)