JAVA高并发编程实战

JAVA 高并发编程之线程池全生命周期管理

第一章 JAVA 高并发编程之线程池全生命周期管理
第二章 JAVA 高并发编程之IO网络模型
第三章 JAVA 高并发编程之设计模式


文章目录

  • JAVA 高并发编程之线程池全生命周期管理
  • 前言
  • 一、线程的核心原理
    • 1.什么是进程?
    • 2.什么是线程?
    • 3.线程与进程的区别
    • 4.线程的生命周期
    • 5.创建一个线程
  • 二、线程池核心原理
    • 1.为什么使用线程池?
    • 2.快捷创建线程池
    • 3.线程池工作原理
      • (1)线程池核心参数
      • (2)线程池任务调度流程
    • 4.规范创建线程池
      • (1)线程池任务类型
      • (2)设计IO密集型线程池
      • (3)设计CPU密集型线程池
      • (4)设计混合型线程池
      • (5)构建线程池
    • 5.线程池提交任务
    • 6.优雅关闭线程池
      • (1)线程池生命周期
      • (2)线程池关闭方法
      • (3)线程池关闭策略
  • 三、简单应用线程池
    • 1.应用背景
    • 2.方案介绍
    • 3.代码实现
      • (1)线程池构建类 ThreadUtil
      • (2)阻塞队列类
      • (3)生产者处理类
      • (4)实现Runnable接口的参数类
      • (5)消费者处理类
    • 4.效果测试
  • 总结
  • 参考资料


前言

Java高并发是指在Java程序中同时处理大量并发请求或操作的能力。而多线程是Java 程序运行的基础性机制,对于高性能、高并发的应用场景,使用多线程技术尤为重要,本文将针对Java多线程的核心原理和使用方法进行简单介绍。


一、线程的核心原理

1.什么是进程?

进程是程序(指存储在硬盘中的可执行文件)的一次启动执行。

Java编写的程序都运行在Java 虚拟机(JVM)中,每当使用Java命令启动一个Java应用程序时,就会启动一个JVM进程。JVM找到程序的入口点main()方法,然后运行main()方法,这样就产生了一个线程,这个线程称为主线程。当main()方法结束后,主线程运行完成,JVM进程也随即退出。

例如: nacos应用运行在服务器中,只存在一个进程号PID。
这里插入图片描述

2.什么是线程?

线程是指“进程代码段”的一次顺序执行,是CPU调度的最小单位。

一个标准的线程通常由线程描述信息、当前指令指针(程序计数器)、栈内存组成。
在这里插入图片描述

  1. 线程ID: 线程的唯一标识。
  2. 线程名称:线程的名字,主要方便用户识别。
  3. 线程优先级:表示线程被调度时的优先级。

3.线程与进程的区别

  1. 线程是“进程代码段”的一次顺序执行流程。一个进程由一个或多个线程组成, 进程至少有一个线程。
  2. 线程是CPU调度的最小单位,进程是操作系统分配资源的最小单位。线程的划分尺度小于进程,使得多线程程序的并发性高。
  3. 线程是出于高并发的调度诉求从进程内部演进而来的。线程的出现既充分发挥了CPU 的计算性能,又弥补了进程调度过于笨重的问题。
  4. 进程之间是相互独立的,但进程内部的各个线程之间并不完全独立。各个线程之间共 享进程的方法区内存、堆内存、系统资源(文件句柄、系统信号等)
  5. 切换速度不同:线程上下文切换比进程上下文切换要快得多。

4.线程的生命周期

Java中线程的生命周期存在6种状态:

  • NEW : 新建状态
  • RUNNABLE :可执行状态(就绪、运行)
  • BLOCKED:阻塞状态
  • WAITING:等待状态
  • TIMED_WAITING:限时等待状态
  • TERMINATED:终止状态

JVM线程常见的状态转化示意图:在这里插入图片描述

  • 就绪状态和运行状态都是操作系统中的线程状态。在Java语言中,并没有细分这两种状态, 而是将这两种状态合并成同种状态RUNNABLE状态。

  • NEW状态的Thread实例调用了start方法后,线程的状态将变成RUNNABLE状态。此时,线程的run方法不一定会马上被并发执行,需要在线程获取了CPU时间之后才真正启动并发执行。

