线程池,就是一个池子嘛,但是这个池子不是装水的,而是装线程的;
想想我们平常水池的是啥,还不是为了用水的时候方便,节省了来回运水的时间;而且我们可以让水可以循环利用,水资源多么宝贵呀,节约了水资源;同理,当面试官再问你,线程池好处的时候,是不是就可以结合“水池作用”,跟面试官装一把了;
多线程是采用了Executor框架。具体的类图关系如下;ThreadPoolExecutor是线程池创建的核心类。然后线程池的具体创建方式如下几种:
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
public class CacheThread {
public static void main(String[] args) {
ExecutorService cachThread= Executors.newCachedThreadPool();
for (int i = 0; i <10 ; i++) {
final int temp=i;
cachThread.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"当前线程的==="+temp);
}
});
}
}
由上图,我们也得出了结果:虽然线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
最大线程数为Integer.MAX_VALUE,可能会创建很多的线程,然后导致OOM异常;
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
public class FixThread {
public static void main(String[] args) {
// 有5个线程
ExecutorService fixThread= Executors.newFixedThreadPool(5);
for (int i = 0; i <10 ; i++) {
final int temp=i;
fixThread.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"当前线程=="+temp);
}
});
}
}
}
我们可以得出结论:因为线程池的大小是5,所以它的好处是可以循环利用线程,合理的分配资源,去执行任务;
通过源码,可以看出,它是核心线程数和最大线程数是相同的,采用的是LinkedBlockQueue<>队列;
LinkedBlockQueue<>没有设定范围,它的值为Inter.Max_Value,这样类似于无界队列,然后会存储很多的线程,造成OOM异常;
创建一个定长线程池,支持定时及周期性任务执行
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService= Executors.newScheduledThreadPool(5);
for (int i = 0; i < 10; i++) {
final int temp=i;
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"当前线程"+temp);
}
},4, TimeUnit.SECONDS);
}
}
}
通过源码分析,它底层也是 调用的ThreadPoolExecutor。
同newCachedThreadPool一样,最大线程数为Integer.MAX_VALUE,可能会创建很多的线程,然后导致OOM异常;
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public class SingleThread {
public static void main(String[] args) {
ExecutorService singleThread= Executors.newSingleThreadExecutor();
for (int i = 0; i <10 ; i++) {
final int temp=i;
singleThread.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"当前线程"+temp);
}
});
}
}
}
同newFixThreadPool一样,LinkedBlockQueue<>没有设定范围,它的值为Inter.Max_Value,这样类似于无界队列,然后会存储很多的线程,造成OOM异常;
由上面的几中线程的创建方式,我们可以看出,他们底层都调用了 “ThreadPoolExecutor”,上面的方法都有弊端,所以我们可以用ThreadPoolExecutor然后自定义线程池,根据我们项目中的需要;
【案例一】此时运行5个任务===最大线程数+队列数
public class ThreadPoolExcutor {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor= new ThreadPoolExecutor(1,
2,
60L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
new ThreadPoolExecutor.AbortPolicy()
);
//此时运行5个任务,没有超过 最大线程数+队列数
for (int i = 1; i <=5 ; i++) {
final int temp=i;
taskThread t1=new taskThread("任务线程");
threadPoolExecutor.execute(t1);
}
}
}
class taskThread implements Runnable{
private String taskName;
public taskThread(String taskName){
this.taskName=taskName;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"当前现成的任务"+taskName);
}
}
运行的结果:
【案例二】:此时用了两个线程,进行了复用,但是我们如果尝试一下运行6个任务(超过了最大线程数+队列的数据);
运行结果:
由结果可知,我们也就更进一步的明白了线程池的运行原理,当我们执行的任务超过了 最大线程数+队列中的线程,然后线程池就会采用一些拒绝策略,进行拒绝;
上面的例子中,我采用的是默认AbortPolicy的拒绝策略,然后直接抛异常,下面我演示一下用 “CallerRunsPolicy”进行调用,查看一下结果;
我们在项目中使用线程池需要自定义最大线程数,那我们可以根据以下两点进行判断;
1 . IO密集型: 即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的;
这时候我们可以设定:最大线程数=CPU核数 * 2;
2 CPU密集型:该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
这时候我们可以设定:最大线程数=CPU核数 ;
在项目中我们需要多个任务的处理的时候,比如 使用多线程去消费mq中的数据等等;
在跟面试官吹的时候,可以借鉴上面的思虑然后在加上在项目中的经验,调理清晰;让面试官也大开眼界,不选你,选谁!