多线程专栏------多线程的实现方式(三)

目录

    • 1、使用线程池
      • 1.1、什么是线程池
      • 1.2、使用线程池的优点
      • 1.3、线程池的核心工作流程
      • 1.3、线程池的五种状态生命周期
        • 1.3.1、RUNNING
        • 1.3.2、SHUTDOWN
        • 1.3.3、STOP
        • 1.3.4、TIDYING
        • 1.3.5、TERMINATED
      • 1.4、创建线程池的方式
        • 1.4.1、通过 ThreadPoolExecutor 创建
          • 1.4.1.1、线程池的核心参数
          • 1.4.1.2、线程池的参数详解
        • 1.4.2、通过 Executors 工厂方法创建(生产环境不推荐使用)
          • 1.4.2.1、newFixedThreadPool (固定大小的线程池)
          • 1.4.2.2、newCachedThreadPool (可缓存的线程池)
          • 1.4.2.3、newSingleThreadExecutor(单个线程数的线程池)
          • 1.4.2.4、newScheduledThreadPool(延迟任务的线程池)
    • 2、总结
      • 2.1、FixedThreadPool和 SingleThreadPool :
      • 2.2、CachedThreadPool :


1、使用线程池

1.1、什么是线程池

简而言之,线程池就是管理线程的一个容器,有任务需要处理时,会相继判断核心线程数是否还有空闲、线程池中的任务队列是否已满、是否超过线程池大小,然后调用或创建线程或者排队,线程执行完任务后并不会立即被销毁,而是仍然在线程池中等待下一个任务,如果超过存活时间还没有新的任务就会被销毁,通过这样复用线程从而降低开销。

1.2、使用线程池的优点

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
  • 提供更多更强大的功能。线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。

说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

1.3、线程池的核心工作流程

多线程专栏------多线程的实现方式(三)_第1张图片

1.3、线程池的五种状态生命周期

1.3.1、RUNNING

状态说明 :线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
状态切换:线程池的初始化状态是RUNNING。换句话说,线程池被一旦被创建(比如调用Executors.newFixedThreadPool()或者使用ThreadPoolExecutor进行创建),就处于RUNNING状态,并且线程池中的任务数为0!

多线程专栏------多线程的实现方式(三)_第2张图片

1.3.2、SHUTDOWN

状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。

1.3.3、STOP

状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) ->STOP。

1.3.4、TIDYING

状态说明:当所有的任务已终止,记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池 变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING,当线程池在STOP状态下,线程池中执行的任务为空时,会由STOP -> TIDYING。

多线程专栏------多线程的实现方式(三)_第3张图片

1.3.5、TERMINATED

状态说明:线程池彻底终止,就变成TERMINATED状态。
状态切换:线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED。

多线程专栏------多线程的实现方式(三)_第4张图片

1.4、创建线程池的方式

1.4.1、通过 ThreadPoolExecutor 创建

1.4.1.1、线程池的核心参数
                              //核心线程数
    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;
    }
1.4.1.2、线程池的参数详解

corePoolSize :核心线程数,一直保持的线程的数量,即使线程空闲也不会释放。除非设置了 allowCoreThreadTimeout 为 true;

设置规则:

  • CPU密集型:(CPU密集型也叫计算密集型,指的是运算较多,cpu占用高,读/写I/O(硬盘/内存)较少):corePoolSize = CPU核数 + 1

  • IO密集型:(与CPU密集型相反,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。):corePoolSize = CPU核数 * 2

maximumPoolSize:最大线程数,队列满时开启新线程直到等于该值;

设置规则:

  • 默认为Integer.MAX_VALUE,一般设置为和核心线程数一样

keepAliveTime:线程空闲时间,即线程池没任务的话,线程存活多久就销毁

设置规则:

  • 默认为60s,一般设置为默认60s

unit:线程空闲时间单位

设置规则:

  • 默认为秒

workQueue:缓存任务的队列。实现 BlockingQueue。LinkedBlockingQueue:用链表实现的队列,可以是有界的,也可以是无界的,但在Executors中默认使用无界的。
ArrayBlockingQueue
LinkedBlockingQueue
DelayQueue
PriorityBlockingQueue
SynchronousQueue

