目录
前言
一、线程池简介
二、Java标准库中的线程池及其使用
ThreadPoolExecutor类构造方法参数介绍:
线程池的拒绝策略:(重点)
三、线程池的模拟实现
本篇博客主要介绍Java库里提供的创建线程池的方法和线程池的一些优点、工厂模式的简单介绍,ThreadPoolExecutor类构造方法参数的详细介绍,以及线程池的拒绝策略,还有模拟实现一个线程池等。
线程池是什么:顾名思义就是存放线程的池子,是一种常见的多线程处理机制,用于管理和复用一组预先创建好的线程。
线程池的优点:线程池可以有效的避免在需要时反复创建和销毁线程的开销,从而提高应用程序的性能和稳定性。
为什么从线程池拿线程比创建线程更加高效:首先从线程池里面拿线程,这是一种纯用户态的操作,而从系统创建线程涉及到内核态和用户态的切换,真正的创建线程是在内核中完成的。为什么内核态操作的效率低:在一个操作系统上只有一个内核,内核负责的事情是非常多的,所以如果把创建线程这件事事情交给了内核,则无法保证内核什么时候会执行,但如果是用户态层面的话,就可以直接去线程池中拿,不必等待内核创建了,效率自然就提高了。
Java中通过Executers创建线程池的一些方法:
通过Executors.newFixedThreadPool:
代码展示:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadDemo24 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(10);
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("我是第一个线程");
}
});
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("我是第二个线程");
}
});
}
}
上面代码就是一个简单创建了一个能存放是个任务的线程池;仔细观察我们会发现上面创建线程池的方法并不像我们之前一样,直接new一个对象,而是通过调用静态一个方法来创建对象的。这里的这种方式其实是工厂模式;
工厂模式是什么:工厂模式本质上就是为了来弥补构造方法的不足而来的一种模式。我们来看一个典型的例子来体会工厂模式的使用:
下面的代码就是当我们要构造平面上一个点时,可以通过x坐标和y坐标,也可以通过极坐标的形式,也就是知道半径和角度;但是这四个参数的类型都是double类型的,这时候我们提供的两个构造方法由于方法签名相同就无法构成重载了。这就是构造方法的一个不足之处了。
上述代码的解决方案:只需要提供两个不同的静态方法就可以解决上面的问题了。这种解决问题的方式就称为工厂模式。
如下图所示:通过Executors创建线程池的方法有很多,但是Executors 本质上是 ThreadPoolExecutor 类的封装.因此接下来我们就重点来介绍一下ThreadPoolExecutor类。
Executors 创建线程池的几种方式 :
newFixedThreadPool: 创建固定线程数的线程池
newCachedThreadPool: 创建线程数目动态增长的线程池.
newSingleThreadExecutor: 创建只包含单个线程的线程池.
newScheduledThreadPool: 设定延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
参数介绍:
corePoolSize:这个参数代表核心线程数,不能随便销毁(相当于一个公司中的正式员工,不能随便辞退)。
maximumPoolSize:这个参数代表最大线程数,空闲的时候就会销毁(相当于一个公司的实习员工,忙的时候招进来,空闲的时候就辞退)。
keepAliveTime:这个参数字面意思是保持存活时间,代表的是实习生线程存活的时间,当实习生线程空闲时,经过多久销毁线程。
TimeUnit unit:这个参数是指定参数存活时间的单位,分,秒,毫秒等等。
BlockingQueue
ThreadFactory threadFactory:这个参数是工厂模式,是一个创建线程的辅助类。
RejectedExecutionHandler handler:这个参数指的是当线程池满的时候,如何拒绝线程的再加入的拒绝策略:
第一个策略:第一个策略是直接抛出异常,全部任务就都执行不了了;
第二个策略:直接拒绝这个要添加进来的任务,让要添加的线程自己负责执行;
第三个策略:丢弃最老的任务,由于队列是先进先出的,也就是队头出,队尾进,所以就是丢弃队头任务;
第四个策略:丢弃最新任务,也就是队尾的任务,也就是以静默方式丢弃拒绝的任务。相当于把任务接过来后,直接丢弃了。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
class MyThreadPool{
//先创建一个阻塞队列存放线程
private BlockingQueue queue = new LinkedBlockingQueue<>();
public void submit(Runnable runnable) throws InterruptedException {//添加线程的方法
queue.put(runnable);
}
public MyThreadPool(int n){//可以指定大小的线程池
for (int i = 0; i < n; i++) {//创建10个线程来执行队列中的任务
Thread t = new Thread(()->{
try {
while (true) {
Runnable task = queue.take();
task.run();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
}
}
}
public class ThreadDemo25 {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool = new MyThreadPool(10);
for (int i = 0; i < 1000; i++) {
int num = i;
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println("任务"+num);
}
});
}
}
}