BLOCKED 阻塞状态

处于BLOCKED(阻塞)状态的线程并不会占用CPU资源,以下情况会让线程进入阻塞状态:

  1. 线程等待获取锁
    等待获取一个锁,而该锁被其他线程持有,则该线程进入阻塞状态当其他线释放了该锁, 并且线程调度器允许该线程持有该锁时,该线程退出阻塞状态。
  2. IO阻塞
    线程发起一个阻塞式IO操作(磁盘IO、网络IO等)后,如果不具备IO条件,线程就会进入阻塞状态。

WAITING 无限期等待状态

处于WAITING(无限期等待)状态的线程不会被分配CPU时间片,需要被其他线程显式地唤醒,才会进入就绪状态
线程调用以下3种方法会让自己进入无限等待状态:

  1. **Object.wait()**方法,对应的唤醒方式为:Object.notify()/Object.notifyAll()。
  2. **Thread.join()**方法,对应的唤醒方式为:被合入的线程执行完毕。
  3. **LockSupport.park()**方法,对应的唤醒方式为:LockSupport.unpark(Thread)

TIMED WAITING 限时等待状态

处于TIMED WAITING(限时等待)状态的线程不会被分配CPU时间片,如果指定时间之内没有被唤醒,限时等待的线程会被系统自动唤醒,进入就绪状态
以下3种方法会让线程进入限时等待状态:

  1. .Thread.sleep(time)方法,对应的唤醒方式为:sleep睡眠时间结束。
  2. Object.wait(time)方法,对应的唤醒方式为:调用Object.notify()/Object.notifyAll()主动唤醒,或者限时结束。
  3. LockSupport.parkNanos(time)/parkUntil(time)方法,对应的唤醒方式为:线程调用配套的
    LockSupport.unpark(Thread)方法结束,或者线程停止(park) 时限结束。

线程调度与操作系统

  • 进入到BLOCKED状态、WAITING状态、TIMED_WAITING状态的线程都会让出CPU的使用权。
  • 等待或者阻塞状态的线程被唤醒后,进入就绪状态,需要重新获取时间片才能接着运行。
  • 操作系统调度线程的时是随机调用(抢占式调度模型),会引发线程安全问题。如:多个线程对同一个变量进行修改,针对变量的非原子操作,内存可见性,指令重排序等。

5.创建一个线程

  1. 继承Thread类创建线程类
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello thread");
    }
  1. 实现Runable接口创建线程类

Lambda 表达式

 Thread thread = new Thread(() -> {
        System.out.println("hello thread");
    });

实例化Runnable接口

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("hello");
    }
}

public static void main(){
    Thread thread = new Thread(new MyRunnable());
    thread.start(); 
}
  1. 使用Callable和FutureTask创建线程

支持获取异步执行结果

class MyCallable implements Callable<String> {
    @Override
    public String call() {
        return "hello";
    }
}
public static void main(){
    MyCallable task = new MyCallable();
    FutureTask<String> futureTask = new FutureTask<String>(task);
    Thread thread = new Thread(futureTask, "线程名称");
    thread.start(); 
    ...
    System.out.println(futureTask.get());//获取执行结果,主线程可能阻塞
}
  1. 通过线程池创建线程
private static ExecutorService threadPool = Executors.newFixedThreadPool(10);
//三个异步执行任务提交方式
pool.execute(new Runnable(){...});
pool.submit(new Runnable(){...});
Future future = pool.submit(new Callable(){...});

二、线程池核心原理

1.为什么使用线程池?

(1)降低资源消耗:
线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,通过重复利用已创建的线程可以降低线程创建和销毁造成的消耗。
(2)提高响应速度:
当任务到达时,可以不需要等待线程创建就能立即执行。
(3)提高线程的可管理性:
线程池提供了一种限制、管理资源的策略,维护一些基本的线程统计信息,如已完成任务的数量等。通过线程池可以对线程资源进行统一的分配、监控和调优。

创建销毁线程需要时间以及系统资源开销,使用线程池的好处是减少这些开销,解决资源不足的问题。某些互联网大厂的编程规范中,不允许在应用中自行显式地创建线程,线程必须通过线程池提供,开发者通过对线程池的管理能够更方便地管理线程。