设置规则:

  • ArrayBlockingQueue

    • ArrayBlockingQueue(有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量。
  • LinkedBlockingQueue

    • LinkedBlockingQueue(可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列。
  • DelayQueue

    • DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
  • PriorityBlockingQueue

    • PriorityBlockingQueue(优先级队列)是具有优先级的无界阻塞队列;
  • SynchronousQueue

    • SynchronousQueue(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。

threadFactory :线程工厂,一般我们用来给线程命名或者做一些定制时使用

设置规则:

  • 创建工厂的两种方式:
  • Executors.defaultThreadFactory() 、new ThreadFactoryBuilder().setNameFormat(“task-service-pool-%d”).build()

handler :拒绝策略,即当线程池满了,新来的任务怎么处理,ThreadPooLExecutor自带四种

设置规则:

  • 默认是AbortPolicy,会抛出异常。
  • 当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务。
  • 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。
    • ThreadPoolExecutor.AbortPolicy() 丢弃任务,抛运行时异常。
    • ThreadPoolExecutor.CallerRunsPolicy 交给主线程去执行,即提交线程任务的线程。
    • ThreadPoolExecutor.DiscardPolicy 丢弃(不做任何处理)。
    • ThreadPoolExecutor.DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务。

1.4.2、通过 Executors 工厂方法创建(生产环境不推荐使用)

序号 类型 说明
1 newFixedThreadPool 创建⼀个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待
2 newCachedThreadPool 创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程
3 newSingleThreadExecutor 创建单个线程数的线程池,它可以保证先进先出的执⾏顺序
4 newScheduledThreadPool 创建⼀个可以执行延迟任务的线程池
5 newSingleThreadScheduledExecutor 创建⼀个单线程的可以执行延迟任务的线程池
6 newWorkStealingPool 创建⼀个抢占式执行的线程池(任务执⾏顺序不确定)【JDK1.8 添加】
1.4.2.1、newFixedThreadPool (固定大小的线程池)
  • fixedThreadPool.execute
    多线程专栏------多线程的实现方式(三)_第5张图片
    public static void main1(String[] args) {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        try {
            for (int i = 0; i < 5; i++) {
                fixedThreadPool.execute(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(" fixedThreadPool.execute---" + Thread.currentThread().getName());
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //线程池用完,程序结束,关闭线程池(为确保关闭,将关闭方法放入到finally中)
            fixedThreadPool.shutdown();
        }
    }
//运行结果
 fixedThreadPool.execute线程池---pool-1-thread-2
 fixedThreadPool.execute线程池---pool-1-thread-1
 fixedThreadPool.execute线程池---pool-1-thread-3
 fixedThreadPool.execute线程池---pool-1-thread-3
 fixedThreadPool.execute线程池---pool-1-thread-1
  • fixedThreadPool.submit
    在这里插入图片描述
   public static void main(String[] args)  {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        try {
            for (int i = 0; i < 5; i++) {
                Future<Integer> future = fixedThreadPool.submit(new Callable<Integer>() {
                    @Override
                    public Integer call() throws Exception {
                        int num = new Random().nextInt(9);
                        System.out.println("随机数-----" + num +"---fixedThreadPool.submit---"+Thread.currentThread().getName());
                        return num;
                    }
                });
                System.out.println("结果---" +future.get());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //线程池用完,程序结束,关闭线程池(为确保关闭,将关闭方法放入到finally中)
            fixedThreadPool.shutdown();
        }
    }
//运行结果
随机数-----5---fixedThreadPool.submit---pool-1-thread-1
结果---5
随机数-----1---fixedThreadPool.submit---pool-1-thread-2
结果---1
随机数-----5---fixedThreadPool.submit---pool-1-thread-3
结果---5
随机数-----4---fixedThreadPool.submit---pool-1-thread-1
结果---4
随机数-----5---fixedThreadPool.submit---pool-1-thread-2
结果---5

execute和submit的区别

1.execute和submit都属于线程池的方法,execute只能提交Runnable类型的任务
2.submit既能提交Runnable类型任务也能提交Callable类型任务。
3.execute()没有返回值
4.submit有返回值,所以需要返回值的时候必须使用submit

特点&注意点
多线程专栏------多线程的实现方式(三)_第6张图片

  • 核心线程数和最大线程数大小一样
  • 没有所谓的非空闲时间,即keepAliveTime为0
  • 阻塞队列为无界队列LinkedBlockingQueue
1.4.2.2、newCachedThreadPool (可缓存的线程池)
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println(" newCachedThreadPool.execute---" + Thread.currentThread().getName());
            });
        }
    }
//运行结果
 newCachedThreadPool.execute---pool-1-thread-1
 newCachedThreadPool.execute---pool-1-thread-5
 newCachedThreadPool.execute---pool-1-thread-4
 newCachedThreadPool.execute---pool-1-thread-2
 newCachedThreadPool.execute---pool-1-thread-3

特点&注意点

    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, 
                Integer.MAX_VALUE,
                60L, 
                TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>(),
                threadFactory);
    }
  • 核心线程数为0
  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是SynchronousQueue
  • 非核心线程空闲存活时间为60秒

