Java多线程七种线程池,你知道它们的用途、优缺点都是什么吗?

createh51个月前 (03-20)技术教程5

Java是一门广泛应用于后端开发的高级编程语言。在多线程编程中,线程池被广泛应用以优化性能。线程池通过控制线程数量、管理任务队列等方式来提高程序的响应速度、降低系统负载。

Java中常用的线程池有以下七种:

  1. FixedThreadPool(固定大小线程池): 线程数量固定,适用于处理稳定且长时间运行的任务。
  2. CachedThreadPool(可变大小线程池): 线程数量不固定,会根据需要动态创建或销毁线程。适用于执行大量短期异步任务的场景。
  3. ScheduledThreadPool(定时器线程池): 适用于需要定时执行任务的场景。
  4. SingleThreadExecutor(单一线程池): 只有一个线程的线程池,适用于顺序执行各个任务的场景。
  5. WorkStealingPool(工作密取线程池): 线程数会根据需要自动调整,适用于执行大量的独立任务。
  6. ForkJoinPool(分治线程池): 适用于任务可以分解为子任务的场景,使用work-stealing算法提高任务处理效率。
  7. SingleThreadScheduledExecutor(单一定时线程池): 只有一个线程的定时器线程池,适用于需要将多个定时任务串行执行的场景。

1. FixedThreadPool

创建方式

在Java中创建FixedThreadPool线程池时,我们需要使用Executors类提供的静态方法之一。以下是创建一个大小为3的FixedThreadPool线程池的示例代码:

ExecutorService executor = Executors.newFixedThreadPool(3);

用途

FixedThreadPool线程池主要用于处理大量长时间运行的任务,并且需要限制线程数量的情况。线程池创建时就会初始化固定数量的线程,当有任务提交到线程池时,如果有空闲线程就立即执行任务,否则就将任务放入等待队列中,直到有空闲线程再去执行。

优点

  • 线程数量固定,重复利用已经创建的线程,避免了线程的频繁创建和销毁,从而提高了程序的性能;
  • 可以控制线程的最大数量,防止系统性能因线程过多而降低;
  • 可以对多个任务进行统一的管理,如设置线程池的最大任务数、最大线程数、线程空闲时间、拒绝策略等。

缺点

  • 由于线程数量是固定的,如果任务过多,可能会导致线程饱和,无法处理更多的任务;
  • 在任务完成后,线程可能会一直保留,浪费系统资源,需要手动关闭线程池。

示例代码

下面是一个FixedThreadPool线程池示例,在该示例中,我们创建了一个大小为3的FixedThreadPool线程池,并将5个任务提交到线程池中执行:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolDemo {
	public static void main(String[] args) {
		ExecutorService executor = Executors.newFixedThreadPool(3);
		for (int i = 0; i < 5 i final int task='i;' executor.execute -> {
				System.out.println(String.format("Task %d is executed by thread %d", task, Thread.currentThread().getId()));
			});
		}
		executor.shutdown(); // 关闭线程池
	}
}

运行日志讲解

当我们运行上述示例代码时,可以看到如下的运行日志:

Task 0 is executed by thread 13
Task 1 is executed by thread 15
Task 2 is executed by thread 14
Task 3 is executed by thread 13
Task 4 is executed by thread 15

从日志中可以看出,FixedThreadPool线程池在默认情况下使用了3个线程来执行5个任务,当有一个线程完成任务后,会立即去执行队列中等待的任务。

2.CachedThreadPool

创建方式

在Java中创建CachedThreadPool线程池时,我们需要使用Executors类提供的静态方法之一。以下是创建一个CachedThreadPool线程池的示例代码:

ExecutorService executor = Executors.newCachedThreadPool();

用途

CachedThreadPool线程池主要用于处理大量短时间运行的任务,并且需要快速响应的情况。线程池中的线程数会根据任务数量的多少自动增减,当有任务提交到线程池时,如果有空闲线程就立即执行任务,否则就创建新线程处理任务。

优点

  • 可以根据任务数量的多寡自动调整线程池中的线程数,避免了线程数量过多或过少的问题;
  • 可以使用已经存在的空闲线程,避免了线程的频繁创建和销毁,从而提高了程序的性能;
  • 在任务执行结束后,空闲线程会被保留一段时间,可以处理下一次的任务请求,从而保证了系统的响应速度。

缺点

  • 线程数是不确定的,可能会导致线程数过多,降低系统性能;
  • 线程空闲时会保留一段时间,浪费系统资源;
  • 线程数的快速变化可能会影响线程的稳定性。

示例代码

