java 线程池 并发并行 线程的生命周期

线程池

什么是线程池技术? 其实,线程池就是一个可以复用线程的技术。要理解什么是线程复用技术,我们先得看一下不使用线程池会有什么问题,理解了这些问题之后,我们在解释线程复用同学们就好理解了。

假设:用户每次发起一个请求给后台,后台就创建一个新的线程来处理,下次新的任务过来肯定也会创建新的线程,如果用户量非常大,创建的线程也讲越来越多。然而,创建线程是开销很大的,并且请求过多时,会严重影响系统性能。
而使用线程池,就可以解决上面的问题。线程池内部会有一个容器,存储几个核心线程,假设有3个核心线程,这3个核心线程可以处理3个任务。
但是任务总有被执行完的时候,假设第1个线程的任务执行完了,那么第1个线程就空闲下来了,有新的任务时,空闲下来的第1个线程可以去执行其他任务。依此内推,这3个线程可以不断的复用,也可以执行很多个任务。
所以,线程池就是一个线程复用技术,它可以提高线程的利用率。

原理

线程池中线程复用原理

线程池的线程复用原理是指,将线程放入线程池中重复利用,而不是每执行一个任务就创建一个新线程。线程池会对线程进行封装,核心原理在于将线程的创建和管理与任务的执行分离。

线程池通过工作队列(WorkQueue)来存储待执行的任务,队列中可能有多个任务等待被执行。线程池中的线程数量是有限的,核心线程数通常是固定的,最大线程数可以设置,超过最大线程数后,任务会被拒绝。

当提交任务时,线程池首先会检查当前线程数是否小于核心线程数,如果是,则新建一个线程来执行任务;如果当前线程数已经达到核心线程数,但队列中没有正在执行的任务,则将任务放入队列中等待执行;如果队列已满,且线程池中的线程数量未达到最大线程数,则新建线程来执行任务;如果队列已满,且线程池中的线程数量达到最大线程数,则根据拒绝策略来处理无法执行的任务。

线程复用的关键是将任务的提交和线程的创建、管理、执行分离,通过线程池来统一管理和调度,减少了创建和销毁线程的开销,提高了系统的效率。同时,由于线程池的复用特性,可以有效控制并发度,避免大量线程的创建和销毁导致的系统负载过大。



线程池的底层工作原理

线程池是一种用于管理和重用线程的机制,其底层工作原理涉及线程的创建、调度、执行以及回收等关键过程。线程池的底层工作原理可以分为以下几个关键步骤:

    1. 线程池的创建: 在使用线程池之前,需要首先创建一个线程池。通常,线程池会根据配置参数(如核心线程数、最大线程数、队列类型等)来初始化线程池的基本属性。
    1. 任务提交: 当有任务需要执行时,将任务提交给线程池。任务可以是Runnable或Callable对象,表示需要在一个独立线程中执行的工作单元。
    1. 线程分配: 线程池内部维护了一组工作线程,这些线程会被动态分配来执行任务。线程池首先会尝试将任务分配给核心线程,如果核心线程数没有达到上限,就创建一个新的核心线程来执行任务。如果核心线程已满,任务会被放入任务队列中等待执行。
    1. 任务执行: 分配给线程的任务会被执行。每个工作线程会不断地从任务队列中获取任务并执行它们。一旦任务执行完成,线程可以选择等待新任务或被回收,具体取决于线程池的配置和实现方式。
    1. 线程回收: 线程池内的线程可能会被回收,这可以是根据一些策略,如闲置时间超过一定阈值或线程数超过最大线程数等。回收的线程会释放资源,如内存和CPU,以便在需要时重新使用。
    1. 任务完成和结果返回: 任务执行完成后,可以将执行结果返回给调用者。如果任务是通过Callable提交的,线程池会返回Future对象,通过该对象可以获取任务的执行结果。
    1. 异常处理: 线程池通常会处理任务执行过程中抛出的异常,可以将异常信息记录下来或采取适当的措施,以确保线程池的稳定性。

总的来说,线程池的底层工作原理是通过管理一组工作线程、任务队列和任务分配策略来实现任务的调度和执行。这种机制可以提高线程的重用性、提高程序性能,并有效地控制线程的生命周期,使得线程池成为多线程编程中的重要工具。



创建线程池及其核心参数 ExecutorService ThreadPoolExecutor

在JDK5版本中提供了代表线程池的接口ExecutorService,而这个接口下有一个实现类叫ThreadPoolExecutor类,使用ThreadPoolExecutor类就可以用来创建线程池对象。下面是它的构造器,参数比较多

