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

在这里插入图片描述


更多相关内容可查看

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

一、多线程创建

继承 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();
    }
}

使用 CallableFutureTask

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> {
    @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<String> 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 创建的线程池(六种)

方法:

  • 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:最大线程数可以存活的时间,当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程。
  • unitkeepAliveTime 的时间单位。
  • workQueue:任务队列,用于保存等待执行的任务。
  • threadFactory:用于创建新线程的工厂。
  • handler:当线程池和队列都满了时,支持的拒绝策略

队列(workQueue)

直接提交队列、有界任务队列、无界任务队列、优先任务队列

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
  • SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

ArrayBlockingQueue

  • 数据结构:数组
  • 队列性质:有界阻塞队列
  • 特性
    • 有界:具有固定的容量,一旦达到容量限制,插入操作会被阻塞,直到队列中有空间可用。
    • 阻塞:如果队列已满,插入元素的操作会被阻塞;如果队列为空,移除元素的操作会被阻塞。
    • 适用场景:适用于生产者-消费者模式,其中生产者和消费者需要固定大小的缓冲区以控制资源使用。

LinkedBlockingQueue

  • 数据结构:链表
  • 队列性质:有界阻塞队列
  • 特性
    • 有界:具有可指定的容量上限,超过该容量时,插入操作会被阻塞。
    • 阻塞:当队列满时,插入操作被阻塞;当队列空时,移除操作被阻塞。
    • 适用场景:适合于需要大缓冲区的生产者-消费者模式,且可以通过构造函数指定容量大小。

SynchronousQueue

  • 数据结构:无内部存储
  • 队列性质:无界阻塞队列(虽然没有实际容量)
  • 特性
    • 无存储:不保存元素,每个插入操作必须等待对应的移除操作,反之亦然。
    • 阻塞:插入操作只有在另一个线程准备好接收元素时才能完成。
    • 适用场景:适用于需要直接交换任务的场景,如工作线程池中任务的传递。

PriorityBlockingQueue

  • 数据结构:基于优先级的内部数据结构(通常是一个优先级队列)
  • 队列性质:无界阻塞队列
  • 特性
    • 无界:没有固定容量限制,理论上可以容纳无限多的元素,直到系统资源耗尽。
    • 优先级:根据元素的优先级进行排序,优先级高的元素会先被移除。
    • 适用场景:适合需要任务优先级调度的场景,如任务调度系统或负载均衡。

DelayQueue

  • 数据结构:基于优先级队列
  • 队列性质:无界阻塞队列
  • 特性
    • 延迟:只有当元素的延迟时间到期后才能从队列中取出。
    • 优先级:内部使用优先级队列来管理元素,确保先到期的元素优先被提取。
    • 适用场景:适用于需要延迟处理的任务,如缓存过期机制、延迟队列任务调度。

LinkedTransferQueue

  • 数据结构:链表
  • 队列性质:无界阻塞队列
  • 特性
    • 无界:理论上没有容量限制,直到系统资源耗尽。
    • 非阻塞方法:除了阻塞方法,还支持一些非阻塞操作,如 tryTransfertryOffer
    • 适用场景:适用于需要高效的任务传递和非阻塞操作的场景,例如在高并发环境下进行任务交换。

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, // 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<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) {
        // 创建一个固定大小的线程池,线程数为 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<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) {
        // 创建一个支持调度的线程池,线程数为 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 工厂方法提供。它的主要特点包括:

  1. 单线程执行:所有的任务都由单个线程执行,这保证了任务的顺序执行,但也意味着任务的执行不会并行化。

  2. 计划任务:支持定期或延迟执行任务,如通过 schedulescheduleAtFixedRatescheduleWithFixedDelay 方法。

  3. 线程安全:即使只有一个线程,也会处理线程安全问题,确保任务不会相互干扰。

ExecutorService executor = Executors.newSingleThreadScheduledExecutor();

使用场景

适合于需要单线程执行且定期或延迟任务的场景,例如定时任务和周期性任务。


newWorkStealingPool

newWorkStealingPool 是一个工厂方法,提供了基于工作窃取算法的线程池实现。这种线程池由 Executors 类提供,主要用于高效地处理大量任务。以下是对它的详细讲解:

ExecutorService executor = Executors.newWorkStealingPool();

主要特点:

  1. 工作窃取算法

    • 使用工作窃取算法来提高任务的执行效率。线程池中的每个线程维护一个自己的任务队列,如果线程的队列为空,它可以从其他线程的队列中窃取任务来执行。
  2. 动态线程数量

    • 默认情况下,newWorkStealingPool() 方法创建的线程池会使用 Runtime.getRuntime().availableProcessors() 作为线程数的上限。这通常是系统的处理器核心数,以便充分利用多核处理器的并行能力。
  3. 提高吞吐量

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

使用场景:

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来一杯龙舌兰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值