一、为什么要有线程池的概念
每次请求都新创建一个线程的话实现起来非常简便,但是存在一个问题:如果并发的请求数量非常多,但每个线程执行的时间很短,这样就会频繁的创建和销毁线程,如此一来会大大降低系统的效率。
因此,单个任务处理时间比较短,需要处理的任务数量很大的后就需要使用线程池了。
二、使用线程池有什么好处
1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3.提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低 系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的类图
Paste_Image.pngExecutor:一个运行新任务的简单接口;
Executor Service:扩展了Executor接口。添加了一些用来管理执行器生命周期和任务生命周期的方法;
Scheduled Executor Service:扩展了Executor Service。支持Future和定期执行任务。
三、浅谈线程池
Thread Pool Executor继承自Abstract Executor Service,也是实现了Executor Service接口。
runStateOf:获取运行状态;
workerCountOf:获取活动线程数;
ctlOf:获取运行状态和活动线程数的值。
...
private final Atomic Integer ctl = new Atomic Integer(ctl Of(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
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;
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
...
//ctl是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它包含两部分的
//信息: 线程池的运行状态 (run State) 和线程池内有效线程的数量 (worker Count),
//能接受新提交的任务,并且也能处理阻塞队列中的任务;
//关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池
//处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态。(finalize() 方法在
//执行过程中也会调用shutdown()方法进入该状态);
//不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 //RUNNING 或 SHUTDOWN 状态时,调用 shutdown Now() 方法会使线程池进入到该状态;
//如果所有的任务都已终止了,worker Count (有效线程数) 为0,线程池进入该状态后会调用 //terminated() 方法进入TERMINATED 状态。
//在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。
下面仔细分析一下:
在getTask方法中,如果这时线程池的状态是SHUTDOWN并且workQueue为空,那么就应该返回null来结束这个工作线程,而使线程池进入SHUTDOWN状态需要调用shutdown方法;
shutdown方法会调用interruptIdleWorkers来中断空闲的线程,interruptIdleWorkers持有mainLock,会遍历workers来逐个判断工作线程是否空闲。但getTask方法中没有mainLock;
在getTask中,如果判断当前线程池状态是RUNNING,并且阻塞队列为空,那么会调用workQueue.take()进行阻塞;
如果在判断当前线程池状态是RUNNING后,这时调用了shutdown方法把状态改为了SHUTDOWN,这时如果不进行中断,那么当前的工作线程在调用了workQueue.take()后会一直阻塞而不会被销毁,因为在SHUTDOWN状态下不允许再有新的任务添加到workQueue中,这样一来线程池永远都关闭不了了;
由上可知,shutdown方法与getTask方法(从队列中获取任务时)存在竞态条件;
解决这一问题就需要用到线程的中断,也就是为什么要用interruptIdleWorkers方法。在调用workQueue.take()时,如果发现当前线程在执行之前或者执行期间是中断状态,则会抛出InterruptedException,解除阻塞的状态;
但是要中断工作线程,还要判断工作线程是否是空闲的,如果工作线程正在处理任务,就不应该发生中断;
所以Worker继承自AQS,在工作线程处理任务时会进行lock,interruptIdleWorkers在进行中断时会使用tryLock来判断该工作线程是否正在处理任务,如果tryLock返回true,说明该工作线程当前未执行任务,这时才可以被中断。
下面就来分析一下interruptIdleWorkers方法。
网友评论