请添加图片描述

用这7个参数的构造器来创建线程池的对象。代码如下

ExecutorService pool = new ThreadPoolExecutor(
    3,	//核心线程数有3个
    5,  //最大线程数有5个。   临时线程数=最大线程数-核心线程数=5-3=2
    8,	//临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
    TimeUnit.SECONDS,//时间单位(秒)
    new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在任务队列中等待
    Executors.defaultThreadFactory(), //用于创建线程的工厂对象
    new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
);

什么时候创建临时线程,什么时候开始拒绝?

关于线程池,我们需要注意下面的两个问题
临时线程什么时候创建?

注意!新任务提交时,发现核心线程都在忙、并且任务队列满了、并且还可以创建临时线程,此时会创建临时线程。
注意是任务队列满了之后才会创建临时线程 而不是临时线程满了才加入任务队列

什么时候开始拒绝新的任务?

核心线程和临时线程都在忙、任务队列也满了、新任务过来时才会开始拒绝任务。


线程池执行Runnable任务

创建好线程池之后,接下来我们就可以使用线程池执行任务了。线程池执行的任务可以有两种,一种是Runnable任务;一种是callable任务。下面的execute方法可以用来执行Runnable任务。

请添加图片描述

请添加图片描述

先准备一个线程任务类

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        // 任务是干啥的?
        System.out.println(Thread.currentThread().getName() + " ==> 输出666~~");
        //为了模拟线程一直在执行,这里睡久一点
        try {
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

下面是执行Runnable任务的代码,注意阅读注释,对照着前面的7个参数理解。

ExecutorService pool = new ThreadPoolExecutor(
    3,	//核心线程数有3个
    5,  //最大线程数有5个。   临时线程数=最大线程数-核心线程数=5-3=2
    8,	//临时线程存活的时间8秒。 意思是临时线程8秒没有任务执行,就会被销毁掉。
    TimeUnit.SECONDS,//时间单位(秒)这是一个枚举类型 可以选择想要的单位
    new ArrayBlockingQueue<>(4), //任务阻塞队列,没有来得及执行的任务在,任务队列中等待 基于数组实现 可以控制等待队列大小
    //这里阻塞队列还可以是new LinkedBlockingQueue<>() 基于链表实现不限制大小 即不限制等待任务数量
    Executors.defaultThreadFactory(), //用于创建线程的工厂对象
    new ThreadPoolExecutor.CallerRunsPolicy() //拒绝策略
);

Runnable target = new MyRunnable();
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
//下面4个任务在任务队列里排队
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);

//下面2个任务,到了临时线程的创建时机了
pool.execute(target);
pool.execute(target);

// 到了新任务的拒绝时机了!
pool.execute(target);


//pool.shutdown();//等待任务执行完关闭线程池
//List<Runnable> runnables = pool.shutdownNow();/立即关闭线程池 停止正在执行的任务 返回队列中未执行的任务

执行结果:

pool-1-thread-5 ==> 输出666~~
main ==> 输出666~~
pool-1-thread-1 ==> 输出666~~
pool-1-thread-3 ==> 输出666~~
pool-1-thread-4 ==> 输出666~~
pool-1-thread-2 ==> 输出666~~
//其中123是核心线程执行的 45是临时线程执行的
//注意程序还是一直运行的 线程池不会自动关闭 设计出来就是一直服务的
//main输出是因为使用了CallerRunsPolicy()拒绝策略 新来的要拒绝的任务由主线程main执行了


线程池执行Callable任务

Callable任务相对于Runnable任务来说,就是多了一个返回值。
执行Callable任务需要用到上面ExecutorService的submit方法

先准备一个Callable线程任务

public class MyCallable implements Callable<String> {
    private int n;
    public MyCallable(int n) {
        this.n = n;
    }

    // 2、重写call方法
    @Override
    public String call() throws Exception {
        // 描述线程的任务,返回线程执行返回后的结果。
        // 需求:求1-n的和返回。
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return Thread.currentThread().getName() + "求出了1-" + n + "的和是:" + sum;
    }
}

再准备一个测试类,在测试类中创建线程池,并执行callable任务。

public class ThreadPoolTest2 {
    public static void main(String[] args) throws Exception {
        // 1、通过ThreadPoolExecutor创建一个线程池对象。
        ExecutorService pool = new ThreadPoolExecutor(
            3,
            5,
            8,
            TimeUnit.SECONDS, 
            new ArrayBlockingQueue<>(4),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.CallerRunsPolicy());

        // 2、使用线程处理Callable任务。
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));

        // 3、执行完Callable任务后,需要获取返回结果。
        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
    }
}

