JAVA多线程(二)--线程池

JAVA多线程(二)--线程池

一、线程池概念

顾名思义,线程池是管理线程的池子。使用线程池有以下优点:

  • 降低线程创建和销毁的开销。
  • 提高响应速度。用到时创建和直接使用已创建好的线程,速度肯定是不一样的。
  • 提高线程可管理性。线程是稀缺资源,使用线程池可对线程进行统一分配、调优和监控。

二、JUC架构

image

1、Executor接口

Executor 接口是JUC架构的顶级接口,它只包含一个void execute(Runnable command)方法,是一个执行线程的工具。

public interface Executor {
    void execute(Runnable command);
}

2、ExecutorService接口

ExecutorService接口继承于Executor 接口,向外提供了接收异步任务的服务。

public interface ExecutorService extends Executor {

    // 接收单个异步任务
    <T> Future<T> submit(Callable<T> task);
	
    <T> Future<T> submit(Runnable task, T result);

    Future<?> submit(Runnable task);

    // 批量接收异步任务
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;

    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
}

3、AbstractExecutorService抽象类

抽象类,实现了ExecutorService接口

4、ScheduledExecutorService接口

ScheduledExecutorService接口继承了ExecutorService接口,是一个可以完成'延时'和'周期性'任务的调度线程池接口。

5、ThreadPoolExecutor类

ThreadPoolExecutor类继承了AbstractExecutorService抽象类,是线程池中核心实现类。

6、ScheduledThreadPoolExecutor类

ScheduledThreadPoolExecutor类实现了ScheduledExecutorService接口,继承了ThreadPoolExecutor类。拓展实现了延时执行和周期执行等抽象方法。

7、Executors

静态工厂类,它通过静态工厂方法返回ExecutorServiceScheduledExecutorService等线程池示例对象

三、使用Executors静态工厂类创建线程池

1、newFixedThreadPool 固定线程数的线程池

创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。如果所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在执行期间有由于失败导致任何线程终止,那么一个新线程将代替它执行后续任务。在某个线程被显示的关闭之前,池中的线程将一直存在。

弊端:阻塞队列是无界队列,大量任务涌入时,会导致队列很大,容易导致JVM出现OOM异常,即内存溢出。

2、newSingleThreadExecutor 单个线程的线程池

创建一个只有一个线程的线程池。这个线程池可以在线程死后(或发生异常),重新启动一个线程来替代原先的线程继续执行后续任务。

弊端:同固定数量线程池,阻塞队列无界。

3、newCachedThreadPool 缓存线程池

创建一个可根据需要创建新线程的线程池,但是在以前线程可用时将重用它们。当前线程池中有可用线程时将重用已有线程,当线程池中无可用线程将创建新的线程加入到线程池中执行新的任务。终止并移除那些已有60s未被使用的线程。

弊端:线程数没有限制,由于其maximumPoolSize的值为Integer.MAX_VALUE(非常大),可以认为可以无限创建线程,如果任务提交较多,就会造成大量的线程被启动,很有可能造成OOM异常,甚至导致CPU线程资源耗尽

4、newScheduledThreadPool 可调度线程池

创建一个可安排在给定延迟后执行任务的线程池。线程池中包含最小限度固定数量的线程corePoolSize,即使空闲状态也一直存在,除非设置了allowCoreThreadTimeOut。当初始线程不够时会创建新的线程加入线程池,这部分线程会因为限制状态被释放

弊端:线程数不设上限

5、newSingleThreadScheduledExecutor 单个线程的可调度线程池

创建一个corePoolSize 为1的可安排在给定延迟后执行任务的线程池。

弊端:线程数不设上限

6、newWorkStealingPool 工作窃取式线程池

Java8新增的创建线程池方法。实际返回ForkJoinPool对象,创建时如果不设置任何参数,则以当前机器处理器个数作为线程个数,此线程池会并行处理任务,不能保证执行顺序。使用所有可用的处理器作为其目标并行度级别创建一个窃取工作的线程池。
使用场景:能够合理的使用CPU进行对任务操作(并行操作),适合使用在很耗时的任务中。底层用的ForkJoinPool 来实现的。 ForkJoinPool的优势在于,可以充分利用多cpu,多核cpu的优势,把一个任务拆分成多个“小任务”分发到不同的cpu核心上执行,执行完后再把结果收集到一起返回。

四、使用ThreadPoolExecutor创建线程池

