0 %

【JAVA】多线程的创建、线程池创建线程的方式(超详细)

2025-06-05 16:38:29

文章目录

一、多线程创建继承 `Thread` 类实现 `Runnable` 接口使用匿名内部类使用 `Callable` 和 `FutureTask`

二、线程池创建线程的方式(四类七种)三、ThreadPoolExecutor 类主要参数队列(workQueue)拒绝策略(handler)

四、线程池实现FixedThreadPoolCachedThreadPoolSingleThreadExecutorScheduledThreadPoolSingleThreadScheduledExecutornewWorkStealingPool

更多相关内容可查看

不说废话,看完直接去面试包流畅

一、多线程创建

继承 Thread 类

你可以通过继承 Thread 类并重写其 run 方法来创建线程。然后,通过创建 Thread 类的实例并调用 start 方法来启动线程。

示例代码:

class MyThread extends Thread {

@Override

public 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 {

@Override

public 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() {

@Override

public 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 {

@Override

public 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 futureTask = new FutureTask<>(myCallable);

Thread thread = new Thread(futureTask);

thread.start();

try {

// Get the result of the computation

String result = futureTask.get();

System.out.println(result);

} catch (InterruptedException | ExecutionException e) {

e.printStackTrace();

}

}

}

二、线程池创建线程的方式(四类七种)

类型:

通过 ThreadPoolExecutor 创建的线程池(一种)通过 Executors 创建的线程池(六种)通过CompletableFuture 创建线程池通过ThreadPoolTaskExecutor 创建线程池

方法:

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());

}

执行流程(网上摘抄图片):

三、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 workQueue = new ArrayBlockingQueue<>(10);

ThreadPoolExecutor executor = new ThreadPoolExecutor(

2, // corePoolSize

4, // maximumPoolSize

10, // keepAliveTime

TimeUnit.SECONDS, // unit

workQueue, // workQueue

Executors.defaultThreadFactory(), // threadFactory

new ThreadPoolExecutor.CallerRunsPolicy() // handler

)

四、线程池实现

FixedThreadPool

特点

固定数量的线程:线程池中始终保持固定数量的线程。任务排队:如果所有线程都在忙碌中,新的任务将会被排队等待执行。线程复用:线程在任务执行完后不会被销毁,而是继续等待新的任务。

源码及原理

public static ExecutorService newFixedThreadPool(int nThreads) {

return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());

}

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) {

// 创建一个固定大小的线程池,线程数为 5

ExecutorService 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());

}

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) {

// 创建一个支持调度的线程池,线程数为 3

ScheduledExecutorService 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() 作为线程数的上限。这通常是系统的处理器核心数,以便充分利用多核处理器的并行能力。 提高吞吐量:

工作窃取池能有效减少任务调度延迟,提高系统吞吐量。它适用于计算密集型任务或任务量波动大的场景。

使用场景:

适合需要高并发处理和任务量变化较大的应用场景,如高吞吐量的计算任务和负载均衡任务处理。

看到这多线程你已经无敌了!!

Posted in 比利时世界杯
Copyright © 2088 2034年世界杯_足球中国世界杯预选赛 - qdhuaxue.com All Rights Reserved.
友情链接