某次执行后,结果如下图所示

pool-1-thread-1求出了1-100的和是:5050
pool-1-thread-2求出了1-200的和是:20100
pool-1-thread-3求出了1-300的和是:45150
pool-1-thread-3求出了1-400的和是:80200


核心线程数量应该配置多少?

根据经验法则,大致参考以下原则:

  • 如果是计算密集型的任务:核心线程数量 = CPU的核数 + 1
    • 原因
      1、计算密集型任务是指大部分时间都在进行 CPU 运算,几乎没有或很少有 IO 操作(如网络、磁盘操作)。因此,这种任务类型对 CPU 资源的需求很高,线程基本上是在执行计算。
      2、当任务是计算密集型时,系统的性能瓶颈主要体现在 CPU 上。因此,理论上最优的线程数量应该是与 CPU 核心数相等,这样每个核心都有一个线程在工作,最大化地利用 CPU 资源。
      3、但是,加上一个线程(即 CPU 核数 + 1)是为了容错性和并发性。有时,某些线程可能会因为上下文切换、内存缓存错失或其他系统调度原因短暂地停止运行,这样额外的一个线程可以继续执行,从而确保 CPU 始终处于高效运转状态。
  • 如果是IO密集型的任务:核心线程数量 = CPU核数 * 2
    • 原因
      1、IO 密集型任务是指大部分时间都在等待外部资源(如网络、文件系统等)的响应,CPU 实际上并没有在执行太多计算。IO 操作通常包括网络请求、文件读写等,这些操作的时间主要消耗在等待外部设备的响应上,而非 CPU 的运算。
      2、在 IO 密集型任务中,线程大部分时间是在等待 IO 操作完成,而 CPU 实际上是闲置的。在这种情况下,即使增加线程数,CPU 也有足够的资源来处理更多的任务。换句话说,尽管增加了线程,CPU 不会成为瓶颈,因为大部分时间并不消耗 CPU。
      因此,为了充分利用 CPU 和提高吞吐量,可以设置比 CPU 核数更多的线程(通常是 CPU 核数的 2 倍,再大可能会导致资源浪费或过度调度带来的开销),这样可以在某些线程阻塞等待 IO 操作时,其他线程可以继续执行。

CPU核数查看,这个cpu是16核。

请添加图片描述



线程池工具类(Executors)

有同学可能会觉得前面创建线程池的代码参数太多、记不住,有没有快捷的创建线程池的方法呢?有的。Java为开发者提供了一个创建线程池的工具类,叫做Executors,它提供了方法可以创建各种不能特点的线程池。如下图所示

请添加图片描述

接下来,我们演示一下创建固定线程数量的线程池。这几个方法用得不多,所以这里不做过多演示,同学们了解一下就行了。

public class ThreadPoolTest3 {
    public static void main(String[] args) throws Exception {
        // 1、通过Executors创建一个线程池对象。
        ExecutorService pool = Executors.newFixedThreadPool(17);

        // 2、使用线程处理Callable任务。
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));

        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());
    }
}

Executors创建线程池这么好用,为什么不推荐同学们使用呢?原因在这里:看下图,这是《阿里巴巴Java开发手册》提供的强制规范要求,在大型并发系统环境中容易出bug。

请添加图片描述



如何处理线程池中的异常?

当一个线程池里面的线程异常后:
● 当执行方式是execute()时,可以看到堆栈异常的输出,线程池会把这个线程移除掉,并创建一个新的线程放到线程池中。
● 当执行方式是submit()时,堆栈异常没有输出。但是调用Future.get()方法时,可以捕获到异常,不会把这个线程移除掉,也不会创建新的线程放入到线程池中。
以上俩种执行方式,都不会影响线程池里面其他线程的正常执行。

在Java中线程池的工作线程出现异常的时候默认会把异常往外抛,同时这个工作线程会因为异常而销毁,我们需要自己去处理对应的异常,异常处理方法有如下几种:

1.在传递任务中try catch去处理异常 对每个提交到线程池中的任务提前通过异常进行捕获,这样即便是出现异常也不会影响线程池中的工作线程。

请添加图片描述

2.使用Future来获取异常结果 submit(Callable)方法返回Future,通过调用Future.get()方法来获取任务的执行结果,如果任务执行过程中出现异常也会抛出一个ExecutionException,其中包含了任务执行过程中的实际异常

