并发编程之线程池ThreadPoolExecutor分析上

目录

线程池

内置线程池

自定义线程池

线程池执行任务的方式

线程池的五种状态

线程池中线程的关闭

线程池源码的基础属性


线程池

线程池可以分为两种主要类型:内置线程池和自定义线程池。

内置线程池

1. FixedThreadPool(固定大小线程池)

使用Executors.newFixedThreadPool(int n)创建,其中n是池中线程的数量。
固定大小,适用于处理固定数量的任务。

ExecutorService executor = Executors.newFixedThreadPool(5);

2. CachedThreadPool(缓存线程池)

使用Executors.newCachedThreadPool()创建。
处理大量的短期异步任务,可以根据需要创建新线程,但会重用之前构造的线程。

ExecutorService executor = Executors.newCachedThreadPool();

3. SingleThreadExecutor(单线程线程池)

使用Executors.newSingleThreadExecutor()创建。
只有一个线程的线程池,适用于按顺序执行任务的场景。

ExecutorService executor = Executors.newSingleThreadExecutor();

4. ScheduledThreadPool(定时线程池)

使用Executors.newScheduledThreadPool(int corePoolSize)创建,其中corePoolSize是池中线程的数量。
用于执行定时任务或者周期性任务。

ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
自定义线程池

可以通过ThreadPoolExecutor类来自定义线程池。自定义线程池可以更灵活地配置线程池的各种参数。

import java.util.concurrent.*;

public class CustomThreadPool {
    public static void main(String[] args) {
        int corePoolSize = 5;
        int maxPoolSize = 10;
        long keepAliveTime = 5000; // 5 seconds

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maxPoolSize,
                keepAliveTime,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>()
        );

        // 使用 executor 执行任务
        executor.execute(() -> System.out.println("Executing task"));

        // 关闭线程池
        executor.shutdown();
    }
}

corePoolSize是线程池的基本大小,maxPoolSize是线程池的最大大小,keepAliveTime是线程在没有任务执行时可以保持存活的时间。


线程池执行任务的方式

       两种主要的方法来提交任务:execute 方法和 submit 方法。这两种方法都用于将任务提交给线程池执行,它们在返回结果和异常处理上有一些区别。

1. execute 方法

execute方法是ThreadPoolExecutor类的基础方法,用于提交不需要返回结果的任务。

void execute(Runnable command);
ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize,
        maxPoolSize,
        keepAliveTime,
        TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<>()
);

executor.execute(() -> {
    // 任务的执行逻辑
    System.out.println("Executing task");
});

// 关闭线程池
executor.shutdown();

使用execute方法提交的任务是Runnable类型的,它不能返回结果。

2. submit 方法

submit方法允许提交需要返回结果的任务,并且可以通过Future对象获取任务的执行结果。

 Future submit(Callable task);
Future submit(Runnable task);
ThreadPoolExecutor executor = new ThreadPoolExecutor(
        corePoolSize,
        maxPoolSize,
        keepAliveTime,
        TimeUnit.MILLISECONDS,
        new LinkedBlockingQueue<>()
);

Future future = executor.submit(() -> {
    // 任务的执行逻辑
    return "Task completed";
});

// 获取任务执行结果
try {
    String result = future.get();
    System.out.println("Result: " + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

// 关闭线程池
executor.shutdown();

实际上submit方法中还是调用的execute方法

    public  Future submit(Callable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

线程池的五种状态

线程池在ThreadPoolExecutor类中有五种状态,这些状态通过ctl(控制状态的变量)字段表示。

1. RUNNING(运行)
        线程池处于正常运行状态。
        当线程池被创建并且execute方法被调用时,线程池会转换到这个状态。
2. SHUTDOWN(关闭)
        不再接受新任务,但会执行已经在队列中的任务。
        当调用 shutdown() 方法时,线程池会进入这个状态。
3. STOP(停止)
        不再接受新任务,不执行队列中的任务,并且中断正在执行的任务。
        当调用 shutdownNow() 方法时,线程池会进入这个状态。
4. TIDYING(整理)
        所有的任务已经终止,ctl 计数为零时,线程池会从 STOP 状态转换到 TIDYING 状态。
        在转换到 TERMINATED 状态之前,线程池会执行一些清理操作。
5. TERMINATED(终止)
        线程池彻底终止,不再处于任何活动状态。
        在执行完整个生命周期后,线程池会进入这个状态。

       这些状态是通过 ctl字段使用位运算进行控制的。ctl是一个AtomicInteger类型的字段,通过 CAS操作来保证线程安全。


线程池中线程的关闭

        Thread类提供了一个stop(),但是标记了@Deprecated,因为stop()方法太粗暴了,一旦调用了stop(),就会直接停掉线程,但是调用的时候根本不知道线程刚 刚在做什么,stop()会释放线程占用的synchronized锁(不会自动释放ReentrantLock锁)

       线程池中就是通过interrupt()来停止线程的,比如shutdownNow()方法中会调用

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }

线程池源码的基础属性

线程池的源码中,会通过一个AtomicInteger类型的变量ctl,来表示线程池的状态和当前线程池中 的工作线程数量。 一个Integer占4个字节,也就是32个bit,线程池有5个状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。线程池的源码中通过三个bit位来表示这五种状态。

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;

Integer.SIZE为32,所以COUNT_BITS为29,最终各个状态对应的二级制为:
RUNNING:11100000 00000000 00000000 00000000
SHUTDOWN:00000000 00000000 00000000 00000000
STOP:00100000 00000000 00000000 00000000
TIDYING:01000000 00000000 00000000 00000000
TERMINATED:01100000 00000000 00000000 00000000

        通过使用一个Integer数字的最高三个bit,就可以表示5种线程池的状态,而剩下的29个bit就 可以用来表示工作线程数,比如,假如ctl为:11100000 00000000 00000000 00001010,就表示 线程池的状态为RUNNING,线程池中目前在工作的线程有10个,这里说的“在工作”意思是线程活 着,要么在执行任务,要么在阻塞等待任务。

        下篇分析线程池的execute方法。

你可能感兴趣的:(并发编程,java,后端)