【JAVA】多线程的创建、线程池创建线程的方式(超详细)
文章目录
- 一、多线程创建
- 继承 `Thread` 类
- 实现 `Runnable` 接口
- 使用匿名内部类
- 使用 `Callable` 和 `FutureTask`
- 二、线程池创建线程的方式(两类七种)
- 三、ThreadPoolExecutor 类
- 主要参数
- 队列(workQueue)
- 拒绝策略(handler)
- 四、线程池实现
- FixedThreadPool
- CachedThreadPool
- SingleThreadExecutor
- ScheduledThreadPool
- SingleThreadScheduledExecutor
- newWorkStealingPool
更多相关内容可查看
不说废话,看完直接去面试包流畅
一、多线程创建
继承 Thread
类
你可以通过继承 Thread
类并重写其 run
方法来创建线程。然后,通过创建 Thread
类的实例并调用 start
方法来启动线程。
示例代码:
class MyThread extends Thread {@Overridepublic void run() {System.out.println("Thread is running using Thread class.");}
}public class Main {public static void main(String[] args) {MyThread thread = new MyThread();thread.start();}
}
实现 Runnable
接口
另一种方法是实现 Runnable
接口并重写其 run
方法。然后,将 Runnable
实例传递给 Thread
类的构造函数来创建和启动线程。
示例代码:
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("Thread is running using Runnable interface.");}
}public class Main {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start();}
}
使用匿名内部类
可以在创建 Thread
对象时使用匿名内部类来实现 Runnable
接口。这种方式比较简洁,不需要单独创建 Runnable
类。
示例代码:
public class Main {public static void main(String[] args) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("Thread is running using anonymous class.");}});thread.start();}
}
使用 Callable
和 FutureTask
Callable
接口类似于 Runnable
,但它可以返回结果并且可以抛出异常。你可以使用 FutureTask
类来包装 Callable
实例,并通过 Thread
类来启动。
示例代码:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {return "Thread is running using Callable and FutureTask.";}
}public class Main {public static void main(String[] args) {MyCallable myCallable = new MyCallable();FutureTask<String> futureTask = new FutureTask<>(myCallable);Thread thread = new Thread(futureTask);thread.start();try {// Get the result of the computationString result = futureTask.get();System.out.println(result);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}
}
二、线程池创建线程的方式(两类七种)
类型:
- 通过 ThreadPoolExecutor 创建的线程池(一种)
- 通过 Executors 创建的线程池(六种)
方法:
- Executors.newFixedThreadPool():是一种固定大小的线程池,它会创建一个指定数量的线程,并且这些线程会在任务完成后继续存在,直到线程池被关闭。
- Executors.newCachedThreadPool():是一种缓存线程池,它会根据需要创建新线程,并且当线程闲置一段时间后会被回收。适用于执行大量短期异步任务的场景。
- Executors.newScheduledThreadPool():是一种支持任务调度的线程池,可以用于执行延迟任务和定时任务。
- Executors.newSingleThreadExecutor():是一种单线程的线程池,它只有一个线程来执行所有提交的任务,任务会按照提交的顺序执行。
- Executors.newSingleThreadScheduledExecutor():是 java.util.concurrent.Executors 类中的一个静态方法,用于创建一个单线程的调度执行器。这个执行器使用单个工作线程来执行任务,并且可以进行定期或延迟的任务调度。
- Executors.newWorkStealingPool():是 java.util.concurrent.Executors 类中的一个静态方法,用于创建一个工作窃取线程池。这个线程池基于 ForkJoinPool 实现,旨在提高线程池的吞吐量,尤其是当任务之间存在大量并行性时。
ThreadPoolExecutor
:最原始的创建线程池的方式,它包含了 7 个参数可供设置
Executors 的是基于ThreadPoolExecutor 进行封装的方法,所以ThreadPoolExecutor 更加灵活,例如:
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
执行流程(网上摘抄图片
):
三、ThreadPoolExecutor 类
主要参数
- corePoolSize:核心线程池数,即线程池中最小的线程数量。
- maximumPoolSize:最大线程池数,即线程池中允许的最大线程数量。
- keepAliveTime:最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。
- unit:
keepAliveTime
的时间单位。 - workQueue:任务队列,用于保存等待执行的任务。
- threadFactory:用于创建新线程的工厂。
- handler:当线程池和队列都满了时,支持的拒绝策略
队列(workQueue)
直接提交队列、有界任务队列、无界任务队列、优先任务队列
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
SynchronousQueue
:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque
:一个由链表结构组成的双向阻塞队列。
ArrayBlockingQueue
- 数据结构:数组
- 队列性质:有界阻塞队列
- 特性:
- 有界:具有固定的容量,一旦达到容量限制,插入操作会被阻塞,直到队列中有空间可用。
- 阻塞:如果队列已满,插入元素的操作会被阻塞;如果队列为空,移除元素的操作会被阻塞。
- 适用场景:适用于生产者-消费者模式,其中生产者和消费者需要固定大小的缓冲区以控制资源使用。
LinkedBlockingQueue
- 数据结构:链表
- 队列性质:有界阻塞队列
- 特性:
- 有界:具有可指定的容量上限,超过该容量时,插入操作会被阻塞。
- 阻塞:当队列满时,插入操作被阻塞;当队列空时,移除操作被阻塞。
- 适用场景:适合于需要大缓冲区的生产者-消费者模式,且可以通过构造函数指定容量大小。
SynchronousQueue
- 数据结构:无内部存储
- 队列性质:无界阻塞队列(虽然没有实际容量)
- 特性:
- 无存储:不保存元素,每个插入操作必须等待对应的移除操作,反之亦然。
- 阻塞:插入操作只有在另一个线程准备好接收元素时才能完成。
- 适用场景:适用于需要直接交换任务的场景,如工作线程池中任务的传递。
PriorityBlockingQueue
- 数据结构:基于优先级的内部数据结构(通常是一个优先级队列)
- 队列性质:无界阻塞队列
- 特性:
- 无界:没有固定容量限制,理论上可以容纳无限多的元素,直到系统资源耗尽。
- 优先级:根据元素的优先级进行排序,优先级高的元素会先被移除。
- 适用场景:适合需要任务优先级调度的场景,如任务调度系统或负载均衡。
DelayQueue
- 数据结构:基于优先级队列
- 队列性质:无界阻塞队列
- 特性:
- 延迟:只有当元素的延迟时间到期后才能从队列中取出。
- 优先级:内部使用优先级队列来管理元素,确保先到期的元素优先被提取。
- 适用场景:适用于需要延迟处理的任务,如缓存过期机制、延迟队列任务调度。
LinkedTransferQueue
- 数据结构:链表
- 队列性质:无界阻塞队列
- 特性:
- 无界:理论上没有容量限制,直到系统资源耗尽。
- 非阻塞方法:除了阻塞方法,还支持一些非阻塞操作,如
tryTransfer
和tryOffer
。 - 适用场景:适用于需要高效的任务传递和非阻塞操作的场景,例如在高并发环境下进行任务交换。
LinkedBlockingDeque
- 数据结构:链表
- 队列性质:双向阻塞队列
- 特性:
- 双向:支持从队列的两端插入和移除元素。
- 有界或无界:可以指定容量,也可以选择无界。
- 阻塞:如同其他阻塞队列,插入操作在队列满时会被阻塞,移除操作在队列空时会被阻塞。
- 适用场景:适合需要双向访问(双端操作)并且有缓冲需求的场景,如双端队列的缓存管理和任务处理。
拒绝策略(handler)
- AbortPolicy:拒绝并抛出异常。
- CallerRunsPolicy:使用当前调用的线程来执行此任务。
- DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
- DiscardPolicy:忽略并抛弃当前任务。
1. AbortPolicy
策略:默认策略。这个策略会抛出一个 RejectedExecutionException
,即立即终止任务提交,并通知提交者任务提交失败。
适用场景:当应用程序希望在任务提交失败时立刻知道,并进行错误处理或日志记录时,这种策略是合适的。例如,当任务的失败可能意味着系统的严重问题,开发者希望程序能立刻响应并处理这些异常情况时,可以使用 AbortPolicy
。
使用示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10),new ThreadPoolExecutor.AbortPolicy()
);
2. CallerRunsPolicy
策略:这个策略会由调用线程来执行被拒绝的任务。即,任务不会被丢弃或丢失,而是会由提交任务的线程自己执行。
适用场景:当系统希望减少提交任务的速度以避免过度拥塞时,使用这个策略是合适的。它可以作为一种“减轻负担”的方式,虽然会降低吞吐量,但可以确保任务不会丢失。适用于对任务丢失非常敏感的场景。
使用示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10),new ThreadPoolExecutor.CallerRunsPolicy()
);
3. DiscardPolicy
策略:这个策略会默默丢弃被拒绝的任务,不抛出异常,也不会进行任何处理。
适用场景:当任务丢失是可以接受的,或者任务的重要性相对较低时,可以使用这个策略。比如在某些日志处理系统中,可以丢弃一些日志记录以避免系统过载,但这需要确保丢弃任务不会对系统造成重大影响。
使用示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10),new ThreadPoolExecutor.DiscardPolicy()
);
4. DiscardOldestPolicy
策略:这个策略会丢弃任务队列中最旧的任务,然后尝试提交新的任务。
适用场景:当系统希望优先处理最近提交的任务,并丢弃最早的任务以腾出空间时,可以使用这个策略。适合任务重要性较高,且不希望丢弃最近的任务的场景。
使用示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(10),new ThreadPoolExecutor.DiscardOldestPolicy()
);
示例代码
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class CustomThreadPoolExecutorExample {public static void main(String[] args) {// 创建一个自定义的线程池BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);ThreadPoolExecutor executor = new ThreadPoolExecutor(2, // corePoolSize4, // maximumPoolSize10, // keepAliveTimeTimeUnit.SECONDS, // unitworkQueue, // workQueueExecutors.defaultThreadFactory(), // threadFactorynew ThreadPoolExecutor.CallerRunsPolicy() // handler)
四、线程池实现
FixedThreadPool
特点
- 固定数量的线程:线程池中始终保持固定数量的线程。
- 任务排队:如果所有线程都在忙碌中,新的任务将会被排队等待执行。
- 线程复用:线程在任务执行完后不会被销毁,而是继续等待新的任务。
源码及原理
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
- corePoolSize 和 maximumPoolSize:
都被设置为 nThreads,即线程池中的核心线程数和最大线程数是相同的。这意味着线程池不会创建超过 nThreads 个线程。 - keepAliveTime:
设置为 0L,表示线程在空闲时不需要等待时间。即使是非核心线程也会保持活动状态,只要线程池中有任务在运行。 - unit:
时间单位设置为 TimeUnit.MILLISECONDS,表示 keepAliveTime 的时间单位为毫秒。不过在这种情况下,由于
keepAliveTime 为 0,单位实际上并不重要。 - workQueue:
使用了一个LinkedBlockingQueue(),这是一个无界阻塞队列。它会存储待执行的任务。由于是无界队列,线程池不会因为任务积压而拒绝新任务,任务会一直被排队直到有线程可用为止。
创建方式
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class FixedThreadPoolExample {public static void main(String[] args) {// 创建一个固定大小的线程池,线程数为 5ExecutorService executorService = Executors.newFixedThreadPool(5);// 提交多个任务到线程池for (int i = 0; i < 10; i++) {final int taskId = i;executorService.submit(() -> {System.out.println("Running task " + taskId + " in thread " + Thread.currentThread().getName());});}// 关闭线程池executorService.shutdown();}
}
CachedThreadPool
特点
- 线程复用:可以重用以前创建的线程,如果这些线程在任务完成后没有被回收。
- 线程数量:线程池的线程数是动态变化的,最多允许有一个线程存在,空闲线程会在 60 秒后被回收。
源码及原理
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
- corePoolSize (0):
线程池的核心线程数设置为 0。意味着线程池在没有任务执行时不会保持任何核心线程。即使有任务到来,线程池也不会预先创建线程。 - maximumPoolSize (Integer.MAX_VALUE):
线程池可以创建的最大线程数设置为
Integer.MAX_VALUE,即几乎没有限制。这表示线程池可以根据需要创建尽可能多的线程来处理任务,理论上没有上限。 - keepAliveTime (60L):
非核心线程在空闲时的存活时间设置为 60 秒。也就是说,如果线程池中的线程数超过核心线程数,且这些线程在 60
秒内没有任务可执行,则这些线程会被终止并从线程池中移除。 - unit (TimeUnit.SECONDS):
时间单位设置为秒。与 keepAliveTime 一起使用,表示线程在空闲状态下的最大存活时间是 60 秒。 - workQueue (new SynchronousQueue()):
使用了一个 SynchronousQueue 作为任务队列。SynchronousQueue
是一个特殊的队列,它没有缓冲区,每个插入操作必须等待一个删除操作,反之亦然。即任务必须立即被处理,而不能被排队。这种设计使得线程池可以快速响应任务并动态调整线程的创建。
创建方式
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class CachedThreadPoolExample {public static void main(String[] args) {// 创建一个缓存线程池ExecutorService executorService = Executors.newCachedThreadPool();// 提交多个任务到线程池for (int i = 0; i < 10; i++) {final int taskId = i;executorService.submit(() -> {System.out.println("Running task " + taskId + " in thread " + Thread.currentThread().getName());});}// 关闭线程池executorService.shutdown();}
}
SingleThreadExecutor
保证先进先出的执行顺序
特点
- 单线程:线程池中只有一个线程,保证任务按照提交的顺序执行。
- 任务排队:任务会被排队,直到前一个任务完成。
创建方式
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class SingleThreadExecutorExample {public static void main(String[] args) {// 创建一个单线程的线程池ExecutorService executorService = Executors.newSingleThreadExecutor();// 提交多个任务到线程池for (int i = 0; i < 10; i++) {final int taskId = i;executorService.submit(() -> {System.out.println("Running task " + taskId + " in thread " + Thread.currentThread().getName());});}// 关闭线程池executorService.shutdown();}
}
ScheduledThreadPool
ScheduledThreadPool
是一种支持任务调度的线程池,可以用于执行延迟任务和定时任务。
特点
- 支持调度:可以调度任务在指定的延迟后执行或以固定的时间间隔执行。
- 线程数量:线程池大小可根据需要调整。
创建方式
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduledThreadPoolExample {public static void main(String[] args) {// 创建一个支持调度的线程池,线程数为 3ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);// 提交一个延迟 1 秒执行的任务scheduledExecutorService.schedule(() -> {System.out.println("Task executed after 1 second delay");}, 1, TimeUnit.SECONDS);// 提交一个固定周期执行的任务,初始延迟为 0,周期为 2 秒scheduledExecutorService.scheduleAtFixedRate(() -> {System.out.println("Task executed at fixed rate");}, 0, 2, TimeUnit.SECONDS);// 关闭线程池scheduledExecutorService.shutdown();}
}
参数解释
- 初始延迟:在
scheduledExecutorService.schedule()
方法中,第一个参数是延迟时间,单位由TimeUnit
指定。 - 周期:在
scheduledExecutorService.scheduleAtFixedRate()
方法中,第二个参数是初始延迟,第三个参数是任务执行的周期时间,单位由TimeUnit
指定。
SingleThreadScheduledExecutor
SingleThreadScheduledExecutor
是一个单线程的计划任务执行器,它会在一个单独的线程中执行所有的任务。这个执行器由 Executors
工厂方法提供。它的主要特点包括:
-
单线程执行:所有的任务都由单个线程执行,这保证了任务的顺序执行,但也意味着任务的执行不会并行化。
-
计划任务:支持定期或延迟执行任务,如通过
schedule
、scheduleAtFixedRate
或scheduleWithFixedDelay
方法。 -
线程安全:即使只有一个线程,也会处理线程安全问题,确保任务不会相互干扰。
ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
使用场景
适合于需要单线程执行且定期或延迟任务的场景,例如定时任务和周期性任务。
newWorkStealingPool
newWorkStealingPool
是一个工厂方法,提供了基于工作窃取算法的线程池实现。这种线程池由 Executors
类提供,主要用于高效地处理大量任务。以下是对它的详细讲解:
ExecutorService executor = Executors.newWorkStealingPool();
主要特点:
-
工作窃取算法:
- 使用工作窃取算法来提高任务的执行效率。线程池中的每个线程维护一个自己的任务队列,如果线程的队列为空,它可以从其他线程的队列中窃取任务来执行。
-
动态线程数量:
- 默认情况下,
newWorkStealingPool()
方法创建的线程池会使用Runtime.getRuntime().availableProcessors()
作为线程数的上限。这通常是系统的处理器核心数,以便充分利用多核处理器的并行能力。
- 默认情况下,
-
提高吞吐量:
- 工作窃取池能有效减少任务调度延迟,提高系统吞吐量。它适用于计算密集型任务或任务量波动大的场景。
使用场景:
适合需要高并发处理和任务量变化较大的应用场景,如高吞吐量的计算任务和负载均衡任务处理。
看到这多线程你已经无敌了!!