0816(032天 线程/进程03 一点细节+线程池)
每日一狗(田园犬西瓜瓜)
线程/进程03 一点细节+线程池
1. 亿些细节
1.1 Thread
该类实现了Runnable接口,
简化写法
Thread t = new Thread(()->{
System.out.println("左手");
});
构造方法
- Thread()//一般用于在Thread类中覆盖定义run方法,可以使用匿名内部类进行定义
- Thread(Runnable)//使用最多的情况,run方式是由Runnable参数对象提供
- Thread(String name) //自定义线程名称
- Thread(Runnable,String name)
一些方法
方法名 | 干啥的 | 备注 |
---|---|---|
void start() | 告诉线程,你可以开始执行了,执不执行不确定 | 到达可运行状态(就绪) |
void run() | 这个线程是干啥的 | |
void setName(String) | 修改线程名 | |
setPriority(int); | 更改线程优先级(java中1-10,越大越高) | java有10级,但是不同的操作系统不一定支持10种,要用就不要太像,距离拉大,防止56映射到同一优先级中(建议使用1、5、10) |
void setDaemon(boolean) | 设置守护线程,在start之前设置 | 用于为其他提供服务的线程,(打辅助的,AD寄了,奶妈活着也没必要了,进程中的其他线程都是AD),一般情况下这都会用死循环,因为守护线程会自动终止 |
void interrupt() | 中断线程,不是中断线程的执行,而是修改中断参数 | 具体实现是抛了一个异常,然后就啥也没有了,线程还是在运行,终止要你自己实现 |
boolean isAlive() | 测试线程是否处于活动状态,活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的 | |
void join()/(long millis) | 等待改线程终止 | 无参没有时间,就硬等,带参最长时间未millis毫秒,参数为0时代表无限制等待 |
static void yield() | 将线程从 运行 转到 就绪 状态 | |
static void sleep(long millisec) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响 | |
static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
1.2 Runnable接口
Runnable接口只定义了一个方法public void run(),这个方法要求实现Runnable接口的类实现,Runnable对象称为可运行对象,一个线程的运行就是执行该对象的run()方法。
在运行线程的时候不能直接调用run方法,需要用Thread.start()来通知线程,你可以开始执行了,去执行吧!直接调用run方法就想到于是先当前线程执行该方方法一样,和普通方法无异。
1.3 Callable接口
问题:继承Thread或实现Runnable接口这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。 如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
解决问题:call()方法有返回值,这个返回值可以通过泛型进行约束,还允许抛出异常
1.4 Future接口
Future表示一个任务的生命周期,并提供了方法来判断是否已经完成或取消以及获取任务的结果和取消任务等。
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
一些方法
-
cancel 用来关闭一个任务
- 参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务
- 运行中mayInterruptIfRunning为true,则返回true,为false则返回false
- 未运行的和运行完成的直接返回false
-
isCancelled 方法表示任务是否被取消成功
-
isDone 方法表示任务是否已经完成,若任务完成,则返回true
有用的方法
- get() 方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回
- get(long timeout, TimeUnit unit) 用来获取执行结果,如果在指定时间内,还没获取到结果,就抛出TimeoutException超时异常。
Future接口的具体实现类FutureTask
FutureTask实现了RunnableFuture,RunnableFuture又继承了Runnable, Future接口,FutureTask就实现了Runnable和 Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
FutureTask在构造的时候传入Runnable、Callable最后都会变成Callable。
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
具体使用
FutureTask<Integer> future = new FutureTask<Integer>(callable);
new Thread(future).start();
FutureTask是一个可取消的异步计算,FutureTask实现了Future的基本方法,提供start cancel 操作,可以查询计算是否已经完成,并且可以获取计算的结果。结果只可以在计算
完成之后获取,get方法会阻塞当计算没有完成的时候,一旦计算已经完成, 那么计算就
不能再次启动或是取消。
一个FutureTask 可以用来包装一个 Callable 或是一个Runnable对象。因为FurtureTask实现了Runnable方法,所以一个 FutureTask可以提交(submit)给一个Excutor执(excution). 它同时实现了Callable, 所以也可以作为Future得到Callable的返回值。
2. 线程池
ThreadPoolExecutor是线程池框架的一个核心类,线程池通过线程复用机制,并对线程进行统一管理 。
特性
- 降低系统资源消耗。通过复用已存在的线程,降低线程创建和销毁造成的消耗;
- 提高响应速度。当有任务到达时,无需等待新线程的创建便能立即执行;
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗大量系统资源,还会降低系统的稳定性,使用线程池可以进行对线程进行统一的分配、调优和监控。
2.1 线程池的运行状态
- RUNNING: 高3位为111,接受新任务并处理阻塞队列中的任务
- SHUTDOWN: 高3位为000,不接受新任务但会处理阻塞队列中的任务
- STOP: 高3位为001,不会接受新任务,也不会处理阻塞队列中的任务,并且中断正在运行的任务
- TIDYING: 高3位为010,所有任务都已终止,工作线程数量为0,线程池将转化到
- TIDYING状态,即将要执行terminated()结束钩子方法
- TERMINATED: 高3位为011,terminated()方法已经执行结束
// runState 存储在高阶位中
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
2.2 构造器的七个参数
1. corePoolSize
线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。
2.maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize。
3.keepAliveTime
线程空闲时的存活时间。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用allowCoreThreadTimeOut(boolean)方法,keepAliveTime参数也会起作用,直到线程池中的线程数为0。
4.unit
keepAliveTime参数的时间单位。
例如TimeUnit.DAYS
5.workQueue
任务缓存队列,用来存放等待执行的任务。如果当前线程数为corePoolSize,继续提交的
任务就会被保存到任务缓存队列中,等待被执行。
一般来说,这里的BlockingQueue有以下三种选择:
- SynchronousQueue:(栅栏队列,)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。因此,如果线程池中始终没有空闲线程(任务提交的平均速度快于被处理的速度),可能出现无限制的线程增长。
- LinkedBlockingQueue:基于链表结构的阻塞队列,如果不设置初始化容量,其容量Integer.MAX_VALUE,即为无界队列。因此,如果线程池中线程数达到corePoolSize,且始终没有空闲线程(任务提交的平均速度快于被处理的速度),任务缓存队列可能出现无限制的增长。
- ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务。
6.threadFactory
线程工厂,创建新线程时使用的线程工厂。
7.handler
任务拒绝策略,当阻塞队列满了,且线程池中的线程数达到maximumPoolSize,如果继续提交任务,
就会采取任务拒绝策略处理该任务,线程池提供了4种任务拒绝策略:
- AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,默认策略;
- CallerRunsPolicy:由调用execute方法的线程执行该任务;
- DiscardPolicy:丢弃任务,但是不抛出异常;
- DiscardOldestPolicy:丢弃阻塞队列最前面的任务,然后重新尝试执行任务(重复此过程)。当然也可以根据应用场景实现RejectedExecutionHandler接口自定义饱和策略,如记录日志或持久化存储不能处理的任务。
2.3 成熟的池子
缓存池子
特性:newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收重用时则新建线程。
应用:用来创建一个可以无限扩大的线程池,适用于服务器负载较轻,执行很多短期异步任务。
new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
ThreadPoolExecutor 定长池子
创建一个固定大小的定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
应用:因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于可以预测线程数量的业务中,或者服务器负载较重,对当前线程数量进行限制
newScheduledThreadPool 延迟池子
特性:newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行可以延时启动,定时启动的线程池。
应用:适用于需要多个后台线程执行周期任务的场景
newSingleThreadExecutor 单线程池子:保证执行顺序
特性:newSingleThreadExecutor创建一个单线程化的线程池它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO先进先出, LIFO后进先出, 优先级)执行
应用:适用于需要保证顺序执行各个任务,并且在任意时间点,不会有多个线程是活动的场景
newWorkStealingPool 卷卷子线程池:给并行用的(线程数量适合等于cpu核心数)
特性:
- 每个线程都有一个阻塞队列
- 自己没事可干就去偷别线程的任务来执行。
newWorkStealingPool:创建一个拥有多个任务队列的线程池,可以减少连接数创建当前可用cpu数量的线程来并行执行,适用于大耗时的操作,可以并行来执行
2.4 操作池子的一些方法
提交任务
ExecutorService提供提交任务的方式
线程池框架提供了两种方式提交任务,submit()和execute(),
通过submit()方法提交的任务可以返回任务执行的结果, <T> Future<T> submit(Callable<T> task);
通过execute()方法提交的任务不能获取任务执行的结果。 void execute(Runnable command);
关闭池子
ExecutorService的方法:关闭线程池
shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表
shutdown:当调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务
扩展小芝士
- 位运算
3<<3; // 3*2^3=24