可以以new Thread(()->{线程执行任务}).start();这种方式开启一个线程。当run()运行结束,线程会被GC释放。
在真实环境中,可能需要很多线程来支撑整个应用,当线程数量非常多时,反而会耗尽CPU资源
多线程可以最大力度的发挥多核cpu的计算能力,提高系统的吞吐量。如果我们不对线程加以管理与控制,反而会影响程序性能。线程的开销主要包括:
1、创建与启动线程的开销,创建线程还需要额外的分配栈空间,启动会有线程调度开销;
2、线程销毁的开销
3、线程调度的开销;上下文切换,通过系统的线程调度器调度线程
4、线程总数受限于cpu内核总数
线程池就是有效使用线程的一种常用方式:线程池内部可以预先创建一定数量的工作线程,客户端 代码直接将任务作为 一个对象提交给线程池,线程池将这些任务缓存在工作(阻塞)队列中,线程池的工作线程不断的从阻塞队列中取出任务并执行。
JDK对线程池的支持
JDK提供了一套Exceutor框架,能帮助开发人员
java.util.concurrent Interface Executuor(基本的线程池接口)
方法: void execut(Runnable command) //线程池提交任务
子接口ExecutuorService 继承 Executuor
void shutdown() //关闭线程池-》不再接收新的任务,线程池中已接收的任务是正常执行完毕
<T> Future<T> submit(Callable<T> task)
submit(Runnable command)
实现类 ThreadPoolExecutor 实现ExecutuorService
子接口 ScheduledExcetuorService 继承 ExecutuorService
ScheduledFuture<T> schedule(Runnable command,long delay,TimeUnit unit)//
实现类 ScheduledThreadPoolExecutor 实现ScheduledExcetuorService 并继承了
ThreadPoolExecutor
类Executors 提供若干工厂方法 依赖 ExecutuorService
java
//创建有5个线程大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(5);
//向线程池中提交15个任务
for(int i = 0;i<15;i++){
threadPool.execute(new Runnable(){
@Override
public void run(){
try{
Thread.sleep(3000);//模拟任务执行时长
}catch(InterruptedException e){
e.printStackTrace();
}
});
}
//创建一个有调度功能的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
//在延迟 2 秒后执行任务, schedule( Runnable 任务, 延迟时长, 时间单位)
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "System.currentTimeMillis());
}
}, 2, TimeUnit.SECONDS);
//以固定的频率执行任务,开启任务的时间是固定的, 在 3 秒后执行任务,以后每隔 5秒重新执行一次
/* scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "----在固定频率开启任务--" + System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(3); //睡眠模拟任务执行时间 ,如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 3, 2, TimeUnit.SECONDS);*/
//在上次任务结束后,在固定延迟后再次执行该任务,不管执行任务耗时多长,总是在任务结束
后的 2 秒再次开启新的任务
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "----在固定频率开启任务---" + System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(3); //睡眠模拟任务执行时间 ,如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 3, 2, TimeUnit.SECONDS);
线程池核心部分
查看 Executors工具类中newCachedThreadPool(),newSingleThreadPool(),newFixedTreadPool()y源码
java
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new
SynchronousQueue<Runnable>());
}
/**该线程池在极端情况下,每次提交新的任务都会创建新的线程执行. 适合用来执行大量
耗时短并且提交频繁的任务
**/
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),threadFactory);
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS, new
LinkedBlockingQueue<Runnable>()));
}
// 当不传ThreadFactory 参数时,threadFactory为 Executors.defaultThreadFactory()
//ThreadPoolExecutor 构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable>
workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
底层都是使用ThreadPoolExecutor线程池,都是对ThreadPoolExecutor线程池的封装。
ThreadPoolExecutor的构造方法参数含义:
corePoolSize 指定线程池中核心线程的数量
maximumPoolSize 指定线程池中最大线程数量
keepAliveTime 当线程池中线程的数量超过corePoolSize时,多余的空闲线程的存活市场,即空闲线程在多长时长内销毁
unit 为keepAliveTime时长单位
workQueue 任务(阻塞)队列,把任务提交到该任务(阻塞)队列中等待执行
threadFactory 线程工厂,用于创建线程
handler 拒绝策略 当任务太多来不及处理时,如何拒绝
说明 :
workQueue 工 作 队 列 是 指 提 交 未 执 行 的 任 务 队 列 , 它 是 BlockingQueue 接口的对象 , 仅
用于存储 Runnable 任务 . 根据队列功 能分类, 在 ThreadPoolExecutor 构造方法中可以使用以下几
种阻塞 队列:
1、 直接提交队列 , 由 SynchronousQueue 对象提供 , 该队列没有 容量, 提交给线程池的任务不会被真
实的保存 , 总是将新的任务提交给线程执行, 如果没有空闲线程 , 则尝试创建新的线程 , 如果线程数量已
经达到 maxinumPoolSize 规定的最大值则执行拒绝策略 .
2、有界任务队列 , 由 ArrayBlockingQueue 实 现 , 在创建ArrayBlockingQueue 对象时 , 可以指
定一个容量. 当有任务需要执行时, 如果线程池中线程数小于 corePoolSize 核心线程数则创建新的
线程;如果大于 corePoolSize 核心线程数则加入等待队列 . 如果队列已满则无法加入, 在线程数小于
maxinumPoolSize 指定的最大线程数前提下会创 建 新 的 线 程 来 执 行 , 如 果 线 程 数 大 于
maxinumPoolSize 最大线程数则执行拒绝策略
3、无界任务队列 , 由 LinkedBlockingQueue 对象实现 , 与有界队列相比, 除非系统资源耗尽 , 否则无界
队列不存在任务入队失败的情况. 当有新的任务时 , 在系统线程数小于 corePoolSize 核心线程数则
创建新的线程来执行任务; 当线程池中线程数量大于corePoolSize 核心线程数则把任务加入阻塞队
列
4、 优先任务队列是通过 PriorityBlockingQueue 实现的 , 是带有任 务 优 先 级 的 队 列 , 是 一 个 特
殊 的 无 界 队 列 . 不 管是 ArrayBlockingQueue 队列还是 LinkedBlockingQueue 队列都是按照先
进先出算法处理任务的.在 PriorityBlockingQueue 队列中可以根据任务优先级顺序先后执行.
jdk提供了四种拒绝策略
AbortPolicy 策略 , 会抛出异常(默认)
CallerRunsPolicy 策略 , 只要线程池没关闭 , 会在调用者线程中运行当前被丢弃的任务
DiscardOldestPolicy 将任务队列中最老的任务丢弃 , 尝试再次提交新任务
DiscardPolicy 直接丢弃这个无法处理的任务
Executors 工具类提供的静态方法返回的线程池默认的拒绝策略是AbortPolicy 抛出异常
也可以自定义拒绝策略
ThreadFactory
线程池中的线程 就是 ThreadFactory来的.
ThreadFactory 是一个接口 , 只有一个用来创建线程的方法 :
Thread newThread(Runnable r);
当线程池中需要创建线程时就会调用该方法
监控线程池
ThreadPoolExecutor 提供了一组方法用于监控线程池
int getActiveCount() 获得线程池中当前活动线程的数量
long getCompletedTaskCount() 返回线程池完成任务的数量
int getCorePoolSize() 线程池中核心线程的数量
int getLargestPoolSize() 返回线程池曾经达到的线程的最大数
int getMaximumPoolSize() 返回线程池的最大容量
int getPoolSize() 当前线程池的大小
BlockingQueue<Runnable> getQueue() 返回阻塞队列
long getTaskCount() 返回线程池收到的任务总数
线程池扩展
可以自定义线程池继承ThreadPoolExecutor,重写afterExecute和beforeExecute方法
java
ExecutorService executorService = new ThreadPoolExecutor(10,10,0,TimeUnit.SECONDS,new LinkedBlockingQueue<>()){//内部类
//重写BeforeExecute方法
//重写afterExecute方法
//重写 terminated方法 线程池退出时
}
在监控每个任务的开始和结束时,或者自定义一些其他增强的功能
ThreadPoolExecutor线程池提供了两个方法:
protected void afterExecute(Runnable r,Throwable t)//任务结束(任务异常退出)后执行
protected void beforeExecute(Thread t,Runnable r)//任务前执行
在ThreadPoolExecutor源码中定义了个内部类Worker,ThreadPoolExecutor线程池的工作线
程就是Worker类的实例,Worker类的实例在执行时也会调佣beforeExecute与afterExecute方法
线程池数量
一般来说,线程池大小需要考虑cpu数量,内存大小等因素
线程池大小 = CPU数量*目标cpu的使用率*(1+等待时间与计算时间比)
线程池死锁
如果在线程池中执行的 任务 A 在执行过程中又向线程池提交了任务 B, 任务 B 添加到了线程
池的等待队列中 , 如果任务 A 的结束需要等待任务 B 的执行结果 . 就有可能会出现这种情况 : 线程池
中所有的工作线程都处于等待任务处理结果, 而这些任务在阻塞队列中等待执行, 线程池中没有可以
对阻塞队列中的任务进行处理的线程 , 这种等待会一直持续下去, 从而造成死锁 .
适合给线程池提交相互独立的任务, 而不是彼此依赖的任务 . 对于彼此依赖的任务, 可以考虑分
别提交给不同的线程池来执行 .
线程池异常
在使用 ThreadPoolExecutor 进行 submit 提交任务时,有的任务抛出了异常,但是线程池并没有
进行提示,即线程池把任务中的异常给吃掉了,可以把 submit 提交改为 execute 执行,也可以对
ThreadPoolExecutor线程池进行扩展.对提交的任务(submit 方法)进行包装