线程池简述

1. 什么是线程池

线程池是一种用于管理和复用线程的机制,它可以在需要执行多个任务时,通过维护一定数量的线程来提供并发执行的能力。线程池可以有效控制线程的创建、销毁和复用,从而减少线程创建与销毁的开销,并且能够更好地管理系统资源,提高应用程序的性能和效率。

2.使用线程池的优势

1.提高系统性能:通过复用线程,减少线程创建和销毁的开销,并且可以控制并发线程的数量,避免系统资源耗尽。
2.提高响应速度:线程池可以快速地获取可用线程来执行任务,减少任务等待的时间,提高应用程序的响应速度。
3.提供线程管理和监控:线程池提供了对线程的管理和监控功能,可以监控线程的状态、运行情况和异常,便于调试和故障排查。请添加图片描述

3.JUC的线程池架构请添加图片描述

JUC 就是 java.util .concurrent 工具包的简称,该工具包是从 JDK 1.5 开始加入到 JDK,用于完成高并发、处理多线程的一个工具包

1. Executor

Executor是Java中一个用于执行任务的接口,它定义了一种方式来执行异步任务,将任务的提交和执行进行解耦。Executor接口包含一个名为execute的方法,用于提交一个Runnable任务给执行器。

Executor框架提供了一种标准的、可重用的方式来执行任务,尤其适用于处理大量小型任务的情况,可以有效地管理和复用线程资源。

Executor框架的核心接口是Executor和ExecutorService

1.Executor接口:该接口定义了一个execute方法,用于接收一个Runnable任务并立即执行。

void execute(Runnable command);

2.ExecutorService接口:这是Executor的子接口,扩展了一些功能,比如可以提交Callable任务、创建可调度的任务等。

Future<?> submit(Runnable task);
<T> Future<T> submit(Callable<T> task);

Java提供了一些已实现了Executor和ExecutorService接口的类,例如:

1.ThreadPoolExecutor:用于创建线程池,灵活地管理线程资源。
2.ScheduledThreadPoolExecutor:在ThreadPoolExecutor的基础上增加了任务调度的功能,可以按照一定的时间间隔或时间点执行任务。

ScheduledExecutorService
ScheduledExecutorService 是一个接口,它继承于于 ExecutorService。它是一个可以完成“延时”“周期性”任务的调度线程池接口,其功能和 Timer/TimerTask 类似。
ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor 继承于 ThreadPoolExecutor,它提供了 ScheduledExecutorService线程池接口中“延时执行”和“周期执行”等抽象调度方法的具体实现。

ScheduledThreadPoolExecutor 类 似 于 Timer , 但 是 在 高 并 发 程 序 中ScheduledThreadPoolExecutor 的性能要优于 Timer

使用Executor接口和相关的实现类可以方便地管理任务的执行和线程资源的分配,提高多线程编程的效率和可维护性。

4.Executors快捷创建线程池

方 法 名方 法 名 功能简介
newSingleThreadExecutor()创建只有一个线程的线程池
newFixedThreadPool(int nThreads)创建固定大小的线程池
newCachedThreadPool()创建一个不限制线程数量的线程池,任何提交的任务都将立即执行,但是空闲线程会得到及时回收
newScheduledThreadPool()创建一个可定期或者延时执行任务的线程池

newSingleThreadExecutor线程池特点

  • 单线程化的线程池中的任务,是按照提交的次序,顺序执行的。
  • 池中的唯一线程的存活时间是无限的。
  • 当池中的唯一线程正繁忙时,新提交的任务实例会进入内部的阻塞队列(无界的阻塞队列)。

1.newFixedThreadPool线程池的特点

  • 如果线程数没有达到“固定数量”,则每次提交一个任务池内就创建一个新线程,直到线程达到线程池的固定的数量。
  • 线程池的大小一旦达到“固定数量”就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
  • 在接收异步任务的执行目标实例时,如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)。