请添加图片描述

3.使用Thread. UncaughtExceptionHandler处理异常 我们可以自定义一个ThreadFactory来自定义创建线程的方式,然后为每个新线程设置一个叫UncaughtExceptionHandler,这个处理器会在线程由于未捕获异常而即将终止的时候被调用

请添加图片描述

也可以直接自定义一个UncaughtExceptionHandler,实现Thread.UncaughtExceptionHandler接口,重写未处理异常的方法,在方法里面来进行一些自定义的处理。然后在调用的时候将我们自定义的未捕获异常处理程序传入到下面这个参数里。

请添加图片描述

4.重写ThreadPoolExecutor.afterExecute处理异常 如下所示,使用了我们自定义的一个线程池(继承ThreadPoolExecutor),在里面重写了构造函数和afterExecute,重点关注afterExecute方法,在里面处理任务执行情况和异常信息。然后在创造线程池的时候使用这个自定义的线程池就行了。

请添加图片描述



线程池如何知道一个线程的任务已经执行完成?

一、当我们把一个任务丢给线程池去执行,线程池调度工作线程执行该任务的run方法,run方法正常结束即意味着任务完成。所以线程池的工作线程通过同步调用任务的run方法,等待其执行完毕再去统计任务完成数量。

二、如果想要在线程池外部获取内部的任务执行状态,有几个方式可以实现

  • 线程池提供了isTerminated()来判断线程池的运行状态,我们可以循环判断isTerminated()方法返回值来了解线程池运行状态,一旦状态是Terminated就意味着所有任务都已执行完。但是通过这个方法获取状态的前提是程序中主动调用了线程池的shutdown()方法,而在大部分应用中不会去主动关闭线程池,因此这个方法的实用性和灵活性不高。
  • 线程池有submit()方法提供了Future的返回值,可以通过Future.get()方法来获取任务的执行结果,当任务没有完成该方法会一直阻塞直到完成,因此只要该get方法正常返回就意味着该任务已经执行完成了。
  • 线程如果本身没有返回值,只能通过阻塞-唤醒的方式来实现。可以通过引入CountDownLatch计数器实现,指定一个计数器进行倒计时,其中有两个方法分别是await()阻塞线程,以及countDown()进行倒计时,一旦倒计时归零,所有被await()阻塞的线程都会被释放。基于该原理参考如下代码,我们可以定义一个CountDownLatch对象并且计数器为1,接着在线程池代码块后面调用await()方法阻塞主线程,然后当传入线程池中的任务执行完成后调用countDown()方法表示任务执行结束,最后计数器归0唤醒阻塞在await方法的主线程

请添加图片描述



提交给线程池中的任务可以被撤回吗?

可以,当向线程池提交任务时,会得到一个Future对象。这个Future对象提供了几种方法来管理任务的执行,包括取消任务。
取消任务的主要方法是Future接口中的cancel(boolean mayInterruptIfRunning)方法。这个方法尝试取消执行的任务。参数mayInterruptIfRunning指示是否允许中断正在执行的任务。如果设置为true,则表示如果任务已经开始执行,那么允许中断任务;如果设置为false,任务已经开始执行则不会被中断。

public interface Future<V> {
    // 是否取消线程的执行
    boolean cancel(boolean mayInterruptIfRunning);
    // 线程是否被取消
    boolean isCancelled();
    //线程是否执行完毕
    boolean isDone();
      // 立即获得线程返回的结果
    V get() throws InterruptedException, ExecutionException;
      // 延时时间后再获得线程返回的结果
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

取消线程池中任务的方式,代码如下,通过 future 对象的 cancel(boolean) 函数来定向取消特定的任务。

public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        Future future = service.submit(new TheradDemo());

        try {
          // 可能抛出异常
            future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
          //终止任务的执行
            future.cancel(true);
        }


Tomcat线程池和Java标准线程池的区别

Tomcat 线程池和 Java 标准线程池都用于管理线程,但它们的设计目的是不同的,主要体现在使用场景和实现细节上。

实现方式

  • Java 线程池:它是通过 ThreadPoolExecutor 来实现的,比较通用,可以用于各种类型的任务。不管是计算密集型任务还是 IO 密集型任务,Java 线程池都可以应付。它有核心线程数、最大线程数、任务队列和拒绝策略等,让开发者可以灵活配置。

