Java 之多线程高级
本文将深入探讨Java线程池的方方面面,从线程状态介绍开始,逐步深入线程池的原理、Executors默认线程池的使用,自定义线程池的创建,ThreadPoolExecutor的参数详解,以及非默认任务拒绝策略的应用。最后,我们将通过一个小案例来展示如何使用线程池来提高程序效率。
1. 线程状态介绍
Java线程的生命周期可以用以下状态来描述:
-
新建 (New): 线程对象被创建但还未启动。
-
可运行 (Runnable): 线程已经启动,正在等待获取 CPU 时间片执行。
-
运行 (Running): 线程获得了 CPU 时间片,正在执行任务。
-
阻塞 (Blocked): 线程正在等待某些事件发生,例如 I/O 操作完成、获取锁等。
-
等待 (Waiting): 线程正在等待其他线程通知它才能继续执行。
-
超时等待 (Timed Waiting): 线程正在等待其他线程通知它,但有一个超时时间限制。
-
终止 (Terminated): 线程已经执行完毕,无法再次启动。
代码示例:
public class ThreadStateDemo {public static void main(String[] args) {// 创建一个新的线程Thread thread = new Thread(() -> {// 模拟线程运行逻辑System.out.println("线程正在运行...");try {// 模拟阻塞操作Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程运行结束...");});// 启动线程thread.start();// 获取线程状态并打印System.out.println("线程状态:" + thread.getState()); }
}
2. 线程池---基本原理
线程池是一种管理和复用线程的技术,它通过维护一组可重用线程来处理任务,避免频繁创建和销毁线程带来的性能开销。
基本原理:
-
线程池维护一个工作线程集合: 用于执行提交的任务。
-
任务队列: 用于存储等待执行的任务。
-
任务提交: 当有新的任务提交时,线程池会判断是否有空闲线程,如果有则直接分配任务给该线程执行,否则将任务加入任务队列等待。
-
线程回收: 当线程执行完任务后,不会立即销毁,而是进入等待状态,等待新的任务分配。
优势:
-
减少创建和销毁线程的开销: 重用线程可以避免频繁创建和销毁线程带来的性能损耗。
-
控制并发线程数量: 可以有效地控制并发线程数,避免资源耗尽。
-
提高响应速度: 当任务提交时,如果线程池中有空闲线程,可以立即执行任务,提高响应速度。
-
简化线程管理: 通过线程池可以方便地管理线程,无需手动创建和销毁线程。
3. 线程池---Executors默认线程池
java.util.concurrent.Executors 类提供了一些常用的线程池创建方法,包括:
-
newCachedThreadPool(): 创建一个可缓存的线程池,如果线程池长度超过处理需要,可回收空闲线程,如果没有空闲线程则创建新的线程。
-
newFixedThreadPool(int nThreads): 创建一个固定线程数的线程池,如果提交的任务数超过线程数,则会将任务放入队列等待。
-
newSingleThreadExecutor(): 创建一个单线程化的线程池,保证任务按照顺序执行。
-
newScheduledThreadPool(int corePoolSize): 创建一个可以执行延迟任务或周期性任务的线程池。
代码示例:
public class ExecutorsDemo {public static void main(String[] args) {// 创建一个缓存线程池ExecutorService cachedThreadPool = Executors.newCachedThreadPool();// 创建一个固定线程数的线程池ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);// 创建一个单线程化的线程池ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();// 创建一个可以执行延迟任务或周期性任务的线程池ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);}
}
4. 线程池---Executors创建指定上限的线程池
可以使用 Executors 类中的 newFixedThreadPool(int nThreads) 方法来创建指定上限的线程池。
代码示例:
public class FixedThreadPoolDemo {public static void main(String[] args) {// 创建一个固定线程数为 5 的线程池ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);// 提交任务到线程池for (int i = 0; i < 10; i++) {fixedThreadPool.execute(() -> {// 模拟任务执行逻辑System.out.println("线程池中的线程正在执行任务...");});}// 关闭线程池fixedThreadPool.shutdown();}
}
5. 线程池---ThreadPoolExecutor
java.util.concurrent.ThreadPoolExecutor 类是创建线程池的核心类,它提供了更加灵活的线程池配置方式。
代码示例:
public class ThreadPoolExecutorDemo {public static void main(String[] args) {// 创建一个线程池ThreadPoolExecutor executor = new ThreadPoolExecutor(5, // corePoolSize:核心线程数10, // maximumPoolSize:最大线程数60, // keepAliveTime:空闲线程存活时间TimeUnit.SECONDS, // unit:空闲线程存活时间单位new LinkedBlockingQueue<>(100), // workQueue:任务队列new ThreadPoolExecutor.AbortPolicy() // handler:任务拒绝策略);// 提交任务到线程池for (int i = 0; i < 10; i++) {executor.execute(() -> {// 模拟任务执行逻辑System.out.println("线程池中的线程正在执行任务...");});}// 关闭线程池executor.shutdown();}
}
6. 线程池---参数详解(创建线程池对象的参数)
-
corePoolSize: 核心线程数,即使线程池处于空闲状态,也始终保持核心线程数数量的线程存活。
-
maximumPoolSize: 最大线程数,当任务队列已满且线程池中线程数量小于最大线程数时,会创建新的线程来处理任务,直到线程数量达到最大线程数。
-
keepAliveTime: 空闲线程存活时间,当线程池中线程数量超过核心线程数时,如果空闲线程在 keepAliveTime 时间内没有新的任务提交,则会回收该线程。
-
unit: 空闲线程存活时间单位。
-
workQueue: 任务队列,用于存放等待执行的任务。
-
threadFactory: 创建线程工厂,不能为null
-
handler: 任务拒绝策略,当任务队列已满且线程池中线程数量达到最大线程数时,会触发任务拒绝策略。
详解如下:
1.corePoolSize:核心线程的最大值,不能小于0
2.maximumPoolSize:最大线程数,不能小于等于0,maximumPoolSize >= corePoolSize
3.keepAliveTime: 空闲线程最大存活时间,不能小于0
4.unit:时间单位
5.workQueue:任务队列,不能为null
6.threadFactory:创建线程工厂,不能为null
7.handler:任务的拒绝策略,不能为null
代码示例:
public class ThreadPoolExecutorDemo {public static void main(String[] args) {// 创建一个线程池ThreadPoolExecutor executor = new ThreadPoolExecutor(5, // corePoolSize:核心线程数10, // maximumPoolSize:最大线程数60, // keepAliveTime:空闲线程存活时间TimeUnit.SECONDS, // unit:空闲线程存活时间单位new LinkedBlockingQueue<>(100), // workQueue:任务队列new ThreadPoolExecutor.AbortPolicy() // handler:任务拒绝策略);// 提交任务到线程池for (int i = 0; i < 10; i++) {executor.execute(() -> {// 模拟任务执行逻辑System.out.println("线程池中的线程正在执行任务...");});}// 关闭线程池executor.shutdown();}
}
7. 线程池---非默认任务拒绝策略
当任务队列已满且线程池中线程数量达到最大线程数时,会触发任务拒绝策略。ThreadPoolExecutor 类提供了一些默认的任务拒绝策略,也可以自定义任务拒绝策略。
-
AbortPolicy: 抛出 RejectedExecutionException 异常,这是默认的拒绝策略。
-
CallerRunsPolicy: 由提交任务的线程执行该任务,可以防止线程池被阻塞。
-
DiscardPolicy: 直接丢弃任务,不做任何处理。
-
DiscardOldestPolicy: 丢弃队列中最老的任务,然后尝试重新提交当前任务。
代码示例:
// 使用 CallerRunsPolicy 作为拒绝策略
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),new ThreadPoolExecutor.CallerRunsPolicy()
);// 使用自定义拒绝策略
class MyRejectionHandler implements RejectedExecutionHandler {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println("任务被拒绝,执行自定义处理逻辑...");}
}ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),new MyRejectionHandler()
);
小案例: 使用线程池下载多个文件
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolDownload {public static void main(String[] args) {// 创建一个固定线程数为 5 的线程池ExecutorService executor = Executors.newFixedThreadPool(5);// 要下载的文件 URLString[] fileUrls = {"https://www.example.com/file1.zip","https://www.example.com/file2.txt","https://www.example.com/file3.jpg","https://www.example.com/file4.pdf","https://www.example.com/file5.mp4"};// 提交下载任务到线程池for (String fileUrl : fileUrls) {executor.execute(() -> downloadFile(fileUrl));}// 关闭线程池executor.shutdown();}// 下载文件private static void downloadFile(String fileUrl) {try {URL url = new URL(fileUrl);ReadableByteChannel rbc = Channels.newChannel(url.openStream());FileOutputStream fos = new FileOutputStream(fileUrl.substring(fileUrl.lastIndexOf('/') + 1));fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);fos.close();rbc.close();System.out.println("下载成功:" + fileUrl);} catch (IOException e) {System.err.println("下载失败:" + fileUrl + " - " + e.getMessage());}}
}
总结:
本文详细介绍了 Java 线程池的基础知识,从线程状态、线程池的基本原理、Executors默认线程池、自定义线程池的创建、ThreadPoolExecutor的参数详解,以及非默认任务拒绝策略等方面进行了深入讲解。最后,通过一个小案例展示了如何使用线程池来提高程序效率。希望本文能够帮助各位看官更好地理解和运用 Java 线程池,从而提升代码效率和性能。感谢各位看官的观看,下期见,谢谢~