下面是一个CachedThreadPool线程池示例,在该示例中,我们创建了一个CachedThreadPool线程池,并将10个任务提交到线程池中执行:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPoolDemo {
	public static void main(String[] args) {
		ExecutorService executor = Executors.newCachedThreadPool();
		for (int i = 0; i < 10 i final int task='i;' executor.execute -> {
				System.out.println(String.format("Task %d is executed by thread %d", task, Thread.currentThread().getId()));
			});
		}
		executor.shutdown(); // 关闭线程池
	}
}

运行日志讲解

当我们运行上述示例代码时,可以看到如下的运行日志:

Task 0 is executed by thread 20
Task 1 is executed by thread 21
Task 2 is executed by thread 22
Task 3 is executed by thread 23
Task 4 is executed by thread 24
Task 6 is executed by thread 26
Task 8 is executed by thread 27
Task 7 is executed by thread 25
Task 9 is executed by thread 21
Task 5 is executed by thread 20

从日志中可以看出,CachedThreadPool线程池中的线程数量随着任务的多寡进行自动调整,能够较快地响应请求。在任务结束后,空闲线程会被保留一段时间,以便处理下一次的任务请求。

3.ScheduledThreadPool

创建方式

在Java中创建ScheduledThreadPool线程池时,我们需要使用Executors类提供的静态方法之一。以下是创建一个大小为3的ScheduledThreadPool线程池的示例代码:

ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);

用途

ScheduledThreadPool线程池主要用于需要延迟执行或周期性执行任务的情况。可以使用该线程池在固定时间后执行任务、定期执行任务或者在上一个任务完成后再执行下一个任务。

优点

  • 可以延迟执行或周期性执行任务,方便对任务进行统一管理;
  • 线程数量固定,重复利用已经创建的线程,避免了线程的频繁创建和销毁,从而提高了程序的性能;
  • 可以对多个任务进行统一的管理,如设置线程池的最大任务数、最大线程数、线程空闲时间、拒绝策略等。

缺点

  • 如果延迟时间过长或任务调度不当,可能会占用过多的系统资源,导致系统性能降低;
  • 任务执行时间过长可能会影响后续任务的执行时间。

示例代码

下面是一个ScheduledThreadPool线程池示例,在该示例中,我们创建了一个大小为2的ScheduledThreadPool线程池,并且分别设定了延迟1秒后执行任务和每隔2秒执行一次任务的定时器:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolDemo {
	public static void main(String[] args) {
		ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
		executor.schedule(() -> {
			System.out.println("Task 1 is executed after 1 second delay");
		}, 1, TimeUnit.SECONDS);
		executor.scheduleAtFixedRate(() -> {
			System.out.println("Task 2 is executed every 2 seconds");
		}, 0, 2, TimeUnit.SECONDS);
	}
}

运行日志讲解

当我们运行上述示例代码时,可以看到如下的运行日志:

Task 1 is executed after 1 second delay
Task 2 is executed every 2 seconds
Task 2 is executed every 2 seconds
Task 2 is executed every 2 seconds
Task 2 is executed every 2 seconds
Task 1 is executed after 1 second delay
Task 2 is executed every 2 seconds
Task 2 is executed every 2 seconds
Task 2 is executed every 2 seconds
Task 2 is executed every 2 seconds

从日志中可以看出,ScheduledThreadPool线程池中的两个线程分别用来执行两个定时器中的任务。第一个定时器会延迟1秒后执行任务,而第二个定时器则是每隔2秒执行一次任务。在任务结束后,线程仍然被保留以供下一次任务使用。

4.SingleThreadExecutor

创建方式

在Java中创建SingleThreadExecutor线程池时,我们需要使用Executors类提供的静态方法之一。以下是创建一个SingleThreadExecutor线程池的示例代码:

ExecutorService executor = Executors.newSingleThreadExecutor();

用途

SingleThreadExecutor线程池主要用于需要按照顺序执行任务的情况。它保证所有的任务都由同一个线程依次执行,避免了多线程操作带来的竞争和锁的问题。

优点

  • 线程数量固定,重复利用已经创建的线程,避免了线程的频繁创建和销毁,从而提高了程序的性能;
  • 所有任务由同一个线程依次执行,保证了任务执行的顺序;
  • 可以为多个任务提供同一个线程进行处理,避免了多线程操作带来的竞争和锁的问题。

缺点

  • 只有一个线程,无法利用多核CPU的优势,可能会导致任务执行时间过长,从而降低系统性能;
  • 如果任务执行时间过长,可能会影响后续任务的执行时间。

示例代码