2.快捷创建线程池

JDK提供了Executors用于快捷创建4类线程池
在这里插入图片描述
本文第一节第5点,即通过Executors静态工厂类创建了线程数量为10的固定线程池。

该创建方式虽然方便,但是在实际开发场景中被禁止使用,其存在巨大的内存溢出风险。

FixedThreadPoolSingleThreadPool
工厂方法所创建的线程池,工作队列(任务排队的队列)的长度都为 Integer.MAX_VALUE(2^31 - 1),可能会堆积大量的任务,从而导致OOM。
CachedThreadPoolScheduledThreadPool
工厂方法所创建的线程池允许创建的线程数量Integer.MAX_VALUE,可能会导致创建大量的线程,从而导致OOM。

3.线程池工作原理

(1)线程池核心参数

线程池存在7 个核心参数如下:

1. corePoolSize:核心线程数
2. maximumPoolSize:最大线程数
3. keepAliveTime:空闲线程存活时间
4. TimeUnit:时间单位
5. BlockingQueue:线程池任务队列
6. ThreadFactory:创建线程的工厂
7. RejectedExecutionHandler:拒绝策略

(2)线程池任务调度流程

在这里插入图片描述

线程池的任务调度流程(包含接收新任务和执行下一个任务)大致如下:

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

线程池拒绝策略

ThreadPoolExecutor.AbortPolicy 拒接策略: 丢弃任务并抛出RejectedExecutionException异常。
②ThreadPoolExecutor.DiscardPolicy 抛弃策略:丢弃任务,但是不抛出异常。
③ThreadPoolExecutor.DiscardOldestPolicy 抛弃最老任务策略:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
④ThreadPoolExecutor.CallerRunsPolicy 调用者执行策略 :由调用线程(提交任务的线程)处理该任务。

4.规范创建线程池

(1)线程池任务类型

使用标准构造器ThreadPoolExecutor创建线程池时,会涉及线程数的配置,而线程数的配置与异步任务类型是分不开的。这里将线程池的异步任务大致分为以下三类:

(1) IO密集型任务
此类任务主要是执行IO操作。由于执行IO操作的时间较长,导致CPU的利用率不高,这类任务CPU常处于空闲状态。Netty的IO读写操作为此类任务的典型例子。
(2) CPU密集型任务
此类任务主要是执行计算任务。由于响应时间很快,CPU一直在运行,这种任务CPU的利用率很高。
(3) 混合型任务
此类任务既要执行逻辑计算,又要进行IO操作(如RPC调用、数据库访问)。相对来说,由于执行IO操作的耗时较长(一次网络往返往往在数百毫秒级别),这类任务的CPU利用率也不是太高。Web服务器的HTTP请求处理操作为此类任务的典型例子。

(2)设计IO密集型线程池

  1. IO密集型任务的CPU使用率较低,导致线程空余时间很多,通常需要设置CPU核心数两倍的线程。当IO线程空闲时,可以启用其他线程继续使用CPU,以提高CPU的使用率。
  2. corePoolSize和maximumPoolSize保持一致,使得在接收到新任务时,如果没有空闲工作线程,就优先创建新的线程去执行新任务,而不是优先加入阻塞队列,等待现有工作线程空闲后再执行。
  1. 使用有界队列缓冲任务而不是无界队列,可以根据具体需要进行增大,但是不能使用无界队列。
  2. 使用懒汉式单例模式创建线程池,如果代码没有用到此线程池,就不会立即创建。
  3. 使用JVM关闭时的钩子函数优雅地自动关闭线程池。

Netty的IO处理任务就是典型的IO密集型任务。所以,Netty的Reactor(反应器)实现类(定制版的线程池)的IO处理线程数默认正好为CPU核数的两倍。

(3)设计CPU密集型线程池

CPU密集型任务也叫计算密集型任务,其特点是要进行大量计算而需要消耗CPU资源,比如计算圆周率、对视频进行高清解码等。
CPU密集型任务虽然也可以并行完成,但是并行的任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低。

  1. CPU密集型任务并行执行的数量应当等于CPU的核心数。
  2. corePoolSize和maximumPoolSize保持一致,使得在接收到新任务时,如果没有空闲工作线程,就优先创建新的线程去执行新任务,而不是优先加入阻塞队列,等待现有工作线程空闲后再执行。

