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)
任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,如果小于则创建线程来执行提交的任务,否则将任务放入workQueue队列,如果workQueue满了,则判断当前线程数量是否小于maximumPoolSize,如果小于则创建线程执行任务,否则就会调用handler,以表示线程池拒绝接收任务。
线程池中线程设置为多少合适呢?我们采用的计算公式如下:
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 密集任务?
(1)工作线程启动后,就进入runWorker(Worker w)方法,里面是一个while循环,循环判断任务是否为空:
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) {
}
}
}
创建一个定时任务,每隔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();
...
}
}
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是共享的,则不能关闭,不然需要重复创建线程池