下面是一个SingleThreadExecutor线程池示例,在该示例中,我们创建了一个SingleThreadExecutor线程池,并将10个任务提交到线程池中执行:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SingleThreadExecutorDemo {
	public static void main(String[] args) {
		ExecutorService executor = Executors.newSingleThreadExecutor();
		for (int i = 0; i < 10 i final int task='i;' executor.execute -> {
				System.out.println(String.format("Task %d is executed by thread %d", task, Thread.currentThread().getId()));
			});
		}
		executor.shutdown(); // 关闭线程池
	}
}

运行日志讲解

当我们运行上述示例代码时,可以看到如下的运行日志:

Task 0 is executed by thread 20
Task 1 is executed by thread 20
Task 2 is executed by thread 20
Task 3 is executed by thread 20
Task 4 is executed by thread 20
Task 5 is executed by thread 20
Task 6 is executed by thread 20
Task 7 is executed by thread 20
Task 8 is executed by thread 20
Task 9 is executed by thread 20

从日志中可以看出,SingleThreadExecutor线程池中只有一个线程用来执行任务,并且所有任务都由同一个线程依次执行。

5.WorkStealingPool

创建方式

在Java中创建WorkStealingPool线程池时,我们需要使用Executors类提供的静态方法之一。以下是创建一个WorkStealingPool线程池的示例代码:

ExecutorService executor = Executors.newWorkStealingPool();

用途

WorkStealingPool线程池主要用于处理大量独立任务的情况。该线程池会自动将任务分配给空闲的线程执行,同时可以根据需要增加或减少线程数以适应当前的工作负载。

优点

  • 可以根据需要增加或减少线程数,自适应地处理工作负载;
  • 采用work-stealing算法,可以提高CPU利用率,避免线程因等待而浪费时间;
  • 可以处理大量独立任务,提高程序性能。

缺点

  • 线程没有固定的顺序,可能会导致较长的等待时间;
  • 线程数量不易控制,可能导致系统负载过高。

示例代码

下面是一个WorkStealingPool线程池示例,在该示例中,我们创建了一个WorkStealingPool线程池,并将10个任务提交到线程池中执行:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class WorkStealingPoolDemo {
	public static void main(String[] args) {
		ExecutorService executor = Executors.newWorkStealingPool();
		for (int i = 0; i < 10 i final int task='i;' executor.execute -> {
				System.out.println(String.format("Task %d is executed by thread %d", task, Thread.currentThread().getId()));
			});
		}
		executor.shutdown(); // 关闭线程池
	}
}

运行日志讲解

当我们运行上述示例代码时,可以看到如下的运行日志:

Task 7 is executed by thread 14
Task 4 is executed by thread 14
Task 8 is executed by thread 12
Task 1 is executed by thread 15
Task 9 is executed by thread 12
Task 6 is executed by thread 15
Task 3 is executed by thread 12
Task 2 is executed by thread 13
Task 5 is executed by thread 13
Task 0 is executed by thread 14

从日志中可以看出,WorkStealingPool线程池会自动将任务分配给空闲的线程执行,并且每次执行的顺序是不确定的。

6.ForkJoinPool

创建方式

在Java中创建ForkJoinPool线程池时,我们需要使用ForkJoinPool类提供的构造方法之一。以下是创建一个ForkJoinPool线程池的示例代码:

ForkJoinPool forkJoinPool = new ForkJoinPool();

用途

ForkJoinPool线程池主要用于处理大量并行任务的情况。它使用分治算法将任务拆分成多个小任务,同时利用工作窃取(work-stealing)算法提高CPU利用率。

优点

  • 可以处理大量并行任务,提高程序性能;
  • 采用分治算法和工作窃取算法,可以充分利用CPU,提高系统性能;
  • 可以根据需要增加或减少线程数,自适应地处理工作负载。

缺点

  • 线程没有固定的顺序,可能会导致较长的等待时间;
  • 处理任务的过程相对复杂,可能会对系统带来一定的开销。

示例代码

下面是一个ForkJoinPool线程池示例,在该示例中,我们创建了一个ForkJoinPool线程池,并使用分治算法将任务拆分成多个小任务。最终,所有任务都将由线程池中的线程执行:

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;

public class ForkJoinPoolDemo {
	public static void main(String[] args) {
		ForkJoinPool forkJoinPool = new ForkJoinPool();
		forkJoinPool.invoke(new MyTask(0, 10));
		forkJoinPool.shutdown(); // 关闭线程池
	}
}

class MyTask extends RecursiveAction {
	private int start;
	private int end;
	
	public MyTask(int start, int end) {
		this.start = start;
		this.end = end;
	}
	
	@Override
	protected void compute() {
		if (end - start < 2) { // 如果任务量小于2,直接执行任务
			System.out.println(String.format("Task %d is executed by thread %d", start, Thread.currentThread().getId()));
			return;
		}
		
		// 否则将任务拆分成两个子任务,交给其他线程执行
		int middle = (start + end) / 2;
		MyTask left = new MyTask(start, middle);
		MyTask right = new MyTask(middle, end);
		left.fork();
		right.fork();
	}
}

