##无限制创建线程的缺点
线程生命周期的开销:创建和关闭一个线程会消耗时间
资源消耗量:若可运行线程数大于可用处理器数,则多的线程会空闲,给垃圾回收带来压力
稳定性:创建线程的限制数量,要综合考虑JVM启动参数,每个线程请求栈的大小,以及底层操作系统的线程限制(如华为手机系统限制500)。否则,很容易导致OOM
线程池(Executor)
任务(Task):离散的工作单元,是独立的活动,不依赖其他任务的状态。如java中的Runnable
任务取消:若任务外部代码能够在任务自然完成之前,将其更改为完成状态,则这个任务就是可取消的。
可取消的任务必须有自己的取消策略,包括外部代码如何请求取消,任务自身在何时检查取消请求的到达,以及如何响应取消请求(如通过一个volatile类型变量标志取消,轮训检查该标志)。另外对于阻塞类型的请求任务方法,应该支持线程中断机制来保证任务可取消,在响应中断的策略中,有传递异常或保存中断状态两种方法。
任务不可取消:有些任务虽然可以调用可中断的阻塞方法,但必须在发现中断后进行重试,注意不要引起无限循环。
class PrimeProducer extends Thread {
private final BlockingQueue<BigInteger> queue;
public PrimeProducer(BlockingQueue<BigInteger> queue) {
this.queue = queue;
}
@Override
public void run() {
try {
BigInteger p = BigInteger.ONE;
while (!Thread.currentThread().isInterrupted()) {
queue.put(p = p.nextProbablePrime());
}
} catch (InterruptedException e) {
/**
* 允许线程退出
*/
}
}
public void cancel() {
interrupt();
}
}
Callable:可携带结果的任务
Future:可以获取任务结果,可调用cancel取消任务,已经提交但还未开始执行的任务则不会再执行,如果子任务已经开始执行了,但是还没有执行结束,根据mayInterruptIfRunning的值,如果mayInterruptIfRunning = true,那么会中断执行任务的线程,然后返回true,如果参数为false,会返回true,不会中断执行任务的线程
线程池(Executor) :基于生产者/消费者模式,对任务(Task)进行了抽象和封装,用于异步任务的执行,并支持不同类型的任务执行策略(FIFO,LIFO,优先级),对任务提交和任务执行解耦。
如何定制线程池大小?
线程池的长度应该由配置动态提供,综合考虑CPU数量,计算环境,资源预算等。对于不同类别的任务,可以考虑使用多个线程池,每个线程池可以根据不同任务类型(计算,I/O,混合操作)进行调节。
计算密集型任务(N(cpu)+ 1):确保有一个额外的线程,可以保证CPU处理周期不会中断
I/O等阻塞类型任务:要考虑任务花费在等待的时间和计算的时间比率。内存资源,文件句柄,套接字句柄,数据库连接数等
线程池好处:
1 重用线程:可以重用已存在的线程,而不是创建新的,减少创建,关闭线程的资源开销
2 高响应性:新任务到达时,工作线程已经有了,不会因为需要重新创建线程而延迟新任务的执行
3 稳定性:通过合理调整线程池大小,可以让cpu工作保持稳定,防止过多线程竞争资源
线程池的生命周期:
运行 -> 关闭 -> 终止
线程池关闭方法:
shutdown:平缓的关闭,停止提交新任务,等待完成所有已提交的执行中的任务和尚未执行的任务。
shutdownNow:强制关闭,取消所有运行中的任务和尚未执行的任务
Executors静态工厂方法提供的四种线程池:
newFixedThreadPool:定长线程池。每提交一个任务则创建一个线程,直到最大长度,保持长度不变。
newCachedThreadPool:可缓存的线程池。长度不限制,每当任务增加,则灵活创建新线程,若长度超过了处理需要,则回收空闲线程。
newSingleThreadExecutor: 单线程的串行执行任务,执行策略有FIFO,LIFO,优先级
newScheduleThreaPool:定长线程池,且支持定时和周期性任务执行。
Timer也可以支持定时执行任务,但只创建唯一的线程来执行所有的timer任务,若任务很耗时,则会影响其他timer任务的时效准确性。且若一个timer任务出现异常,将导致所有任务都不能再执行了。因此采用ScheduleThreaPool更好。
ThreadPoolexecutor实现定制化线程池
- 核心池大小:线程池维护的核心线程数,有任务提交时才开始创建
- 最大池大小:若线程池内线程数超过核心池大小,有任务提交时新建线程,但不会超过该数
- 存活时间:若一个线程已经闲置的时间超过了存活时间,则将被回收,若当前超过了核心池大小,则终止它
- 任务队列:一个BlockingQueue来实现任务队列。可以实现为有限队列,无限队列和同步移交
newFixedThreadPool和newSingleThreadExecutor是采用一个无限的LinkBlockingQueue。比较好的方式是采用有限队列(ArrayBlockingQueue,PriorityBlocking)避免资源耗尽,但必须有饱和策略(提交任务时抛出异常/抛弃最低优先级任务,执行新任务等)。
newCachedThreadPool则采用SynchronousQueue,即同步移交的方式.SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等队列中的添加元素被消费后才能继续添加新的元素.
以上的任务队列都是以FIFO顺序执行,也可以通过传入PriorityBlockingQueue(任务实现Comparable)来通过优先级排序执行。