  • Tomcat 线程池:其实它底层也是基于 ThreadPoolExecutor,但做了针对 Web 请求的优化。比如它会考虑到 HTTP 长连接、keep-alive 等网络通信场景。Tomcat 的线程池不仅要处理短期的请求,还要维护长连接和并发请求,它会更加注重连接的管理。

使用场景

  • Java 线程池(ThreadPoolExecutor):这是 Java 内置的线程池,适合处理各种通用的并发任务。比如,如果我们要执行异步任务、定时任务或者批量处理数据,Java 的线程池就很合适。它可以让我们通过线程复用的方式减少频繁创建和销毁线程的开销,从而提升程序性能。

  • Tomcat 线程池:这个线程池主要用来处理HTTP 请求,是 Tomcat 服务器专门优化过的线程池。它的目标是管理和优化大量的 Web 请求,比如处理浏览器发来的 HTTP 请求、保持长连接等。Tomcat 会用线程池来控制并发的请求数,确保服务器不被瞬时高并发压垮。

配置方式

  • Java 线程池:通常我们通过代码来配置 Java 线程池,可以灵活地控制线程池的核心线程数、最大线程数、任务队列等。比如,我们可以定义一个线程池,设置它最多允许多少线程并发执行,如果线程不够用时,是否可以放到队列里等待处理,或者直接拒绝新的任务。

  • Tomcat 线程池:Tomcat 线程池是通过配置文件(server.xml)来进行设置的,主要参数有最大线程数(maxThreads)、最小空闲线程数(minSpareThreads)和最大等待队列(acceptCount)等。比如,maxThreads 决定了 Tomcat 最多能同时处理多少个 HTTP 请求,而 acceptCount 决定当线程都忙不过来时,最多能有多少个请求在等待。

拒绝策略

  • Java 线程池:当线程池满了,任务无法被处理时,Java 线程池有几种处理策略,比如直接拒绝(抛出异常)、让调用线程自己执行任务、丢弃最旧的任务等。

  • Tomcat 线程池:Tomcat 的策略比较直接,当线程池和等待队列都满了时,Tomcat 会返回一个 HTTP 503 错误,告诉客户端服务器忙不过来了,稍后再试。

使用上的优化

  • Java 线程池:我们使用 Java 线程池时,通常是为了提高程序的并发性能,减少线程创建销毁的开销。我们可以根据任务的类型来调整线程池的参数,比如对于 CPU 密集型的任务,线程数可能设得相对少一些;而对于 IO 密集型的任务,线程数可以设得多一些。

  • Tomcat 线程池:Tomcat 则更多是为高并发的 Web 场景优化的。它的目标是尽可能处理更多的 HTTP 请求,但又要避免因为过多请求而让服务器崩溃,所以它的设计要更专注于请求响应的效率和服务器资源的平衡。

总结: 总的来说,Java 线程池是通用的,适合处理任意类型的并发任务,开发者有很大的自由度来配置。而Tomcat 线程池则专注于 Web 请求处理,做了很多针对 HTTP 请求的优化。两者都用线程池来提高并发性能,但各有侧重点。Java 线程池是你在普通应用中处理并发任务的工具,而 Tomcat 的线程池是为了让 Web 服务器在高并发下稳定、高效运行。



ThreadPoolExecutor、ThreadPoolTaskExecutor 与 Tomcat线程池的区别

不同场景下,有时我们用 Java 内置的 ThreadPoolExecutor,有时又会在 Spring 项目中用到 ThreadPoolTaskExecutor,而当你启动 Tomcat 时,背后默默支撑请求处理的正是 Tomcat 自己的线程池。那么,这三者到底各有何异?

1.ThreadPoolExecutor —— 纯粹的“原生态”

ThreadPoolExecutor 可以说是 Java 多线程编程中的基础组件。它出自 java.util.concurrent 包,提供了非常灵活的线程池实现:

  • 灵活定制:你可以精准设置核心线程数、最大线程数、任务队列、拒绝策略……简直就像自己调制一杯“线程鸡尾酒”,按需配置。
  • 原生组件:不依赖任何第三方框架,是 Java 标准库的一部分。如果你需要对线程池的每个细节都控制在手里,这就是你的不二选择。
  • 低层次操作:虽然功能强大,但配置和调优时需要开发者具备一定的多线程知识。适合用在自己编写的后台任务、数据处理等场景。

总的来说,ThreadPoolExecutor 就像是一台高性能跑车,虽然配置复杂,但能提供极致的性能和灵活性。

2.ThreadPoolTaskExecutor —— Spring 的优雅封装

当你使用 Spring 开发时,很多时候不希望直接接触繁琐的线程池配置。于是,ThreadPoolTaskExecutor 出现了,它就是对 ThreadPoolExecutor 的一层友好封装:

  • 便捷集成:作为 Spring 的一部分,它天然支持依赖注入和生命周期管理。你只需在配置文件中定义好参数,Spring 就会帮你搞定一切。
  • 注解驱动:结合 @Async 等注解,轻松实现异步方法调用。不需要自己手动提交任务,Spring 自动调度处理,开发体验极佳。
  • 隐藏细节:对于大部分开发者而言,ThreadPoolTaskExecutor 把很多底层细节隐藏起来,屏蔽了复杂配置,让你能专注于业务逻辑。

简单来说,它就是那种既能让你享受线程池高性能优势,又能让你免于复杂配置的“懒人神器”。

3.Tomcat线程池 —— 专为 Web 请求打造的战斗机

进入 Web 开发领域,你不可避免要和 Tomcat 打交道。其实它底层也是基于 ThreadPoolExecutor,但做了针对 Web 请求的优化:

  • 专用场景:这并不是一个通用的线程池,而是为网络请求量身定制。比如说,它会有 maxThreads、minSpareThreads 等参数,帮助你平衡并发访问和系统资源。
  • 与连接紧密结合:Tomcat 的线程池不仅仅是处理任务,更与 HTTP 连接管理密切相关。它需要应对短时高并发、长连接等场景,确保请求的快速响应。
  • 独立配置:通常在 server.xml 中进行配置,运维人员可以根据实际流量和服务器能力做出针对性调整,而不需要改变业务代码。

可以理解为,Tomcat 的线程池就是一支专门为 Web 服务量身打造的“定制军队”,它们在前线默默守护每一次 HTTP 请求的到达和响应。

总结与对比

  • 应用范围:

    • ThreadPoolExecutor:适用于各种需要自定义线程池的场景,是一个通用、底层的线程池。
    • ThreadPoolTaskExecutor:在 Spring 环境下,方便集成和管理,主要用于异步任务和调度。
    • Tomcat线程池:专注于 Web 服务器请求处理,配置项与网络连接管理紧密相关。
  • 配置与使用:

    • ThreadPoolExecutor:需要开发者自行编写代码进行详细配置,灵活但复杂。
    • ThreadPoolTaskExecutor:通过 Spring 配置文件或注解就能轻松使用,减少配置麻烦。
    • Tomcat线程池:通常通过配置文件(如 server.xml)进行参数调整,侧重于连接管理和请求调度。
  • 使用场景:

    • ThreadPoolExecutor:适合底层逻辑或需要精细调优的后台任务处理。
    • ThreadPoolTaskExecutor:适合 Spring 应用中异步任务的执行,例如异步事件处理、批量任务等。
    • Tomcat线程池:完全内嵌于 Tomcat,用于处理每个进入服务器的 HTTP 请求。



补充知识

补充几个概念性的知识点,知道这些概念什么意思就可以了。

并发和并行

先来了解一下什么是进程、线程?

正常运行的程序(软件)就是一个独立的进程
线程是属于进程,一个进程中包含多个线程
进程中的线程其实并发和并行同时存在

可以打开系统的任务管理器看看(快捷键:Ctrl+Shfit+Esc),自己的电脑上目前有哪些进程。

请添加图片描述

什么是并发?

进程中的线程由CPU负责调度执行,但是CPU同时处理线程的数量是有限的,为了保证全部线程都能执行到,CPU采用轮询机制为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
简单记:并发就是多条线程交替执行

什么是并行?

并行指的是,多个线程同时被CPU调度执行。如下图所示,多个CPU核心在执行多条线程

请添加图片描述

多线程到底是并发还是并行呢?

其实多个线程在我们的电脑上执行,并发和并行是同时存在的。



线程的生命周期

在Thread类中有一个嵌套的枚举类叫Thread.Status,这里面定义了线程的6中状态。如下图所示

请添加图片描述

NEW: 新建状态,线程还没有启动
RUNNABLE: 可以运行状态,线程调用了start()方法后处于这个状态
BLOCKED: 锁阻塞状态,没有获取到锁处于这个状态
WAITING: 无限等待状态,线程执行时被调用了wait方法处于这个状态
TIMED_WAITING: 计时等待状态,线程执行时被调用了sleep(毫秒)或者wait(毫秒)方法处于这个状态
TERMINATED: 终止状态, 线程执行完毕或者遇到异常时,处于这个状态。

这几种状态之间切换关系如下图所示

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值