无限制创建线程的不足
1.线程生命周期的开销非常高。线程的创建并不是没有代价的。根据平台的不同,实际的开销也有所不同,但是线程的创建过程都会需要时间,延迟处理的请求,并且需要JVM和操作系统提供一些辅导操作。如果请求的到达率非常高且请求的处理过程是轻量级的,例如大多数服务器应用程序就是这种情况,那么为每个请求创建一个新线程将消耗大量的计算资源。
JVM线程使用的是1:1的线程模型,而内核线程的调用必须让CPU转为内核态,比普通调用更加浪费时间
2.资源消耗。活跃的线程会消耗资源,尤其是内存。如果可运行的线程数量多于可用处理器的数量,那么有些线程将会闲置。大量的空闲的线程会占用许多内存,给垃圾回收器带来压力,而且大量线程在竞争CPU资源时还将产生其他的性能开销。如果你已经拥有足够多的线程使所有cpu保持忙碌状态,那么再创建更多的线程反而会降低性能。
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态,从任务保存到再加载的过程就是一次上下文切换。
3.稳定性。在可创建线程的数量上存在一个限制。这个限制将随着平台的不同而不同,并且受多个因素制约,包括jvm的启动参数、Thread构造函数中请求的栈大小,以及底层操作的限制等。如果破坏这些限制,那么很可能抛出OutOfMemoryError异常,要想从这种错误中恢复过来是非常危险的,更简单的办法是通过构造程序来避免超出这种限制。
-Xms 最小堆内存 -Xmx 最大堆内存
-Xss 设置每个线程的堆栈大小(JAVA线程栈占用的内存是堆外的,不是 Xmx里面的)。JDK5.0以后每个线程堆栈大小为1M
操作系统限制 系统最大可开线程数
总内存 = JAVA内存Xmx+ 其它程序内存P+空闲内存Free
还可开线程数= free/Xss+已开线程数<系统最大可开线程数?free/Xss:系统最大可开线程数-已开线程数
由以上问题看出,无限创建线程的危害,在jdk5以后提供Executor框架有效的为使用者提供了一个可用的方案。
Executor框架
public interface Executor {
void execute(Runnable command);
}
虽然Executor是一个简单的接口,但它却为灵活且强大的异步任务执行框架提供了基础,该框架能够支持多种不同类型的任务执行策略,它提供了一种标准的方法将任务的提交过程与执行过程解耦开来
Executor基于生产者-消费者模式:
提交任务的线程(new Runnable)相当于生产者
执行任务的线程 (ThreadPoolExecutor里的线程)则相当于消费者。
即然是生产者-消费者模式,因此ThreadPoolExecutor的设计必然要考虑下
- 消费者数量控制
- 缓存队列设计,如果满了怎么处理
- 任务的调度策略(FIFO,LIFO,优先级)
ThreadPoolExecutor的构造参数:
corePoolSize:核心线程池的poolSize。
maximumPoolSize:线程池最大大小
workQueue:等待队列。
keepAliveTime:当maximumPoolSize>corePoolSize且没有任务了,maximumPoolSize-corePoolSize的线程将在keepAliveTime时间后被销毁
unit:keepAliveTime 的时间单位
threadFactory:是构造Thread的方法,你可以自己去包装和传递,主要实现newThread方法即可;
handler:无法执行任务处理策略
1、CallerRunsPolicy:如果发现线程池还在运行,就直接运行这个线程
2、DiscardOldestPolicy:在线程池的等待队列中,将头取出一个抛弃,然后将当前线程放进去。
3、DiscardPolicy:什么也不做
4、AbortPolicy:java默认,抛出一个异常:RejectedExecutionException。
从构造参数得知,
corePoolSize,maximumPoolSize,keepAliveTime,unit这几参数是解决第1点。
workQueue,handler,这2参数直接把设计转给程度开发者,巧妙的解决2,3点。
关于线程的销毁策略:
allowCoreThreadTimeOut 当线程<=corePoolSize 是否继续回收, 默认为false.
// 线程的核心逻辑
final void runWorker(Worker w) {
boolean completedAbruptly = true;
try {
// getTask=null
// 1. 当allowCoreThreadTimeOut为false,线程数大于核心线程数任务超时则返回null
// 2. 当allowCoreThreadTimeOut为true,任务超时则返回null
while (task != null || (task = getTask()) != null) {
w.lock();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
// 当 getTask()=null时会执行到这条代码,表示正常结果
// 当 beforeExecute,run, afterExecute抛出异常的时候不会执行代码
completedAbruptly = false;
} finally {
// 1. 当线程异常跳出completedAbruptly=true 手工减数量
// 2. 维护些全局的基本信息
// 3. 通过tryTerminate,使用中Interrupt方式唤醒getTask中的阻塞(getTask已经处理了中断异常)
// 4. tryTerminate这种传递式的唤醒中断,保存shutdown之后每个线程都能关闭。
processWorkerExit(w, completedAbruptly);
}
}
线程池的实战
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
corePoolSize:0
maximumPoolSize:Integer.MAX_VALUE
workQueue:SynchronousQueue(1个一直offer不进去的队列,ThreadPoolExecutor的线程数量就会一直增加)
keepAliveTime:60s
newFixedThreadPool 创建一个nThreads定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
corePoolSize:nThreads
maximumPoolSize:nThreads
workQueue:LinkedBlockingQueue(无限大)
keepAliveTime:0s
newScheduledThreadPool 创建一个nThreads定长线程池,支持定时及周期性任务执行。
corePoolSize:nThreads
maximumPoolSize:Integer.MAX_VALUE
workQueue:DelayedWorkQueue(1个时间到了就会生效任务的队列)
keepAliveTime:0s
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newFixedThreadPool 的nThreads=1;
一.Executor生命周期
1.线程池有运行、关闭、停止、结束四种状态,结束后就会释放所有资源
2.shutdown 与 shutdowNow 分别对应
- 平缓关闭:已经启动的任务全部执行完毕,同时不再接受新的任务
- 立即关闭:取消所有正在执行和未执行的任务
3.检测线程池是否正处于关闭中,使用isShutdown()
描述的是非RUNNING状态,也就是SHUTDOWN/STOP/TERMINATED三种状态
4.检测线程池是否已经关闭使用isTerminated()
描述的是关闭状态,也就是TERMINATED三种状态
5.定时或者永久等待线程池关闭结束使用awaitTermination()操作
shutdown 与 shutdowNow不是阻塞操作,只是发起关闭任务,awaitTermination则是等待到线程isTerminated()
二.任务生命周期
任务定义了7种状态:
- NEW:新建
- COMPLETING:完成
- NORMAL:正常运行
- EXCEPTIONAL:异常退出
- CANCELLED:任务取消
- INTERRUPTING:线程中断中
- INTERRUPTED:线程已中断
在这七种状态中,有四种任务终止状态:NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTED。
以下为7种状态的关系情况
- NEW—>COMPLETING—>NORMAL(任务执行正常)
- NEW—>COMPLETING—>EXCEPTIONAL(任务执行异常)
- NEW—>CANCELLED(等待任务的取消)
- NEW—>INTERRUPTING—>INTERRUPTED(执行中任务的取消)
JAVA中,Future 表示了一个任务的生命周期。
Future.get()
如果任务正常执行,则返回任务结果
如果任务执行异常,则会抛出执行异常
如果任务被取消,则抛出取消异常(末执行任务直接不执行,正在执行任务中断)
中断正在执行的任务
采用interrupt()。对线程设置为中断标志。设置后不会立刻中断线程,而是待调用严格检查异常interruptException的时候就会停止,比如await,sleep等。
然而,并不是所有的阻塞都会响应中断,对于不能响应中断interruptException的阻塞。interrupt方法只能标志位可中断,但是无任何其他的作用。处理不可中断的阻塞 ,我们应该查找阻塞的原因,然后重写interrupt的方法来中断。
1.io包中的socket I/O:阻塞I/O形式为对套接字的read、write操作。虽然read,write操作不会响应中断,但是通过关闭底层的套接字,可以使得由于使用该方法而阻塞的线程抛出socketException
2.io包中的同步I/O
3.selector的异步IO,close或wakeup的时候会使线程抛出CloseSelectorException
4.获得某个锁:如果一个线程因等待某个锁而阻塞,那么该线程是不会理会中断,唯一的办法是使用实现lock接口的reentrantlock(注释3)对象
(具体参考JAVA并发编程实战)
Future的灵活运用
等待一任务超过一定时间则取消它
public static void timeRun(Runnable r, long timeout, TimeUnit unit){
ExecutorService executor = Executors.newSingleThreadExecutor();
Future task = executor.submit(r);
try {
task.get(timeout, unit);
} catch (InterruptedException e) {
//中断取消
} catch (ExecutionException e) {
//执行异常
} catch (TimeoutException e) {
//超时
}finally {
//如果任务超时,则直接取消,结果已没作用
task.cancel(true);
//true 不管任务是否执行,可以中断。false:如果任务还未执行,就不要运行。
}
}