2.newCachedThreadPool 特点

  • 在接收新的异步任务 target 执行目标实例时,如果池内所有线程繁忙,此线程池会添加新线程来处理任务。
  • 此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小
  • 如果部分线程空闲,也就是存量线程的数量超过了处理任务数量,那么就会回收空闲(60 秒不执行任务)线程。
  • “可缓存线程池”的弊端:线程池没有最大线程数量限制,如果大量的异步任务执行目标实例同时提交,可能导致创线程过多会而导致资源耗尽。

3.newScheduledThreadPool

使用ScheduledExecutorService可以方便地创建和管理定时任务,线程池会自动分配和管理线程资源,同时提供了灵活的任务调度方式,适用于定时任务的处理。
用法:

//方法一:创建一个可调度线程池,池内仅含有一条线程
public static ScheduledExecutorService newSingleThreadScheduledExecutor();
//方法二:创建一个可调度线程池,池内含有 N 条线程, N 的值为输入参数 corePoolSize
/* Executors.newScheduledThreadPool(int nThreads)是一个静态方法,用于创建一个ScheduledExecutorService对象,具有固定大小的线程池。
该方法将返回一个ScheduledExecutorService对象,它ScheduledThreadPoolExecutor类的实例。ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,实现了ScheduledExecutorService接口,提供了任务调度的功能。*/


public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) ;

通过调用ScheduledExecutorService的方法,如schedule()、scheduleAtFixedRate()、scheduleWithFixedDelay()等,可以提交定时任务和周期性任务。

scheduleAtFixedRate 方法的定义如下

public ScheduledFuture<?> scheduleAtFixedRate(
    Runnable command, //异步任务 target 执行目标实例;
    long initialDelay, //首次执行延时;
    long period, //两次开始执行最小间隔时间;
    TimeUnit unit //所设置的时间的计时单位,如 TimeUnit.SECONDS 常量;
);

scheduleWithFixedDelay方法的定义如下:

public ScheduledFuture<?> scheduleWithFixedDelay(
    Runnable command,//异步任务 target 执行目标实例;
    long initialDelay, //首次执行延时;
    long delay, //前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间);
    TimeUnit unit //所设置的时间的计时单位,如 TimeUnit.SECONDS 常量;
);

两个方法区别:

  • scheduleAtFixedRate 是从任务开始时算起
  • scheduleWithFixedDelay 是从任务结束时算起

4.下面代码是关于四个方法可以自行测试:

package Thred.Pool;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadPool {
    public static final int SLEEP_GAP = 500;
    public static class  TargetTask implements Runnable{
        static AtomicInteger taskNo = new AtomicInteger(1);
        protected String taskName;
        public TargetTask() {
            taskName = "task-" + taskNo.get();
            taskNo.incrementAndGet();
        }

        @Override
        public void run() {
            System.out.println("任务:" + taskName + " doing");

            try {
                Thread.sleep(SLEEP_GAP);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            System.out.println(taskName + " 运行结束.");
        }
    }

    public static void main(String[] args) {
        ThreadPool a =new ThreadPool();
//        a.testSingleThreadExecutor();
       // a.testNewFixedThreadPool();

       // a.testNewCacheThreadPool();
        //a.testNewScheduledThreadPool();
        a.testThreadPoolExecutor();

    }
    public void testSingleThreadExecutor(){
        ExecutorService pool = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5 ; i++) {
            pool.execute(new TargetTask());
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        pool.shutdown();
    }
    public void testNewFixedThreadPool(){
        ExecutorService pool = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            pool.execute(new TargetTask());

        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        pool.shutdown();
    }

    public void testNewCacheThreadPool(){
        ExecutorService pool = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            pool.execute(new TargetTask());
            pool.submit(new TargetTask());
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        pool.shutdown();
    }
    public void testNewScheduledThreadPool(){
        ScheduledExecutorService scheduled =Executors.newScheduledThreadPool(2);
        for (int i = 0; i < 2; i++) {
            scheduled.scheduleAtFixedRate(new TargetTask(),0,500, TimeUnit.MILLISECONDS);

        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        scheduled.shutdown();
    }
    public void testThreadPoolExecutor(){
        ThreadPoolExecutor executor =new ThreadPoolExecutor(
                2,
                4,
                100,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(2),new ThreadPoolExecutor.DiscardOldestPolicy());

        for (int i = 0; i < 8; i++) {
            final int taskIndex =i;
            Runnable task =new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() +  "   taskIndex = " + taskIndex );
                    try {
                        Thread.sleep(10000);
//                        Thread.sleep(Long.MAX_VALUE);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            };
            executor.execute(task);

            
        }
        while (true) {
            //每隔 1 秒,输出线程池的工作任务数量、总计的任务数量
            System.out.printf("- activeCount: %d - taskCount: %d\r\n", executor.getActiveCount(), executor.getTaskCount());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }

}}

5.线程池的标准创建方式

大部分企业的开发规范,都会禁止使用快捷线程池,要求通过标准构造器 ThreadPoolExecutor 去构造工作线程池。要求使用ThreadPoolExecutor创建线程池请添加图片描述

// 使用标准构造器,构造一个普通的线程池
public ThreadPoolExecutor(
    int corePoolSize, // 核心线程数,即使线程空闲(Idle),也不会回收;
    int maximumPoolSize, // 线程数的上限;
    long keepAliveTime, TimeUnit unit, // 线程最大空闲(Idle)时长
    BlockingQueue workQueue, // 任务的排队队列
    ThreadFactory threadFactory, // 新线程的产生方式
    RejectedExecutionHandler handler) // 拒绝策略

线程池管理器(ThreadPool Manager)
线程池管理器负责线程池的创建、销毁和管理。它包含一些操作线程池的方法,如提交任务、关闭线程池、设置核心线程数等。线程池管理器可以是ExecutorService接口的实现类,例如ThreadPoolExecutor。

线程工厂(Thread Factory)
线程工厂是用于创建新线程的工厂类,它定义了如何创建线程的方式。线程池在需要创建新线程时会通过线程工厂来创建,并将其添加到线程池中。

有两种方式可以来创建新线程
1.ThreadFactory threadFactory = Executors.defaultThreadFactory();
这个是用来来获取默认的线程工厂实例
2.自定义线程工厂来创建
Executors.defaultThreadFactory()方法返回的是一个默认的线程工厂实例,它创建的线程对象具有默认的配置。如果你需要自定义线程的一些属性,例如线程名称、优先级等,可以使用ThreadFactory接口的其他实现类来创建线程。

以下是一个创建自定义线程工厂的示例代码:

import java.util.concurrent.*;

public class CustomThreadFactoryExample {

    public static void main(String[] args) {
        int corePoolSize = 5;
        int maximumPoolSize = 10;
        long keepAliveTime = 1;
        TimeUnit unit = TimeUnit.SECONDS;
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
        ThreadFactory threadFactory = new CustomThreadFactory();

        ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize,
                keepAliveTime, unit, workQueue, threadFactory);

        // 提交任务给线程池...
    }

    static class CustomThreadFactory implements ThreadFactory {
        private final AtomicInteger counter = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("MyThread-" + counter.getAndIncrement());
            thread.setPriority(Thread.NORM_PRIORITY);
            // 设置其他线程属性...

            return thread;
        }
    }
}

在上述代码中,创建了一个名为CustomThreadFactory的自定义线程工厂类,实现了ThreadFactory接口,并覆盖了其中的newThread()方法。在newThread()方法中,可以对新创建的线程对象进行自定义设置,例如设置线程名称、优先级等属性。

通过在ThreadPoolExecutor的构造方法中传入自定义的线程工厂实例,可以让线程池使用该工厂来创建线程对象。

拒绝策略(Rejected Execution Policy)
当线程池已经达到最大线程数且任务队列已满时,新提交的任务会触发拒绝策略。拒绝策略定义了如何处理无法执行的任务,常见的策略有抛出异常、丢弃任务、丢弃最早的任务等。

