【多线程】线程池

发布于:2025-04-16 ⋅ 阅读:(21) ⋅ 点赞:(0)

前言

多线程的出现是为了优化多进程,优化进程创建、销毁的开销;而随着计算机技术的发展,人们又进一步优化多线程,提前创建线程减少多线程的开销,让程序更快更轻量。于是线程池诞生了,根据需求提前创建一堆线程,里面的线程也可以复用,减少创建销毁的内存开销,将创建和使用线程的步骤分开既减少了代码耦合度,也方便统一管理线程。线程池的实现(依靠ThreadPoolExecutor)开发者也是可以自定义的,包括池里线程的数量、池满的策略、单个线程的具体属性。

实现线程池的两种方式

Executor将线程池这一概念抽象出来,ExecutorService继承Executor接口,抽象类AbstractExecutorService类实现ExecutorService接口,ThreadPoolExecutor继承AbstractExecutorService,而最终通过ThreadPoolExecutor来实现线程池。在这里插入图片描述

Java可以通过Executors工厂类的静态方法和ThreadPoolExecutor实现线程池。ThreadPoolExecutor高度自由化,可以通过最多7个参数来自定义线程池,而Executors是对ThreadPoolExecutor的封装,Executors实现了几种常见的线程池,并对外提供了静态方法用于获取线程池实例。所以线程池的最底层就是ThreadPoolExecutor类。

ThreadPoolExecutor

任务”概念:Runnable类的run()方法主体内容

//构造方法
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler){}
参数名 定义及定位
corePoolSize 核心线程数(最小线程数);核心线程即便空闲也不会自动销毁,除非主动销毁或线程池关闭
maximumPoolSize 最大线程数(包括核心线程和非核心线程);非核心线程根据任务需要被创建,空闲超时(keepAliveTime)则被回收销毁
keepAliveTime 非核心线程最大空闲时间
TimeUnit unit 枚举变量;定义空闲时间的时间单位
BlockingQueue workQueue 工作阻塞队列;用于存放一系列任务并取出任务给线程池里的线程执行
ThreadFactory threadFactory 工厂类;一些类对Thread类的创建进行封装(自定义线程名、优化级等)达到构造不同属性的线程的目的,传入这些类可以让线程池里存在不同属性的线程
RejectedExecutionHandler handler 拒绝策略;用于处理线程池满、工作队列满的情况,默认提供了四种策略,开发者也可以自定义实现策略类

当向线程池提交一个任务时触发了拒绝策略:

四种拒绝策略 内容
AbortPolicy() 抛出异常,让用户来处理触发策略的这种情况
CallerRunsPolicy() 把这个新任务丢给调用submit()(添加这个任务)的线程处理
DiscardOldestPolicy() 丢弃队列里存在最久的任务(队首元素)并让这个新任务替换它
DiscardPolicy() 丢弃这个新来的任务,不管它

为了方便理解线程池的逻辑,这里模拟了一个简化版线程池方便理解:

import java.util.concurrent.ArrayBlockingQueue;

class MyThreadPool{
    private ArrayBlockingQueue<Runnable> queue=null;
    //n为核心线程数
    public MyThreadPool(int n){
        queue=new ArrayBlockingQueue(100);//队列最多任务数
        for (int i = 0; i < n; i++) {
            //内部定义一个线程来取出队列里的任务并执行
            Thread t = new Thread(() -> {
                try {
                    while (true) {
                        Runnable task = queue.take();
                        task.run();
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }

    }
    //提交任务到线程池里也就是工作队列里
    public void submit(Runnable task) throws InterruptedException {
        queue.put(task);
    }
}
class DemoThreadPool {

	public static void main(String[] args) throws InterruptedException {
	        MyThreadPool pool=new MyThreadPool(100);
	        for (int i = 0; i < 100; i++) {
	            int id=i;
	            pool.submit(()->{
	                System.out.println(Thread.currentThread().getName()+" id="+id);
	            });
	
	        }
	    }
}

结果:

在这里插入代码片

通过Executors静态方法

通过Executors静态方法创建线程池,以下方法返回值类型ExecutorService(实际上返回了一个实现ExecutorService接口的类的实例)(newScheduledThreadPool除外,返回值类型是ScheduledExecutorService(实际上返回了一个实现ExecutorService接口的类的实例),ScheduledExecutorService也是一个接口,继承了ExecutorService接口)。

方法名 作用
Executors.newFixedThreadPool() 创建一个固定线程数目的池
Executors.newCachedThreadPool() 创建一个线程数动态增加的池
Executors.newSingleThreadExecutor() 创建一个只有一个线程的池
Executors.newScheduledThreadPool() 相较于普通线程池,它可以控制任务执行时间,让任务定时或延时执行

使用newFixedThreadPool()替代上面的示例,效果类似:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        ExecutorService pool= Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            int j=i;
            //提交一个匿名Runnable类对象
            pool.submit(()->{
                System.out.println(Thread.currentThread().getName()+" i= "+j);
            });
        }
    }
}

Executors创建线程池的缺陷

由于Executors静态方法返回的是一个现成的线程池,它不能百分百的适配实际的开发需求和任务需要。例如:

  • newFixedThreadPool()和newSingleThreadExecutor()里的工作队列长度允许最大为Integer.MAX_VALUE,很有可能会被添加过多的任务,导致内存溢出;
  • newCachedThreadPool()和newScheduledThreadPool(),允许最大创建Integer.MAX_VALUE个线程,可能被发生大量线程被创建的情况,导致内存溢出。
    因此,最好使用ThreadPoolExecutor来创建线程池,这样线程数目、队列满情况处理、拒绝策略等也是可控的。
    最后,线程池里的线程都是前台线程,因此,上面两个示例程序在100层for循环任务结束后程序并未结束而是进入WATTING状态,线程池Running。想结束线程池,可以使用线程池对象调用三种方法:
方法名 作用
shutdown() 将队列里的任务执行完后再关闭线程池,在此期间线程池不再接收新任务(比下面的更优雅);线程池先SHUTDOWNSTOP
shutdownNow() 尝试中断正在执行的任务(是否响应中断取决于任务自己的逻辑),将余下的任务以链表形式返回(List< Runnable >);线程池STOP
awaitTermination(long timeout, TimeUnit unit) 设定超时时间,若超时则返回false,否则返回true。方法本身没有上面两个的关闭线程池作用,通常用来确认线程池是否关闭

完。