目录
1.前言
2. Java中的线程池以及参数介绍
2.1 核心线程数和最大线程数
2.2最大空闲存活时间
2.3任务队列和线程工厂
2.4 拒绝策略(最重要)
2.5 线程池的类型
3.线程池的大小如何确定
4.手动写一个线程池
我们知道.在开发过程中.为了效率,会引进很多池,比如常量池,对象池,字符串池.今天我们来介绍另一种可以管理线程的池,线程池.我们知道在多线程编程中,线程的开销是比较大的,如线程的摧毁和创建.为了解决这种问题,我们引入了线程池这个概念,它可以把部分线程的创建和销毁给省去了,而是直接放到特定的数据结构里,需要用的时候再拿出来.这样就可以极大的提升程序的效率
在Java中,也为我们封装了线程池的类,ThreadpollExecutor类,而这个类里面有几个重要的参数。它的构造方法有四种。
我们这里来重点介绍一下第四个构造方法,因为这个构造方法里的参数是最完整的。它包含了前面几个构造方法里的参数。
int corePoolSize 核心线程数,相当于是正式的线程,正式工。
int maxmumPoolSize 最大线程数,除了核心线程,还有临时线程,相当于临时工,实习生。
long keepAliveTime 实习生线程允许的最大空闲存活时间。 就是除了核心线程数之外的线程,在没有被利用到的时候,可以存在的时间。
TimeUnit unit 最大空闲存活时间单位,(s,hour,day。。。)
BlockkingQueue
Threadfactry threadFactory 线程工厂
工厂模式可以解决Java语法的缺陷,就是在重载的时候,参数不能相同,但实际上,我们会有这种业务需求。比如,我们需要通过构造方法,来创建类,但是我们想传入的形参它的意义不同,但是类型和个数都一样。在Java基本语法中,是做不到的。但是我们可以通过把构造方法进行封装, 把它们放到工厂类里面去,通过工厂类来进行创建对象。
RejectedExecutionHandler handler
线程池中,会有一个阻塞队列。所以它能容纳的线程数量是有限的,当任务队列里的线程数满了以后,线程池会做出什么样的举动。这个是可以我们来控制一下的,所以就引入了 我们的拒绝策略。
在标准库里,Java为我们提供了四种拒绝策略。
我们来逐个解释一下这些拒绝策略分别是什么:
ThreadPoolExecutor.AbortPolicy
抛出一个 RejectedExecutionException ,举个例子,就好比我现在在家做家务,手头有好几个活,我妈这时候让我在做一个新的家务,如果是这种拒绝策略。那就是我情绪崩溃了,哇的一声就哭出来了,然后撂挑子不干了。
ThreadPoolExecutor.CallerRunsPolicy
新的任务,由添加任务的线程去执行。也就是我上述例子中,我给我妈说,我不干,你去做。
ThreadPoolExecutor.DiscardOldestPolicy
被拒绝的任务的处理程序,丢弃最旧的未处理请求。就是把队列中,最老的那个线程给丢掉,然后加入这个最新的。就是上述例子中,我有一个最早的家务,比如是拖地,我就不去做这个了,而是把新家务给放到我的家务计划中。
ThreadPoolExecutor.DiscardPolicy
直接丢掉最新的,不要别的线程加入,加不进去。就是我直接拒绝了给我安排的家务,并不会出现在我的家务计划列表中。
Java标准库中,有这么四类线程池的类型。我们来通过图片来给大家看看。
如果只是简单用一下,就用这几种就可以了。如果需要高度定制化,那么就需ThreadPoolExecutor。来逐个设置参数了。阿里巴巴编程规范,要我们使用ThreadPoolExecutor。来让线程池更可控。可以参考一下。大家要以实际开发入职的编程规范为准。
网上有很多种关于线程池大小如何设定的说法,有的说如果cpu逻辑核心数是N,那么就应该设置为N个,1.5N.2N...但是这些说法都不严谨。
我们要看具体的代码里,线程中是cpu密集型的操作,还是io密集型的操作。如果是cpu密集型的操作,那么就设置为N就行,如果是io密集型的操作,这个时候对于cpu的消耗就比较小,我们就可以设置为远大于N的线程池大小。
但是在具体的工作开发中,这个东西是很难通过看代码来确定的,因为任务会很复杂,大概率是cpu和io同时都有,所以综上所述,最好的方法就是通过实验来确定。设置多大的合适,对于性能的提升最大。
实践是检验真理的唯一标准!
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
class MyThreadPoolExcutor {
private BlockingQueue queue = new ArrayBlockingQueue<>(1000);
private List list = new ArrayList<>();
public MyThreadPoolExcutor(int a){
for (int i = 0; i < a; i++) {
Thread t =new Thread(()->{
while (true){
try {
Runnable runnable =queue.take();
runnable.run();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
list.add(t);
}
}
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
}
public class MyThreadExcutor {
public static void main(String[] args) throws InterruptedException {
MyThreadPoolExcutor myThreadPoolExcutor = new MyThreadPoolExcutor(4);
for (int i =0; i < 1000; i++) {
int a= i;
myThreadPoolExcutor.submit(new Runnable() {
@Override
public void run() {
System.out.println("打印变量" + a +"执行的线程为" + Thread.currentThread().getName());
}
});
}
}
}
在上述代码里,我们手动创建了一个线程池,是固定线程的线程池。里面有一个带阻塞功能的任务队列,当我们put进去新的任务的时候,会进入到任务队列中。然后线程池里的线程就会执行这个任务,至于是哪个线程,是随机的一个。
这段代码实现了一个简单的线程池,能够并发执行多个任务,而不需要为每个任务都创建一个新的线程。