(4)设计混合型线程池

混合型任务既要执行逻辑计算,又要进行大量非CPU耗时操作(如RPC调用、数据库访问、网络通信等),混合型任务CPU的利用率不是太高,非CPU耗时往往是CPU耗时的数倍。比如在Web应用中处理HTTP请求时,一次请求处理会包括DB操作、RPC操作、缓存操作等多种耗时操作。

为混合型线程确定线程数可以参考以下工公式:

在这里插入图片描述

(5)构建线程池

使用 ThreadPoolExecutor 标准构建器构建线程池

private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(
        MAXIMUM_POOL_SIZE,
        MAXIMUM_POOL_SIZE,
        KEEP_ALIVE_SECONDS,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue(QUEUE_SIZE),
        new CustomThreadFactory("cpu")
        );
    

5.线程池提交任务

向线程池提交任务的方式如下:

  • executor() 方法只能接收Runnable类型的参数;
  • submit() 方法可以接收Runnable类型Callable类型的参数;在处理Callable类型任务时,可以通过Future对象获取执行结果,同样可以进行异步执行的过程中的异常捕获
  • ThreadPoolExecutor类的实现中,内部核心的任务提交方法是execute()方法,虽然用户程序通过submit()也可以提交任务,但是实际上submit()方法中最终调用的还是execute()方法。

6.优雅关闭线程池

(1)线程池生命周期

线程池的5种状态具体如下:

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

(2)线程池关闭方法

(1) shutdown:是JUC提供的一个有序关闭线程池的方法,此方法会等待当前工作队列中的剩余任务全部执行完成之后,才会执行关闭,但是此方法被调用之后线程池的状态转为SHUTDOWN,线程池不会再接收新的任务。
(2)shutdownNow:是JUC提供的一个立即关闭线程池的方法,此方法会打断正在执行的工作线程,并且会清空当前工作队列中的剩余任务,返回的是尚未执行的任务。
(3)awaitTermination:等待线程池完成关闭。在调用线程池的shutdown()与shutdownNow()方法时,当前线程会立即返回,不会一直等待直到线程池完成关闭。如果需要等到线程池关闭完成,可以调用awaitTermination()方法。

在这里插入图片描述

(3)线程池关闭策略

结合shutdown()、shutdownNow()、awaitTermination()三个方法优雅地关闭一个线程池,大致分为以下几步:

(1)执行shutdown()方法,拒绝新任务的提交,并等待所有任务有序地执行完毕。
(2)执行awaitTermination(long timeout,TimeUnit unit)方法,指定超时时间,判断是否已经关闭所有任务,线程池关闭完成。
(3)如果awaitTermination()方法返回false,或者被中断,就调用shutDownNow()方法立即关闭线程池所有任务。
(4)补充执行awaitTermination(long timeout,TimeUnit unit)方法,判断线程池是否关闭完成。如果超时,就可以进入循环关闭,循环一定的次数(如1000次),不断关闭线程池,直到其关闭或者循环结束。


三、简单应用线程池

1.应用背景

某项目的特定业务场景存在大量的IO操作,经分析该业务场景特征如下:

  1. IO操作密集,需要将大量的系统数据实时写入磁盘文件中,其中的部分数据持久化数据库中。
  2. 数据要求低,该数据为非业务数据,数据时效性、完整性不高。
  3. 数据量波动,数据量随系统交易量波动,具有明显的高峰和低估特征。
  4. 数据隔离性,数据不存在分布式问题,数据的产生和处理均在同一个应用进程。

2.方案介绍

  1. 针对数据时效性、完整性不高、数据量随系统交易量同步波动,综合考虑系统性能,选取多线程异步执行IO操作。
  2. 针对高IO场景特征,选取IO密集型线程池。
  3. 针对数据不存在跨进程问题,可基于阻塞队列设计生产者-消费者模型,在进程内部完成任务生产与消费。
  4. 为尽可能保证数据完整性,应在关闭前线程池,完成已提交任务。

3.代码实现

