线程池
1.什么是线程池
字面意思,一次创建多个线程,放在一个池子(集合类),用的时候拿一个,用完了之后就放回这个池子就可以了。
2.为什么要用线程池
- 首先使用多线程编程就是为了提高效率,势必会创建很多线程,创建的过程是JVM通过调用系统API来申请系统的过程,虽然说创建线程的开销要比创建进程的开销要小的多,但是也架不住特别频繁的创建和销毁,而池化技术就可以减少线程的频繁创建与销毁,从而提高程序性能
- JVM调用系统API就意味着从用户态到内核态去执行,而一个系统只有一个内核态,这个内核需要处理很多的事情,所有的进程都是要兼顾到的
因此使用线程池的最主要的目的是为了提高效率,尽量减少从用户态到内核态的切换
3.怎么使用线程池
JDK中提供了一组不同的线程池的实例
public class Demo01 {
public static void main(String[] args) {
// 1. 用来处理大量短时间工作任务的线程池,如果池中没有可用的线程将创建新的线程,如果线程空闲60秒将收回并移出缓存
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建一个操作无界队列且固定大小线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 3. 创建一个操作无界队列且只有一个工作线程的线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 4. 创建一个单线程执行器,可以在给定时间后执行或定期执行。
ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
// 5. 创建一个指定大小的线程池,可以在给定时间后执行或定期执行。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
// 6. 创建一个指定大小(不传入参数,为当前机器CPU核心数)的线程池,并行地处理任务,不保证处理顺序
Executors.newWorkStealingPool();
}
}
以上方法都是用来获取线程池对象的,通过不同的工厂方法获取不同功能的线程池。
4.工厂模式
工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
为什么要使用工厂模式
这里我们用一个简单的例子来说明原因
public class Factory {
public static void main(String[] args) {
Student student = Student.createByAgeAndName(20, "张三");
System.out.println(student);
}
}
class Student{
private int id;
private int age;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student() {
}
public Student(int id, String name) {
this.id = id;
this.name = name;
}
public Student(int age, String name) {
this.age = age;
this.name = name;
}
}
观察上述代码,观察一下有什么问题,当我们想通过id
或者age
来创建一个学生类时,利用构造方法来创建时,出现了'Student(int, String)' is already defined in...
这里的语法不符合Java
语法中重载的语法规则,因此我们使用工厂模式可以解决这类问题。
public class Factory {
}
class Student{
private int id;
private int age;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student() {
}
// 通过方法名的区分来分别实现不同的创建对象的方法
public static Student createByIdAndName(int id,String name){
Student student=new Student();
student.setId(id);
student.setName(name);
return student;
}
public static Student createByAgeAndName(int age,String name){
Student student=new Student();
student.setAge(age);
student.setName(name);
return student;
}
}
对于工厂模式可以参考以下教程 工厂模式
5.自己实现一个线程池
实现步骤:
- 管理任务的一个队列,可以用阻塞队列去实现,使用阻塞队列的好处是,当线程去取任务时,如果队列为空那么就阻塞等待,不会造成过多的CPU资源消耗
- 提供一个往队列中添加任务的方法
- 创建多个线程,扫描这个队列,如果有任务就拿出来执行
public class MyThreadPool{
//定义一个阻塞队列来管理任务
BlockingQueue<Runnable>queue=new LinkedBlockingQueue<>();
/**
* 提供一个往队列中添加任务的方法
* @param runnable
* @throws InterruptedException
*/
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
/**
* 提供一个指定了创建线程数的构造方法
* @param num
*/
public MyThreadPool(int num){
if(num<=0){
throw new RuntimeException("线程数必须大于0");
}
// 创建线程
for (int i = 0; i < num; i++) {
Thread thread = new Thread(() -> {
while (true){
try {
Runnable runnable=queue.take();
runnable.run();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//启动线程
thread.start();
}
}
}
6.创建系统自带的线程池
在开发过程中一般使用ThreadPoolExecutor
这个类来创建线程池,以下为每个参数的代表意义
代码实现
public class Demo {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3,//最大线程数
10,//最大线程数
1,//临时线程的存活时间
TimeUnit.SECONDS,//临时线程的存活时间单位
new LinkedBlockingQueue<>(20),//阻塞队列的类型和大小
);
for (int i = 0; i < 100; i++) {
int taskId=i;
threadPoolExecutor.submit(()->{
System.out.println("执行任务 " +taskId+",当前线程:"+Thread.currentThread().getName());
});
}
}
}
6.1 拒绝策略
6.2 线程池的工作流程
关于线程池的分享就到这里了,看完留下的你们的三连吧,你们的支持是我最大的动力!!!