其实Executors中除去Java8新加的newWorkStealingPool方法外(调用的ForkJoinPool),其他的创建线程池的方法基本上都是调用了ThreadPoolExecutor的构造函数。
ThreadPoolExecutor提供了一系列属性,供我们根据实际业务来创建合适的线程池;

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  • int corePoolSize: 核心线程最大数量,通俗点来讲就是,线程池中常驻线程的最大数量
  • int maximumPoolSize: 线程池中运行最大线程数(包括核心线程和非核心线程)
  • long keepAliveTime: 线程池中空闲线程(仅适用于非核心线程)所能存活的最长时间
  • TimeUnit unit: 存活时间单位,与keepAliveTime搭配使用
  • BlockingQueue<Runnable> workQueue:存放任务的阻塞队列
  • ThreadFactory threadFactory:新线程的产生方式
  • RejectedExecutionHandler handler: 线程池拒绝策略

注:若调用了allowCoreThreadTimeOut(boolean)方法,并且传入了参数true,则keepAliveTime参数所设置的Idle超时策略也将被应用于核心线程

五、向线程池提交任务

1、execute()

  • void execute(Runnable command)

Executor接口中的方法, ThreadPoolExecutor中实现,ScheduledThreadPoolExecutor中重写等。
只能接收Runnable,无返回值

2、submit()

  • <T> Future<T> submit(Callable<T> task)
  • <T> Future<T> submit(Runnable task, T result)
  • Future<?> submit(Runnable task)

这3个submit()方法都是ExecutorService接口中定义的方法, AbstractExecutorService中实现,ScheduledThreadPoolExecutor中重写等。

可以接收Callable``Runnable,有返回值。

3、invokeAny()

批量提交,返回第一个返回值。

4、invokeAll()

批量提交,返回所有返回值。

六、线程池执行流程

image

1、当核心线程数未满时,即使当前有空闲线程,也会优先创建新的线程。
2、如果当前核心线程数达到上限时,新的任务会被分配到阻塞队列中,一直到阻塞队列已满。
3、当一个任务被完成时,执行器优先从阻塞队列中获取任务执行,一直到阻塞队列为空。
4、当核心线程数已满,而且阻塞队列也满了时,接收到新的任务,将会创建新的线程(非核心线程),并马上执行新任务。
5、当核心线程和阻塞队列都已满时,会一直创建新的线程执行新任务,直达线程数超出maximumPoolSize。如果超出maximumPoolSize,线程池会拒绝接收任务。当新任务过来时,执行拒绝策略。

注:

  • corePoolSize maximumPoolSize BlockingQueue等参数如果配置的不合理,可能会造成异步任务得不到预期的执行效果,造成严重的排队现象;
  • 创建新线程的顺序: corePoolSize已满后,在BlockingQueue也满之后,才会创建新的线程,直到超出maximumPoolSize

七、阻塞队列

当一个线程从空的阻塞队列中获取任务时,会被阻塞,直到队列中有了元素。当队列中有了元素,队列会被自动唤醒。
常见的阻塞队列:

  • ArrayBlockingQueue: 使用数组实现的有界阻塞队列,特性先进先出,创建时必须设置大小;
  • LinkedBlockingQueue: 使用链表实现的阻塞队列,特性先进先出,可以设置其容量,默认为Interger.MAX_VALUE,特性先进先出;
  • PriorityBlockingQueue: 使用平衡二叉树堆,实现的具有优先级的无界阻塞队列;
  • DelayQueue: 无界阻塞延迟队列,队列中每个元素均有过期时间,当从队列获取元素时,只有过期元素才会出队列。队列头元素是最块要过期的元素;
  • SynchronousQueue: 一个不存储元素的阻塞队列,每个插入操作,必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态;

八、线程工厂

ThreadFactory是Java线程工厂接口,只有一个方法Thread newThread(Runnable r),调用它创建新线程时可以更改所创建的新线程的名称、线程组、优先级、守护进程状态等。

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}
  • ThreadFactory是线程工厂,用于创建线程;Executors是线程池工厂类,用于便捷创建线程池。

九、拒绝策略

触发情况:

  • 线程池已经关闭
  • corePool已满,阻塞队列已满,且maximumPoolSize已满
    image
    ThreadPoolExecutor 中主要提供了四种拒绝策略:

1、AbortPolicy

拒绝策略。新任务进入会被拒绝,并抛出 RejectedExecutionException 异常。该策略是线程池默认的拒绝策略。

2、DiscardPolicy

抛弃策略。新任务被直接丢掉,且不会抛出任何异常。

3、DiscardOldestPolicy

抛弃最老任务策略。将最早加入队列的任务抛弃,并尝试加入队列。