(1)线程池构建类 ThreadUtil

获取IO密集线程数量

/**
 * CPU核数
 **/
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

/**
 * IO线程池最大线程数
 */
public static final int IO_MAX = Math.max(2, CPU_COUNT * 2);   

创建IO密集型线程池:懒汉式 + 注册JVM关闭钩子函数

//懒汉式单例创建线程池:用于IO密集型任务
private static class IoIntenseTargetThreadPoolLazyHolder {
    //线程池: 用于IO密集型任务
    private static final ThreadPoolExecutor EXECUTOR = new ThreadPoolExecutor(
            IO_MAX,
            IO_MAX,
            KEEP_ALIVE_SECONDS,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue(QUEUE_SIZE),
            new CustomThreadFactory("io"));

    static {
        EXECUTOR.allowCoreThreadTimeOut(true);
        //JVM关闭时的钩子函数
        Runtime.getRuntime().addShutdownHook(
                new ShutdownHookThread("IO密集型任务线程池", new Callable<Void>() {
                    @Override
                    public Void call() throws Exception {
                        //优雅关闭线程池
                        shutdownThreadPoolGracefully(EXECUTOR);
                        return null;
                    }
                }));
    }
}

线程池获取方法:懒汉式

/**
 * 获取执行IO密集型任务的线程池
 *
 * @return
 */
public static ThreadPoolExecutor getIoIntenseTargetThreadPool() {
    return IoIntenseTargetThreadPoolLazyHolder.EXECUTOR;
}

关闭线程池方法

public static void shutdownThreadPoolGracefully(ExecutorService threadPool) {
    if (!(threadPool instanceof ExecutorService) || threadPool.isTerminated()) {
        return;
    }
    try {
        threadPool.shutdown();   //拒绝接受新任务
    } catch (SecurityException e) {
        return;
    } catch (NullPointerException e) {
        return;
    }
    try {
        // 等待 4s,等待线程池中的任务完成执行
        if (!threadPool.awaitTermination(4, TimeUnit.SECONDS)) {
            // 调用 shutdownNow 取消正在执行的任务
            threadPool.shutdownNow();
            // 再次等待 1s,如果还未结束,可以再次尝试,或则直接放弃
            if (!threadPool.awaitTermination(1, TimeUnit.SECONDS)) {
                System.err.println("线程池任务未正常执行结束");
            }
        }
    } catch (InterruptedException ie) {
        // 捕获异常,重新调用 shutdownNow
        threadPool.shutdownNow();
    }
    //任然没有关闭,循环关闭1000次,每次等待10毫秒
    if (!threadPool.isTerminated()) {
        try {
            for (int i = 0; i < 1000; i++) {
                if (threadPool.awaitTermination(10, TimeUnit.MILLISECONDS)) {
                    break;
                }
                threadPool.shutdownNow();
            }
        } catch (InterruptedException e) {
            System.err.println(e.getMessage());
        } catch (Throwable e) {
            System.err.println(e.getMessage());
        }
    }
}

(2)阻塞队列类

非阻塞式添加、 阻塞式删除

@Component
public class AppLogLBQ {

    private LinkedBlockingQueue<SysOptAppJsonLog> linkedBlockingQueueApp = new LinkedBlockingQueue<SysOptAppJsonLog>(AopLogConstants.LBQ_MAX_AMOUNT);

    public Boolean add(SysOptAppJsonLog sysOptAppJsonLog) throws Exception {
        return linkedBlockingQueueApp.offer(sysOptAppJsonLog);
    }

    public SysOptAppJsonLog fetch() throws Exception {
        return linkedBlockingQueueApp.take();
    }
}

(3)生产者处理类

