线程池是一个可以复用线程的技术。当需要创建的线程对象的数量多时,我们就可以使用线程池进行线程复用节约资源。
创建线程池
方式一:
通过ThreadPoolExecutor创建线程池
ExecutorService pool = new ThreadPoolExecutor(对应参数);
ThreadPoolExecutor中共有七个参数
参数一:corePoolSize:指定线程池的核心线程数量
参数二:maximumPoolSize:指定线程池的最大线程数量
参数三:keepAliveTime:指定临时线程的存活时间
参数四:unit:指定临时线程的存活时间单位(秒,分,时,天)
参数五:workQueue:指定线程池的任务队列(核心线程满了排队的地方)
参数六:threadFactory:指定线程池的线程工厂
参数七:handler:指定线程池的任务拒绝策略(工作的线程到达最大线程数量且任务队列也排满时,对新来的线程的拒绝策略)
示例
/**
* corePoolSize:核心池的核心线程数量
* maximumPoolSize:线程池能够容纳同时执行的最大线程数量
* keepAliveTime:非核心线程的闲置最多时长
* unit:keepAliveTime参数的时间单位
* workQueue:任务队列,用于存放等待执行的任务(ArrayBlockingQueue基于数组、LinkedBlockingQueue基于链表)
* threadFactory:线程工厂,用于创建线程
* handler:拒绝策略,当线程池中的线程数量达到上限时,再有新任务来时,会执行拒绝策略(默认抛出异常AbortPolicy)
*/
ExecutorService pool = new ThreadPoolExecutor(3,5,10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
任务拒绝策略
ThreadPoolExecutor.AbortPolicy() 丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy() 丢弃任务,但不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy() 抛弃队列中等待时间最久(最早进入排队)的任务,然后把当前任务加入队列
ThreadPoolExecutor.CallerRunsPolicy() 由主线程负责调用任务的run()方法从而绕过线程池进行。
临时线程的创建策略
当核心线程全在工作中并且任务队列排队排满且线程数量没有到达最大线程数量时,线程工厂会开始创建临时线程。
核心线程和临时线程都在忙,任务队列也满了,此时开始拒绝任务。
线程池处理Runnable任务
语法
线程池对象名.execute(Runnable对象名)
关闭线程池
线程池对象名.shutdown() 线程池不再接受新的任务,但会继续处理已提交的任务
线程池对象名.shutdownNow() 立即关闭,不管已提交的任务是否完成,会强制中断任务
package ExecutorServiceDemo;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++)
{
System.out.println(Thread.currentThread().getName()+"子线程输出:"+i);
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
package ExecutorServiceDemo;
import java.util.concurrent.*;
public class ExecutorServiceDemo1 {
public static void main(String[] args) {
//目标:创造线程池对象来使用
//1、使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
/**
* corePoolSize:核心池的核心线程数量
* maximumPoolSize:线程池能够容纳同时执行的最大线程数量
* keepAliveTime:非核心线程的闲置最多时长
* unit:keepAliveTime参数的时间单位
* workQueue:任务队列,用于存放等待执行的任务(ArrayBlockingQueue基于数组、LinkedBlockingQueue基于链表)
* threadFactory:线程工厂,用于创建线程
* handler:拒绝策略,当线程池中的线程数量达到上限时,再有新任务来时,会执行拒绝策略(默认抛出异常AbortPolicy)
*/
ExecutorService pool = new ThreadPoolExecutor(3,5,10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
//2、使用线程池对象来执行Runnable任务,看看会不会复用线程
Runnable task = new MyRunnable(); //一个Runnable可以给多个线程池多用
pool.execute(task); //提交第一个任务 创建第一个线程 自动启动线程处理这个任务
pool.execute(task); //提交第二个任务 创建第二个线程 自动启动线程处理这个任务
pool.execute(task); //提交第三个任务 创建第三个线程 自动启动线程处理这个任务
pool.execute(task); //复用线程(3个核心线程)
pool.execute(task); //复用线程
pool.execute(task);
pool.execute(task);
pool.execute(task);
//关闭线程池,等所有任务执行完毕关闭(一般不关)
//pool.shutdown(); //线程池不再接受新的任务,但会继续处理已经提交的任务
pool.shutdownNow();//立即关闭,不管线程池中的任务是否执行完毕,都会中断正在执行的任务并返回正在执行的任务列表
}
}
线程池处理Callable任务
线程池对象名.submit(Callable任务对象)
这个方法会返回一个未来任务对象用于获取线程的执行结果。
package ExecutorServiceDemo;
import java.util.concurrent.Callable;
//定义一个Callable接口的实现类
public class MyCallable implements Callable<String> { //<>内的类型就是call方法的返回值类型
private int n;
public MyCallable(int n) {
this.n = n;
}
//实现call方法,定义线程执行体
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return Thread.currentThread().getName()+"子线程计算1-"+n+"的和为:"+sum;
}
}
package ExecutorServiceDemo;
import java.util.concurrent.*;
public class ExecutorServiceDemo2 {
public static void main(String[] args) {
//目标:创造线程池对象来使用
//1、使用线程池的实现类ThreadPoolExecutor声明七个参数来创建线程池对象
/**
* corePoolSize:核心池的核心线程数量
* maximumPoolSize:线程池能够容纳同时执行的最大线程数量
* keepAliveTime:非核心线程的闲置最多时长
* unit:keepAliveTime参数的时间单位
* workQueue:任务队列,用于存放等待执行的任务(ArrayBlockingQueue基于数组、LinkedBlockingQueue基于链表)
* threadFactory:线程工厂,用于创建线程
* handler:拒绝策略,当线程池中的线程数量达到上限时,再有新任务来时,会执行拒绝策略(默认抛出异常AbortPolicy)
*/
ExecutorService pool = new ThreadPoolExecutor(3,5,10, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
//使用线程池处理Callable任务
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
Future<String> f3 = pool.submit(new MyCallable(300));
Future<String> f4 = pool.submit(new MyCallable(400));
try {
System.out.println(f1.get());
System.out.println(f2.get());
System.out.println(f3.get());
System.out.println(f4.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
方式二(不推荐):通过Executors创建线程池
固定大小的线程池
创建一个固定大小的线程池,线程池中的线程数量始终保持在指定的nThreads
。如果所有线程都在工作,新任务会进入队列等待。创建一个固定大小的线程池,线程池中的线程数量始终保持在指定的nThreads
。如果所有线程都在工作,新任务会进入队列等待。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
单线程的线程池
创建一个只有一个线程的线程池。所有任务按顺序执行,保证任务的顺序性。
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
可缓存的线程池
创建一个可缓存的线程池。线程池中的线程数量会根据任务的数量动态调整,空闲线程会被回收,适合执行大量短时间的任务。
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
定时任务线程池
创建一个支持定时及周期性任务执行的线程池,适合需要延迟执行或周期性执行的任务。
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
虽然这样创建线程池很方便,但开发中基本不用,因为大型并发环境中使用Executors如果不注意可能出现系统风险,例如固定大小的线程池的任务队列可以无限排,如果涌入1亿用户会让服务器直接崩了,会有资源耗尽的风险。
线程池大小设定规范
IO密集型
对于IO密集型(如网络请求,磁盘读写等)任务,线程可能大部分时间都在等待IO操作完成,因此可以设定更多线程来充分利用CPU等待时间。
CPU密集型
对于CPU密集型(计算密集型)任务,线程池大小通常设置为与系统可用处理器的数量相等或稍多一点,以避免过多的上下文切换和减少线程调度的开销。
例如核心线程数量等于系统可用处理器数量,最大线程数量等于核心线程数量或核心线程数量+1
并发
进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。
并行
在同一时刻上,同时有多个线程在被CPU调度执行。