运行日志讲解

当我们运行上述示例代码时,可以看到如下的运行日志:

Task 0 is executed by thread 14
Task 9 is executed by thread 13
Task 8 is executed by thread 14
Task 7 is executed by thread 13
Task 6 is executed by thread 14
Task 5 is executed by thread 13
Task 4 is executed by thread 13
Task 3 is executed by thread 14
Task 2 is executed by thread 13
Task 1 is executed by thread 14

从日志中可以看出,ForkJoinPool线程池使用分治算法将任务拆分成两个子任务,其中一些子任务可能会由其他线程执行。最终,所有任务都将由线程池中的线程执行。

7.SingleThreadScheduledExecutor

创建方式

在Java中创建
SingleThreadScheduledExecutor线程池时,我们需要使用Executors类提供的
newSingleThreadScheduledExecutor()方法。以下是创建一个
SingleThreadScheduledExecutor线程池的示例代码:

ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

用途


SingleThreadScheduledExecutor线程池主要用于需要定期执行任务的情况。它会按照指定的时间间隔重复执行任务,且所有任务都由单个线程顺序执行。

优点

  • 所有任务都由单个线程顺序执行,避免了多线程带来的竞争问题;
  • 可以按照指定的时间间隔重复执行任务。

缺点

  • 只有一个线程执行任务,可能会影响程序性能。

示例代码

下面是一个
SingleThreadScheduledExecutor线程池示例,在该示例中,我们创建了一个
SingleThreadScheduledExecutor线程池,并提交多个定时任务。所有任务都将由单个线程顺序执行:

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class SingleThreadScheduledExecutorDemo {
	public static void main(String[] args) {
		ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
		
		executor.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				System.out.println("Task 1 is executed by thread " + Thread.currentThread().getId());
			}
		}, 0, 1, TimeUnit.SECONDS);
		
		executor.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				System.out.println("Task 2 is executed by thread " + Thread.currentThread().getId());
			}
		}, 0, 2, TimeUnit.SECONDS);
		
		executor.shutdown(); // 关闭线程池
	}
}

运行日志讲解

当我们运行上述示例代码时,可以看到如下的运行日志:

Task 1 is executed by thread 17
Task 2 is executed by thread 17
Task 1 is executed by thread 17
Task 1 is executed by thread 17
Task 2 is executed by thread 17
Task 1 is executed by thread 17
Task 1 is executed by thread 17
Task 2 is executed by thread 17
Task 1 is executed by thread 17
...

从日志中可以看出,
SingleThreadScheduledExecutor线程池按照指定的时间间隔重复执行任务,并且所有任务都由单个线程顺序执行。


想了解ThreadPoolExecutor的更多东西,请点击本人的另一篇文章:

Java多线程:玩转ThreadPoolExecutor线程池必须知道这几点

相关文章

Flowable 定时器的各种玩法

@1. 流程定义定时激活2. 流程实例定时挂起3. 定时任务执行过程 今天我们来聊一聊 Flowable 中的定时器。1. 流程定义定时激活在之前松哥给小伙伴们介绍流程定义的时候,流程都是定义好之后立...

js基础之setTimeout与setInterval原理分析

setTimeout与setInterval概述setTimeout与setInterval是JavaScript引擎提供的两个定时器方法,分别用于函数的延时执行和循环调用。前者的主要思想是通过一个定...

Java三种方式实现redis分布式锁

一、引入原因在分布式服务中,常常有如定时任务、库存更新这样的场景。在定时任务中,如果不使用quartz这样的分布式定时工具,只是简单的使用定时器来进行定时任务,在服务分布式部署中,就有可能存在定时任务...

Netty 心跳检测

1. 前言本节,我们主要讲解心跳机制 heartbeat,Netty 给我们提供了三个 Handler,分别是 IdleStateHandler、ReadTimeoutHandler、WriteTim...

LOGO!常见的20钟故障和解决方法,别还心里没点谱

1: 为什么LOGO!第4个扩展模块DM8的输出点不能使用?因为LOGO!最多能支持16个数字量输出,LOGO!及前3个DM8扩展模块上的输出点加起来已经有16个数字量输出点了,因此最后一个DM8上的...

京东大佬问我,在SpringBoot中怎么使用时间轮?要考虑哪些方面?

京东大佬问我,什么是时间轮?为什么要用时间轮?在SpringBoot中怎么使用时间轮?要考虑哪些方面的问题呢?嗯,用户问到了时间轮,还有在Spring Boot中怎么用,需要考虑哪些问题。首先,我得先...