以非阻塞方式将目标数据添加到阻塞队列

      @Autowired
      public AppLogLBQ appLogLBQ;
  
      public void operationAllLogBefore(JoinPoint joinPoint) {
        try {
            SysOptFlowJsonLog flowJsonLog = new SysOptFlowJsonLog();
            appLogLBQ.add(flowJsonLog);       
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

(4)实现Runnable接口的参数类

以阻塞的方式从阻塞队列获取目标数据

public class TaskRunable implements Runnable {

    private BaseLogBiz baseLogHandler = SpringContextUtil.getApplicationContext().getBean(BaseLogBiz.class);

    private AppJsonLogLBQ appLogLBQ = SpringContextUtil.getApplicationContext().getBean(AppLogLBQ.class);

    @Override
    public void run() {
    
       while (true) {
            try {
                SysOptAppJsonLog sysOptAppJsonLog = appLogLBQ.fetch();
                if (sysOptAppJsonLog != null) {
                    String jsonString = JSON.toJSONString(sysOptAppJsonLog);
                    StringBuilder sb = new StringBuilder();
                    sb.append("当前线程名称: " + Thread.currentThread().getName());
                    sb.append("  当前线程ID: " + Thread.currentThread().getId());
                    sb.append("  " + jsonString);
                    File jsonFile = baseLogHandler.getJsonFile(AopLogConstants.TYPE_APP);
                    if (jsonFile == null) return;
                    baseLogHandler.writeToJson(jsonFile, sb.toString());
                }
            }catch (InterruptedException e) {
                if (baseLogHandler.getThreadPoolStatus()) {
                    System.out.println("发生中断,JVM检测threadPoolExecutor已关闭,作业正常退出...");
                    break;
                }else {
                    System.out.println("发生中断,JVM监测threadPoolExecutor正常运行...");
                }

            }  catch (Exception e) {
                e.printStackTrace();
            }   
        }       
    }
}

(5)消费者处理类

提交任务到消费者线程池
注意:Runnable方法阻塞式获取数据会造成线程占用,导致线程无法返回到线程池中等待新的任务,如果阻塞的线程数量等于线程池上限,会造成线程饥饿,要根据实际应用需要合理分配线程池中阻塞任务。

方式一:提交尽可能多的任务,最多完全占用线程池,此时其他任务无法使用该线程池执行。

    /**
     * 写入json日志文件--应用日志
     *
     */
    public synchronized void writeAppJsonLog() {
        for(int i=0 ; i < ThreadUtil.IO_MAX; i++){
            threadPoolExecutor.submit(new TaskRunable());
           } 
    }
    
    /**
     * 写入到日志文件
     *
     * @param file
     * @param jsonString
     */
    public static synchronized void writeToJson(File file, String jsonString) {
        try(BufferedWriter writer =new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true)))) {
            writer.write(jsonString);
            writer.write(System.lineSeparator());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }    

