一、线程池介绍
线程池(Thread Pool)是一种基于多线程处理的服务器架构,它预先创建并维护一组线程,用于处理异步任务或并发请求。线程池的设计目的是减少创建和销毁线程的开销,提高系统的响应速度和吞吐量。
(一)线程池的主要核心原理
- 创建一个池子,池子中是空的。
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子。下次再次提交任务时,不需要创建新的线程,直接复用已有的线程即可。
- 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待。
二、线程池代码实现
实现步骤:
- 创建线程池
- 提交任务
- 所有的任务全部执行完毕,关闭线程池
Excutors:线程池的工具类通过调用方法返回不同类型的线程池对象。
public static ExecutorService newCachedThreadPool(); 创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool(int nThreads); 创建有上限的线程池
(一)创建只有一个线程的线程池(了解)
private static void demo1() {// 创建只有一个线程的线程池ExecutorService executorService = Executors.newSingleThreadExecutor();// 不能并发 假设有10个任务,只有一个执行,其他9个都在等待for (int i = 0; i < 10; i++) {executorService.submit(()-> System.out.println(Thread.currentThread().getName() + "执行了"));}// 异步任务执行完之后关闭线程池
// executorService.shutdown();// 立即关闭线程池executorService.shutdownNow();}
运行结果:
(二)指定线程池中线程的数量(了解)
private static void demo2() {// 指定线程池中线程的数量ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < 20; i++) {executorService.submit(() -> System.out.println(Thread.currentThread().getName() + "执行了"));}}
运行结果:无论提交多少个新任务,只会创建指定线程数
(三)创造一个可以伸缩的线程池对象
private static void demo3() {ExecutorService executorService = Executors.newCachedThreadPool();for (int i = 0; i < 10; i++) {executorService.submit(() -> System.out.println(Thread.currentThread().getName() + "执行了"));}
}
运行结果:
(四)创建一个可以延迟的线程池对象
private static void demo4() {// 创建一个可以延迟的线程池对象ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);long start = System.currentTimeMillis();scheduledExecutorService.schedule(() -> {long end = System.currentTimeMillis();System.out.println(end-start); // 5018System.out.println("hello Thread");}, 5, TimeUnit.SECONDS);
}
运行结果:
三、自定义线程池——ThreadPoolExecutor
(一)自定义线程池的七个参数
参数 | 参数解释 | 参数范围 |
---|---|---|
int corePoolSize | 核心线程数量 | 不能小于0 |
int maximumPoolSize | 最大线程数量 | 不能小于等于0,最大数量>=核心线程数 |
long keepAliveTime | 在指定的时间内回收线程 | 不能小于0 |
TimeUnit unit | 时间单位 | 用TimeUnit指定 |
BlockingQueue<Runnable> workQueue | 任务队列 | 不能为null 最大线程数中排满了,多余的队列就会在任务队列中排队等待 实现类: ArrayBlockingQueue 有界队列 LinkedBlockingQueue 无界队列 PriorityBlockingQueue 优先队列 |
ThreadFactory threadFactory | 创建线程工厂 | 不能为null 传入Executors.defaultThreadFactory() |
RejectedExecutionHandler handler | 任务的拒绝策略 | 不能为null 当线程池和任务队列都满了的时候的拒绝策略,该策略在主线程执行 默认传入: new ThreadPoolExecutor.AbortPolicy() |
1.自定义线程池任务拒绝策略
设置核心线程数为3,最大线程数为6,队列长度为3:
提交3个任务:此时线程池中就会创建3个线程来处理这3个任务;
提交5个任务:此时线程池中会创建3个线程来处理3个任务,剩余的2个任务就会在任务队列中排队等待,等有了空余的线程,后面2个任务才会被执行;
提交8个任务:此时线程池中会创建3个线程来处理3个任务,后面3个任务就会在队列中排队等待,线程池创建临时线程处理剩下2个任务;
因此,创建临时线程的条件:核心线程都在运行,任务队列中已经排满了,才会创建临时线程处理后面的任务。
任务在执行的时候,并不一定会按照提交的顺序来执行,先提交的任务不一定先执行。
提交10个任务:此时提交的任务数量已经超过了最大线程数+队列长度。处理方案:线程池中会创建3个线程来处理3个任务,有3个任务在队列中等待,线程池中会创建3个临时线程处理3个任务,剩下1个任务就会触发任务的拒绝策略。
2.代码实现线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 100, 5,TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
// 执行
threadPoolExecutor.execute(() -> System.out.println(Thread.currentThread().getName() + "执行了"));
(二)封装线程池
线程池在整个项目中只有一个,所以可以将线程池与单例模式相结合。
public class ThreadPoolUtil {private ThreadPoolUtil() {}private static final ThreadPoolUtil THREAD_POOL_UTIL = new ThreadPoolUtil();private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR;private static final ScheduledThreadPoolExecutor SCHEDULED_THREAD_POOL_EXECUTOR;static {THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(10, 100, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());SCHEDULED_THREAD_POOL_EXECUTOR = new ScheduledThreadPoolExecutor(5, Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());}public static ThreadPoolUtil getInstance() {return THREAD_POOL_UTIL;}// 异步执行public void execute(Runnable runnable) {THREAD_POOL_EXECUTOR.execute(runnable);}// 延迟执行public void delay(Runnable runnable, long time, TimeUnit timeUnit) {SCHEDULED_THREAD_POOL_EXECUTOR.schedule(runnable, time, timeUnit);}// 传入Callable接口的实现类public <V> void task(Callable<V> callable) {THREAD_POOL_EXECUTOR.submit(callable);}// 停止线程public void release() {THREAD_POOL_EXECUTOR.shutdown();}public void releaseNow() {THREAD_POOL_EXECUTOR.shutdownNow();}public void shutdown() {SCHEDULED_THREAD_POOL_EXECUTOR.shutdown();}public void shutdownNow() {SCHEDULED_THREAD_POOL_EXECUTOR.shutdownNow();}
}
测试封装后的线程池:
public static void main(String[] args) {// 测试封装后的线程池ThreadPoolUtil3.getInstance().execute(() -> System.out.println(Thread.currentThread().getName() + " hello Thread"));
}// pool-1-thread-1 hello Thread
(三)最大并行数
使用代码可以获取当前电脑的最大并行数:
public class MyThreadPoolDemo2 {public static void main(String[] args) {int count = Runtime.getRuntime().availableProcessors();System.out.println(count); // 6}
}
线程池的大小:
计算比较多,读取本地文件和连接数据库的操作比较少,使用:
CPU密集型运算:最大并行数+1
读取本地文件和连接数据库的操作比较多,使用:
I/O密集型运算:最大并行数 * 期望CPU利用率 * (总时间(CPU计算时间+等待时间) / CPU计算时间)
四、ThreadLocal
(一)ThreadLocal基本介绍
每一个线程有一个自己的ThreadLocal,ThreadLocal本质上是线程的一个映射。
ThreadLocal 是 Java 中的一个类,它提供了线程局部变量。每个使用该变量的线程都有其自己的独立初始化变量副本,因此每个线程可以访问到自己的线程局部变量,而不会影响到其他线程中的变量。这在多线程编程中非常有用,因为它可以避免共享资源的同步问题。
ThreadLocal的作用:在线程之间传递数据,即从线程的上游到线程的下游;解决并发安全的问题。
出现线程安全隐患的条件:
- 共享资源
- 写操作
- 多线程(通过加锁,可以破坏多线程,局部变成单线程)
(二)使用ThreadLocal传递数据
public class TestDemo3 {private static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {String str = "hello e";threadLocal.set(str);a();}private static void a() {b();}private static void b() {c();}private static void c() {d();}private static void d() {e();}private static void e() {System.out.println(threadLocal.get());}
}
(三)解决线程安全隐患——破坏共享资源
定义一个打印机类,再定义两个线程,调用打印机,实现论文打印的功能,每个线程打印3行论文,并且一个线程打印完,另一个线程才能打印。
public class TestDemo4 {public static void main(String[] args) {Printer printer = new Printer();new Thread(new GFS(printer)).start();new Thread(new BFM(printer)).start();}
}class Printer {public void print(String str) {System.out.println("打印机在打印" + str);}
}class GFS implements Runnable {private Printer printer;public GFS(Printer printer) {this.printer = printer;}@Overridepublic void run() {printer.print(printer + "高富帅");printer.print(printer + "高富帅");printer.print(printer + "高富帅");}
}class BFM implements Runnable {private Printer printer;public BFM(Printer printer) {this.printer = printer;}@Overridepublic void run() {printer.print(printer + "白富美");printer.print(printer + "白富美");printer.print(printer + "白富美");}
}
运行结果:打印输出是乱序的
使用synchronized锁,使其连续打印:
class GFS implements Runnable {private Printer printer;public GFS(Printer printer) {this.printer = printer;}@Overridepublic void run() {synchronized (printer) {printer.print(printer + "高富帅");printer.print(printer + "高富帅");printer.print(printer + "高富帅");}}
}class BFM implements Runnable {private Printer printer;public BFM(Printer printer) {this.printer = printer;}@Overridepublic void run() {synchronized (printer) {printer.print(printer + "白富美");printer.print(printer + "白富美");printer.print(printer + "白富美");}}
}
运行结果:
加锁是破坏多线程,使其每次只执行一个线程,除了这种方法,我们也可以共享资源:
通过重写ThreadLocal的initialValue方法,使其返回值为指定的值,源码默认为null:
protected T initialValue() {return null;
}
public class TestDemo4 {public static ThreadLocal<Printer> threadLocal = new ThreadLocal<>() {// 匿名内部类,重写initialValue方法@Overrideprotected Printer initialValue() {return new Printer();}};// lambda表达式
// static ThreadLocal<Printer> threadLocal = ThreadLocal.withInitial(() -> new Printer());public static void main(String[] args) {new Thread(new GFS()).start();new Thread(new BFM()).start();}
}class Printer {public void print(String str) {System.out.println("打印机在打印" + str);}
}class GFS implements Runnable {@Overridepublic void run() {Printer printer = TestDemo4.threadLocal.get();printer.print(printer + "高富帅");printer.print(printer + "高富帅");printer.print(printer + "高富帅");}
}class BFM implements Runnable {@Overridepublic void run() {Printer printer = TestDemo4.threadLocal.get();printer.print(printer + "白富美");printer.print(printer + "白富美");printer.print(printer + "白富美");}
}
运行结果:打印虽然是乱序的,但是资源是一个线程一份的,地址值有2个:
ThreadLocal与synchronized锁:
当需要共享资源的时候,例如卖票,就使用synchronzied锁;
当一个线程一份资源的时候,就使用ThreadLocal。