当提交任务的速度大于处理任务的速度时,每次提交一个任务,就必然会创建一个线程。极端情况下会创建过多的线程,耗尽 CPU 和内存资源。由于空闲 60 秒的线程会被终止,长时间保持空闲的 CachedThreadPool 不会占用任何资源。

1.4.2.3、newSingleThreadExecutor(单个线程数的线程池)
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            executorService.execute(()-> {
                System.out.println(" newCachedThreadPool.execute---" + Thread.currentThread().getName());
            });
        }
    }
//运行结果
 newCachedThreadPool.execute---pool-1-thread-1
 newCachedThreadPool.execute---pool-1-thread-1
 newCachedThreadPool.execute---pool-1-thread-1
 newCachedThreadPool.execute---pool-1-thread-1
 newCachedThreadPool.execute---pool-1-thread-1

特点&注意点

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
                (new ThreadPoolExecutor(
                        1, 
                        1,
                        0L, 
                        TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<Runnable>(),
                        threadFactory));
    }
  • 核心线程数为1
  • 最大线程数也为1
  • 阻塞队列是LinkedBlockingQueue
  • keepAliveTime为0
1.4.2.4、newScheduledThreadPool(延迟任务的线程池)
   public static void main(String[] args) {
        /**
         * 创建一个给定初始延迟的间隔性的任务,之后的下次执行时间是上一次任务从执行到结束所需要的时间+给定的间隔时间
         */
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
        scheduledThreadPool.scheduleWithFixedDelay(()->{
            System.out.println("current Time" + System.currentTimeMillis());
            System.out.println(" scheduledThreadPool.scheduleWithFixedDe lay---" + Thread.currentThread().getName());
        }, 1, 3, TimeUnit.SECONDS);

    }
//运行结果
current Time1693668943517
 scheduledThreadPool.scheduleWithFixedDelay---pool-1-thread-1
current Time1693668946523
 scheduledThreadPool.scheduleWithFixedDelay---pool-1-thread-1
current Time1693668949534
 scheduledThreadPool.scheduleWithFixedDelay---pool-1-thread-1
current Time1693668952536
 scheduledThreadPool.scheduleWithFixedDelay---pool-1-thread-1

特点&注意点

    public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
        super(corePoolSize, 
                Integer.MAX_VALUE,
                0, 
                NANOSECONDS,
                new DelayedWorkQueue(),
                threadFactory);
    }
  • 最大线程数为Integer.MAX_VALUE
  • 阻塞队列是DelayedWorkQueue
  • keepAliveTime为0
  • scheduleAtFixedRate() :按某种速率周期执行
  • scheduleWithFixedDelay():在某个延迟后执行

2、总结

2.1、FixedThreadPool和 SingleThreadPool :

  • 允许的请求队列长度为Integer.MAX VALUE,可能会堆积大量的请求,从而导致OOM异常

2.2、CachedThreadPool :

  • 允许的创建线程数量为Integer.MAX VALUE,可能会创建大量的线程,从而导致OOM异常

你可能感兴趣的:(多线程,Java基础,java,spring,boot,spring)