6.线程池的任务调度流程请添加图片描述

(1)当前工作线程数小于核心线程池数量,执行器总是优先创建一个任务线程,而不是从线程队列中取一个空闲线程。
(2)线程池中任务数大于核心线程池数,任务将被加入到阻塞队列中,一直到阻塞队列满。在核心线程池数量已经用完、阻塞队列没有满的场景下,线程池不是为新任务创建一个新线程。
(3)当完成一个任务的执行时,执行器总是优先从阻塞队列中取下一个任务,并开始其执行,一直到阻塞队列为空,其中所有的缓存任务被取光。
(4)在核心线程池数量已经用完、阻塞队列也已经满了的场景下,如果线程池接收到新的任务,将会为新任务创建一个线程(非核心线程),并且立即开始执行新任务。
(5)在核心线程都用完、阻塞队列已满的情况下,一直会创建新线程去执行新任务,直到池内的线程总数超出 maximumPoolSize。如果线程池的线程总数超时 maximumPoolSize,则线程池会拒绝接收任务,当新任务过来时,会为新任务执行拒绝策略。

在创建线程池时,如果线程池的参数如核心线程数量、最大线程数量、 BlockingQueue 等配置不合理,就会出现任务不能被正常调度的问题。
下面是一个错误的线程池配置示例:

    @org.junit.Test
    public void testThreadPoolExecutor() {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, //corePoolSize
                100, //maximumPoolSize
                100, //keepAliveTime
                TimeUnit.SECONDS, //unit
                new LinkedBlockingDeque<>(100));//workQueue

        for (int i = 0; i < 5; i++) {
            final int taskIndex = i;
            executor.execute(() ->
            {
                Print.tco("taskIndex = " + taskIndex);
                try {
                    Thread.sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        //每隔 1 秒,输出线程池的工作任务数量、总计的任务数量
        while (true) {
            Print.tco("- activeCount:" + executor.getActiveCount() +
                    " - taskCount:" + executor.getTaskCount());
            sleepSeconds(1);
        }
    }

请添加图片描述
运行程序,结果如下:

12:01:49.667 [pool-1-thread-1] INFO com.lxs.demo.a_thread.ThreadPoolDemo - taskIndex = 0
12:01:49.667 [pool-1-thread-2] INFO com.lxs.demo.a_thread.ThreadPoolDemo - taskIndex = 1
12:01:50.671 [main] INFO com.lxs.demo.a_thread.ThreadPoolDemo - - activeCount:2 - taskCount:5
12:01:51.671 [main] INFO com.lxs.demo.a_thread.ThreadPoolDemo - - activeCount:2 - taskCount:5

虽然设置了maximumPoolSize为100的线程池,提交了2个任务只会执行2个

7. 线程池的拒绝策略

使用有界队列的时候,如果队列满了,提交任务到线程池的时候就会被拒绝。总体来说,任务被拒绝有两种情况:

(1)线程池已经被关闭。

(2)工作队列已满且 maximumPoolSize 已满。无论以上哪种情况任务被拒,线程都会调用RejectedExecutionHandler 实 例 的rejectedExecution 方法。RejectedExecutionHandler 是拒绝策略的接口, JUC 为该接口提供了以下几种实现:

  • AbortPolicy:拒绝策略
  • DiscardPolicy:抛弃策略
  • DiscardOldestPolicy:抛弃最老任务策略
  • CallerRunsPolicy:调用者执行策略
  • 自定义策略

JUC 线程池拒绝策略的接口与类之间的关系图请添加图片描述
(1) AbortPolicy
使用该策略时,如果线程池队列满了则新任务被拒绝,并且会抛出 RejectedExecutionException异常。该策略是线程池的默认的拒绝策略。
(2) DiscardPolicy
该策略是 AbortPolicy 的 Silent(安静)版本,如果线程池队列满了,新任务会直接被丢掉,并且不会有任何异常抛出。
(3) DiscardOldestPolicy
抛弃最老任务策略,也就是说如果队列满了,会将最早进入队列的任务抛弃,从队列中腾出空间,再尝试加入队列。因为队列是队尾进队头出,队头元素是最老的,所以每次都是移除对头元素后再尝试入队。
(4) CallerRunsPolicy
调用者执行策略。在新任务被添加到线程池时,如果添加失败,那么提交任务线程会自己去执行该任务,不会使用线程池中的线程去执行新任务。
在以上的四种内置策略中,线程池默认的拒绝策略为 AbortPolicy,如果提交的任务被拒绝,线程池抛出 RejectedExecutionException 异常,该异常是非受检异常(运行时异常),很容易忘记捕获。如果关心任务被拒绝的事件,需要在提交任务时捕获 RejectedExecutionException 异常。
(5)自定义策略
如果以上拒绝策略都不符合需求,则可自定义一个拒绝策略,实现 RejectedExecutionHandler接口的 rejectedExecution 方法即可。

自定义拒绝策略的例子,代码如下:

    //自定义拒绝策略
    public static class CustomIgnorePolicy implements RejectedExecutionHandler {
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            // 可做日志记录等
            Print.tco(r + " rejected; " + " - getTaskCount: " + e.getTaskCount());
        }
    }

    @org.junit.Test
    public void testCustomIgnorePolicy() {
        int corePoolSize = 2; //核心线程数
        int maximumPoolSize = 4;  //最大线程数
        long keepAliveTime = 10;
        TimeUnit unit = TimeUnit.SECONDS;
        //最大排队任务数
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
        //线程工厂
        ThreadFactory threadFactory = new SimpleThreadFactory();
        //拒绝和异常策略
        RejectedExecutionHandler policy = new CustomIgnorePolicy();
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime, unit,
                workQueue,
                threadFactory,
                policy);

        // 预启动所有核心线程
        pool.prestartAllCoreThreads();
        for (int i = 1; i <= 10; i++) {
            pool.execute(new TargetTask());
        }
        //等待10秒
        sleepSeconds(10);
        Print.tco("关闭线程池");
        pool.shutdown();
    }

运行以上代码,大致结果如下:
请添加图片描述

8.线程池状态

线程池的 5 种状态,具体如下:
(1) RUNNING:线程池创建之后的初始状态,这种状态下可以执行任务。
(2)SHUTDOWN:该状态下线程池不再接受新任务,但是会将工作队列中的任务执行完毕。
(3) STOP:该状态下线程池不再接受新任务,也不会处理工作队列中的剩余任务,并且将会中断所有工作线程。
(4) TIDYING:该状态下所有任务都已终止或者处理完成,将会执行terminated( )钩子方法。
(5) TERMINATED:执行完terminated( )钩子方法之后的状态。

请添加图片描述
线程池的状态转换规则为:
(1)线程池创建之后状态为 RUNNING。
(2)执行线程池的 shutdown 实例方法,会使线程池状态从 RUNNING 转变为 SHUTDOWN。
(3)执行线程池的 shutdownNow 实例方法,会使线程池状态从 RUNNING 转变为 STOP。
(4)当线程池处于 SHUTDOWN 状态,执行器 shutdownNow 方法,会将其状态转变为 STOP状态。
(5)等待线程池的所有工作线程停止,工作队列清空之后,线程池状态会从 STOP 转变为TIDYING。
(6)执行完 terminated( ) 钩子方法之后,线程池状态从 TIDYING 转变为 TERMINATED 。

9.Junit单元测试不能支持测试多线程

节选自
https://blog.csdn.net/w605283073/article/details/92016433

请添加图片描述

即test方法运行在主线程中,外层函数执行完test等操作后执行System.exit来退出虚拟机,这个时候thread1和thread2可能还没执行完,就被销毁了。

也就是使用测试时候,可能线程中的任务还没执行完就因为退出虚拟机而被销毁了!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值