模式二: 使用部分线程阻塞式获取数据,其余线程由线程池调度异步处理任务

    /**
     * 写入json日志文件--应用日志
     *
     */
    public synchronized void writeAppJsonLog() {
        for(int i=0 ; i < ThreadUtil.IO_MAX/4; i++){
            threadPoolExecutor.submit(new TaskRunable());
        } 
     }

    /**
     * 写入到日志文件
     *
     * @param file
     * @param jsonString
     */
    public synchronized void writeToJson(File file, String jsonString) {
        threadPoolExecutor.submit(()->{
            try(BufferedWriter writer =new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true)))) {
                writer.write(jsonString);
                writer.write(System.lineSeparator());
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

4.效果测试

Jmeter 压力测试: 大小为10的线程组,循环请求100次,共计1000次请求

在这里插入图片描述

服务端配置: cpu为20核,消费者IO线程池大小为40

IO线程池输出结果如图:
在这里插入图片描述

附源文件:首行当前线程名称为异步IO线程(消费者),次行 json数据中最后一个属性thread线程名称为业务线程(生产者)

> 当前线程名称: apppool-1-io-4  当前线程ID: 42  
> {"txnDateTime":"2024-11-03 11:52:48.340","traceId":"dcd458d2e3894651bd63258e00df6305","tranId":"75fb42d3d8354ee29f7b400fc61ea4f4","level":"INFO","msg":"/unAuth/futureCompletable/masterMethod结束:/unAuth/futureCompletable/masterMethod成功","thread":"http-nio-8765-exec-1"}
> 当前线程名称: apppool-1-io-2  当前线程ID: 40  
> {"txnDateTime":"2024-11-03 11:52:48.392","traceId":"7e2defdcb27a4bc88c54d91fabdba105","tranId":"4d41caa1de8a441ab4b72a0d69fd8c11","level":"INFO","msg":"/unAuth/futureCompletable/masterMethod结束:/unAuth/futureCompletable/masterMethod成功","thread":"http-nio-8765-exec-2"}
> 当前线程名称: apppool-1-io-19  当前线程ID: 57  
> {"txnDateTime":"2024-11-03 11:52:48.409","traceId":"a303bf9ffcbc4e79acfb77fa035c9ed9","tranId":"f03cbb3790104ffe8ec6f25100f28ad6","level":"INFO","msg":"/unAuth/futureCompletable/masterMethod结束:/unAuth/futureCompletable/masterMethod成功","thread":"http-nio-8765-exec-5"}
> 当前线程名称: apppool-1-io-3  当前线程ID: 41  
> {"txnDateTime":"2024-11-03 11:52:48.392","traceId":"7e2defdcb27a4bc88c54d91fabdba105","tranId":"4d41caa1de8a441ab4b72a0d69fd8c11","level":"INFO","msg":"/unAuth/futureCompletable/masterMethod开始:","thread":"http-nio-8765-exec-2"}
> 当前线程名称: apppool-1-io-7  当前线程ID: 45  
> {"txnDateTime":"2024-11-03 11:52:48.405","traceId":"62202c8ca35b422ab33db42f29c3fb77","tranId":"c3473048728d453ca9caa08593423882","level":"INFO","msg":"/unAuth/futureCompletable/masterMethod结束:/unAuth/futureCompletable/masterMethod成功","thread":"http-nio-8765-exec-3"}
> 当前线程名称: apppool-1-io-19  当前线程ID: 57 
> {"txnDateTime":"2024-11-03 11:52:55.362","traceId":"7fba88850a2e4823811f056b2632e959","tranId":"136b61ad4a4b4b7caa66c7b7ed9fd0e2","level":"INFO","msg":"/unAuth/futureCompletable/masterMethod结束:/unAuth/futureCompletable/masterMethod成功","thread":"http-nio-8765-exec-6"}
> 当前线程名称: apppool-1-io-22  当前线程ID: 60  
> {"txnDateTime":"2024-11-03 11:52:55.041","traceId":"a5787e31c1504925afad9091373adbf7","tranId":"f4136231a96a4924aff3ec6858198ec4","level":"INFO","msg":"/unAuth/futureCompletable/masterMethod结束:/unAuth/futureCompletable/masterMethod成功","thread":"http-nio-8765-exec-10"}
> 当前线程名称: apppool-1-io-18  当前线程ID: 56  
> {"txnDateTime":"2024-11-03 11:52:54.454","traceId":"5fd8499ca54c4a1981055015879b4d47","tranId":"33f609981b4d4d35b322d339177c8215","level":"INFO","msg":"/unAuth/futureCompletable/masterMethod开始:","thread":"http-nio-8765-exec-4"}
> 当前线程名称: apppool-1-io-15  当前线程ID: 53  
> {"txnDateTime":"2024-11-03 11:52:54.454","traceId":"d04f3260b2374e9cb36b5cccd0ddf8ce","tranId":"2e0e518d8b6e42af848adaa0ae20c29e","level":"INFO","msg":"/unAuth/futureCompletable/masterMethod开始:","thread":"http-nio-8765-exec-3"}
> 当前线程名称: apppool-1-io-4  当前线程ID: 42  
> {"txnDateTime":"2024-11-03 11:52:54.454","traceId":"a5787e31c1504925afad9091373adbf7","tranId":"f4136231a96a4924aff3ec6858198ec4","level":"INFO","msg":"/unAuth/futureCompletable/masterMethod开始:","thread":"http-nio-8765-exec-10"}

总结

本文简单介绍了线程、线程池的基本概念、原理和应用,旨在通过开发者对线程池的管理进而实现对应用中部分线程的全生命周期管理。另外,本文介绍了一个简单IO密集型场景下的线程池应用示例,其中引用了部分生产者-消费者模式的高并发异步任务设计模式,希望该设计思想能够在实际业务场景中有更广泛的应用。

参考资料

本文参考资料为 尼恩 编著的《高并发核心编程 卷2 :多线程、锁、JMM、JUC、高并发设计模式》
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值