4、CallerRunsPolicy

调用者执行策略。新任务被添加到线程池时,如果添加失败,那么提交任务线程会自己去执行该任务,不会使用线程池中的线程去执行新任务。

十、调度器的钩子方法

调度器的钩子方法定义在ThreadPoolExecutor中,三个方法都是空方法,一般在子类中重写。

public class ThreadPoolExecutor extends AbstractExecutorService {
    // ...
    // 任务执行之前的钩子方法
    protected void afterExecute(Runnable r, Throwable t) { }
    // 任务执行之后的钩子方法
    protected void beforeExecute(Thread t, Runnable r) { }
    // 线程池终止时的钩子方法
    protected void terminated() { }
    // ...
public class MyThreadPoolTest {
    public static final int SLEEP_TIME = 1000;
    static class MyThreadFactory implements ThreadFactory {
        // 自增对象
        static AtomicInteger threadNo=new AtomicInteger(1);
        @Override
        public Thread newThread(Runnable r) {
            String threadName = "MyThread-"+ threadNo;
            System.out.println("创建一条新线程," + threadName);
            threadNo.incrementAndGet();
            Thread thread=new Thread(r, threadName);
            thread.setDaemon(true);
            return thread;
        }
    }
    static class TargetTask implements Runnable{
        static AtomicInteger taskNo=new AtomicInteger(1);
        public String taskName;
        public TargetTask(){
            taskName="task-"+taskNo;
            taskNo.incrementAndGet();
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+": "+taskName+" is running...");
            try {
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(taskName+" end...");
        }
    }

    public static void main(String[] arg) throws InterruptedException {
        ExecutorService pool = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2)){
            @Override
            protected void terminated() {
                System.out.println("调度器已停止...");
            }
            @Override
            protected void beforeExecute(Thread t,Runnable target) {
                System.out.println(t.getName() + " 前钩执行...");
                super.beforeExecute(t, target);
            }
            @Override
            protected void afterExecute(Runnable target,Throwable t) {
                System.out.println("后钩执行...");
                super.afterExecute(target, t);
            }
        };
        for(int i=0;i<5;i++){
            pool.submit(new TargetTask());
        }
        Thread.sleep(5000);
        pool.shutdown();
    }

}
/**
pool-1-thread-1 前钩执行...
pool-1-thread-3 前钩执行...
pool-1-thread-2 前钩执行...
pool-1-thread-2: task-2 is running...
pool-1-thread-3: task-5 is running...
pool-1-thread-1: task-1 is running...
task-1 end...
后钩执行...
pool-1-thread-1 前钩执行...
pool-1-thread-1: task-3 is running...
task-2 end...
后钩执行...
pool-1-thread-2 前钩执行...
pool-1-thread-2: task-4 is running...
task-5 end...
后钩执行...
task-4 end...
task-3 end...
后钩执行...
后钩执行...
调度器已停止...

Process finished with exit code 0
**/

十一、关闭线程池

1、线程池的5种状态

  • RUNNING: 线程池创建之后的初始状态,这种状态下可以执行任务
  • SHUTDOWN:该状态下线程池不再接受新任务,但是会将工作队列中的任务执行完毕
  • STOP:该状态下线程池不再接受新任务,也不会处理工作队列中的剩余任务,并且将会中断所有工作线程
  • TIDYING:该状态下所有任务都已终止或者处理完成,将会执行terminated()钩子方法
  • TERMINATED:执行完terminated()钩子方法之后的状态
    // 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;

2、线程池状态流转

image

3、几种关闭线程池的方法

1、shutdown()

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

等待当前工作队列中的剩余任务全部执行完成之后,才会执行关闭,但是此方法被调用之后线程池的状态转为SHUTDOWN,线程池不会再接收新的任务

2、shutdownNow()

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

立即关闭线程池的方法,此方法会打断正在执行的工作线程,并且会清空当前工作队列中的剩余任务,返回的是尚未执行的任务

3、awaitTermination()

    public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (;;) {
                if (runStateAtLeast(ctl.get(), TERMINATED))
                    return true;
                if (nanos <= 0)
                    return false;
                nanos = termination.awaitNanos(nanos);
            }
        } finally {
            mainLock.unlock();
        }
    }

等待线程池完成关闭, shutdown()shutdownNow()方法之后,用户程序都不会主动等待线程池关闭完成
在设置的时间timeout内如果线程池完成关闭,返回true, 否则返回false

posted @   残忍的幻象饭团  阅读(121)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
点击右上角即可分享
微信分享提示