高效开发:线程池的使用和基本原理剖析

Posted Java架构师(公众号:毛奇志)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高效开发:线程池的使用和基本原理剖析相关的知识,希望对你有一定的参考价值。

一、前言

项目开发中,需要用到多线程的,我们一般都是直接使用线程池,而不是自己临时新建一个线程,原因在于每次创建和销毁线程,对于CPU和内存的开销比较大,影响后端的稳定性和高效性。

多个线程单打独斗,要使用多线程就要创建多一个线程,这是必须的,当多线程处理完毕后,不销毁线程则占用内存资源,销毁线程后面要使用到多线程又要新建,无法复用之前的线程。

使用线程池,根据项目实际,在线程池里面新建好指定个数的线程,需要的使用就拿来用,不需要的时候就释放,既实现了多线程处理的目的,又可以让团队代码规范化,最重要的是线程复用可以实现减少创建和销毁线程的开销。

线程销毁三个方式:
interrupt() stop() run()方法中的逻辑执行完毕

二、线程池的使用与四种线程池的区别

2.1 线程池的使用

线程池的使用就是实现多线程并发的功能,直接上代码:

package com.example.demo;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 四个线程执行十个任务,所以一定有线程执行多个任务
// 但是每个线程要每次只能执行一个任务,要执行完成一个任务之后再执行下一个
public class Main {
    public static void main(String[] args) {
        // 固定数量线程池
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Task());
        }
        executorService.shutdown();
    }

    static class Task implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " - 开始执行任务");
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " - 执行完成");
        }
    }
}

在这里插入图片描述

2.2 四种线程池的区别

Executors.newFixedThreadPool() // 固定线程数量 的线程池
Executors.newSingleThreadExecutor() // 只有一个线程的 的线程池
Executors.newCachedThreadPool() // 可以缓存的线程池,理论上来说,有多少请求,该线程池就可以创建多少个线程来处理
Executors.newScheduledThreadPool() // 提供了按照周期执行的线程池 Timer

   public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

五个参数的意思:初始线程数、最大线程数(扩容)、 初始线程数和最大线程数数量相同表示不扩容,线程存活时间,存活单位、阻塞队列

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

就是固定数量线程池的 初始线程数和最大线程数都是1、阻塞队列

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

初始线程数为0、最大线程数(扩容)为Integer最大数量,理论上来说,有多少请求,该线程池就可以创建多少个线程来处理

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
    return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ThreadPoolExecutor(int corePoolSize,  // 初始线程数(核心线程数)
                          int maximumPoolSize,  // 最大数量  Integer最大值
                          long keepAliveTime,  // 0
                          TimeUnit unit,  // 单位
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,  核心线程数
                          int maximumPoolSize,   最大线程数
                          long keepAliveTime,   存活时间
                          TimeUnit unit,   存活单位
                          BlockingQueue<Runnable> workQueue, 阻塞队列
                          ThreadFactory threadFactory,   线程工厂,用来创建工作线程的,默认实现,可以不传递
                          RejectedExecutionHandler handler) {  拒绝执行策略  默认实现(报错),可以不传递   

三、线程池的底层原理

线程池要解决的就是线程复用,它是通过内置一个阻塞队列来实现线程复用的,

搞清楚阻塞队列和线程池:阻塞队列本质是一个任务队列,里面存放多个任务;线程池里面存放这个多个线程,扩容的阻塞队列,拒绝的也是阻塞队列。

线程池中,对于run方法执行完成的线程,不是销毁,而是阻塞,并放入到阻塞队列中,下次要使用的使用直接唤醒,从而实现线程复用。

线程池模型可以理解为一个简单的 生产者-消费者 模型,但是消费者不能阻塞生产者,如果生产数量太多,消费者两种方式:扩容、拒绝处理,唯独不能阻塞生产者。

拒绝策略:
直接报错(默认方式)、
丢弃刚来的任务(去尾)、
直接线程调用task.run()方法、
队列中头部等待最久的任务丢弃,然后把当前任务添加到阻塞队列
储存起来,等待孔宪后重试(自定义)

让线程实现复用的唯一方法就是线程不结束。
如何让线程执行新任务?通过共享内存(list.add)实现线程通信
线程一直处于运行状态?有任务来的时候,执行;没有任务的时候,阻塞。

扩容是因为当前任务数过多,增加新增,如果峰值过去了,要销毁这样扩容的线程,即通过设置新增扩容的线程的生命周期来实现。

四、尾声

线程池的使用和基本原理剖析,完成了。

天天打码,天天进步!!

以上是关于高效开发:线程池的使用和基本原理剖析的主要内容,如果未能解决你的问题,请参考以下文章

深入源码分析Java线程池的实现原理

深入源码分析Java线程池的实现原理

线程池核心原理剖析

线程池的探索(上)

Java线程池原理

深入源码,深度解析Java 线程池的实现原理