1. 为什么需要线程池
1.1 对于操作系统来说
在操作系统创建线程、切换线程状态、终结线程都要进行CPU调度,进行上下文切换——这是一个耗费时间和系统资源的事情。
大多数实际场景中是这样的:处理某一次请求的时间是非常短暂的,但是请求数量是巨大的。这种技术背景下,如果我们为每一个请求都单独创建一个线程,那么物理机的所有资源基本上都被操作系统创建线程、切换线程状态、销毁线程这些操作所占用,用于业务请求处理的资源反而减少了。
所以最理想的处理方式是,将处理请求的线程数量控制在一个范围,既保证后续的请求不会等待太长时间,又保证物理机将足够的资源用于请求处理本身。
另外,一些操作系统是有最大线程数量限制的。当运行的线程数量逼近这个值的时候,操作系统会变得不稳定。这也是我们要限制线程数量的原因
总结来说:① 反复创建线程开销大 ② 过多的线程会占用太多的内存
1.2 对于服务器
服务器应用程序中经常出现的情况是:单个任务处理的时间很短而请求的数目却是巨大的,为每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源要比花在处理实际的用户请求的时间和资源更多
除了创建和销毁线程的开销之外,活动的线程也消耗系统资源(线程的生命周期!)。在一个JVM 里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”。为了防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目。
2. 线程池的基本原理
线程池实际上是一个生产者消费者模型,生产者不断生成任务放入阻塞队列,线程池作为消费者不断从阻塞队列中取出任务执行,所有可以看到,我们的线程池实际是由一个存放线程的wokers集合(消费者)和一个存放任务的workQueue实现的(阻塞队列)
2.1 阻塞队列的实现
class BlockingQueue<T>{
//任务队列
private Deque<T> queue = new ArrayDeque<>();//性能比LinkedList更高
//锁 不能让多个线程同时去把队列头的任务取走
private ReentrantLock lock = new ReentrantLock();
//针对消费的条件变量 没有元素的时候 阻塞等待
private Condition emptyWaitSet = lock.newCondition();
//针对生产者的条件变量 不能无限制的往阻塞队列加
private Condition fullWaitSet = lock.newCondition();
//容量
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public T take(){
//加锁
//如果没有任务就要阻塞等待任务
lock.lock();
try{
while(queue.isEmpty()){
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
}finally {
lock.unlock();
}
}
//带超时的阻塞获取
public T poll(long timeout, TimeUnit timeUnit){
lock.lock();
try{
long nanos = timeUnit.toNanos(timeout);
while(queue.isEmpty()){
try {
if (nanos<=0){//如果等待的时间超时了,直接返回
return null;
}
nanos = emptyWaitSet.awaitNanos(nanos);//返回值是总需要等待的时间-以及等待了的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
}finally {
lock.unlock();
}
}
public void put(T element){
lock.lock();
try {
while(queue.size() == capacity){
try {
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(element);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
}
public int size(){
lock.lock();
try{
return queue.size();
}finally {
lock.unlock();
}
}
}
2.2 线程池的实现
首先是我们刚刚定义的阻塞队列,用来存放生产者生产的线程
注意,生产者传进来的是线程任务,所有可以抽象出来Runnable
private BlockingQueue<Runnable> taskQueue;//任务可以抽象为Runnable
线程池的线程集合wokers
private HashSet wokers = new HashSet();
线程池的线程数
private int coreSize;
执行任务的方法
可以看到我们在任务数没有超过coresize,交给woker对象执行,woker对象是我们自己定义的线程池中的线程,下面会说,目的就是为了让传进来的任务使用线程池中的线程执行
如果现在线程池中的线程都在忙,那么会把任务加到阻塞队列等待
public void execute(Runnable task){
//当任务数没有超过coresize,则交给woker对象执行
//否则加入任务队列暂存
synchronized (wokers){
if(wokers.size()<coreSize){
Woker woker = new Woker(task);
wokers.add(woker);
woker.start();//会调用run方法
}else{
taskQueue.put(task);
}
}
}
我们在ThreadPool里面创造了一个内部类Woker ,这是我们线程池里面一直在运行的线程
我们的任务是要把生产者要运行的任务(线程)使用线程池里的线程运行,而不让他自己新起线程,所有我们在这里对外部传递的任务线程进行了包装,包装成了我们的Woker线程类,在Woker的run方法中调用任务线程的run方法(注意直接调用run方法是不会新启线程的,要调用start)
class Woker extends Thread{
private Runnable task;
public Woker(Runnable task) {
this.task = task;
}
@Override
public void run(){
//如果传来的有task,直接执行
//如果没有 我们想到是想让这个线程不停止去执行,避免上下文切换,去取任务执行任务
while(task!=null||(task= taskQueue.take())!=null){
try{
task.run();
}catch (Exception e){
e.printStackTrace();
}finally {
task = null;
}
}
//上面使用taskQueue.take()并不会移除,因为会在take一直阻塞等待
synchronized (wokers){
wokers.remove(this);
}
}
}
再看上面的run方法取阻塞队列中取任务的时候使用的是take方法,还记不记得在实现阻塞队列的时候,take方法是无时间限制的等待,所有如果任务队列没有任务,我们就会一直等待,程序不会结束
想改变可以使用我们阻塞队列实现的带超时的阻塞获取poll()方法
完整代码
class ThreadPool{
//阻塞队列
private BlockingQueue<Runnable> taskQueue;//任务可以抽象为Runnable
//线程集合
private HashSet wokers = new HashSet();
//规定线程数
private int coreSize;
//超时时间 一旦超过了超时时间还没有任务,可以让线程报废,不然让他一直跑会占用cpu
private long timeout;
private TimeUnit timeUnit;
//执行任务
public void execute(Runnable task){
//当任务数没有超过coresize,则交给woker对象执行
//否则加入任务队列暂存
synchronized (wokers){
if(wokers.size()<coreSize){
Woker woker = new Woker(task);
wokers.add(woker);
woker.start();//会调用run方法
}else{
taskQueue.put(task);
}
}
}
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity) {
this.taskQueue = new BlockingQueue<>(queueCapacity);
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
}
class Woker extends Thread{
private Runnable task;
public Woker(Runnable task) {
this.task = task;
}
@Override
public void run(){
//如果传来的有task,直接执行
//如果没有 我们想到是想让这个线程不停止去执行,避免上下文切换,去取任务执行任务
while(task!=null||(task= taskQueue.take())!=null){
try{
task.run();
}catch (Exception e){
e.printStackTrace();
}finally {
task = null;
}
}
//上面使用taskQueue.take()并不会移除,因为会在take一直阻塞等待
synchronized (wokers){
wokers.remove(this);
}
}
}
}
使用
public class TestPool {
public static void main(String[] args) {
//核心线程数为2
//阻塞队列的容量为10
ThreadPool pool = new ThreadPool(2, 1000, TimeUnit.MILLISECONDS,10);
for(int i = 0; i < 15; i++){
pool.execute(() ->{
//任务
});
}
}
}
2.3 offer增强
上面的代码有个问题,我们把线程池的核心线程数设置为2,阻塞队列的容量为10,再往线程池中添加15个任务,并让每个任务执行的时间足够长
那么可以看到,只会有12个任务运行和加到阻塞队列等待(核心线程数 + 任务队列的容量),而另外的3个任务无法加到阻塞队列,那么主线程就会死等
那他们是在哪里阻塞的呢,再看一下put的代码,可以看到是在put的 fullWaitSet.await()一直等待
public void put(T element){
lock.lock();
try {
while(queue.size() == capacity){
try {
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(element);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
}
我们给阻塞队列添加一个带超时时间的添加方法
//带超时时间的阻塞添加
public boolean offer(T task, long timeout, TimeUnit timeUnit){
lock.lock();
try {
long nanos = timeUnit.toNanos(timeout);
while(queue.size() == capacity){
try {
if(nanos<0){
return false;
}
fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(task);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
return true;
}
我们在队列满了以后可以有多种不同的选择策略
① 死等
② 超时等待,等不到就算啦
③ 让调用者自己执行
④ 让调用者抛出异常
⑤ 让给调用者放弃
我们希望可以自己选择添加的策略
这里可以使用策略模式,把队列满了以后的操作单独抽象出来,以后可以让操作者自己选择,而不是写死在线程池
//拒绝策略
interface RejectPolicy<T>{
void reject(BlockingQueue<T> queue, T task);
}
在BlockingQueue改进一下加入的操作,新增tryPut方法
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
if(queue.size() == capacity) {
rejectPolicy.reject(this, task);
} else { // 有空闲
queue.addLast(task);
emptyWaitSet.signal();
}
} finally {
lock.unlock();
}
}
在线程池中加入rejectPolicy成员变量,并更改构造方法
private RejectPolicy<Runnable> rejectPolicy;
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity, RejectPolicy<Runnable> rejectPolicy) {
this.taskQueue = new BlockingQueue<>(queueCapacity);
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.rejectPolicy = rejectPolicy;
}
改进execute方法,我们就可以通过在构造方法传递进来的测量进行执行
public void execute(Runnable task){
//当任务数没有超过coresize,则交给woker对象执行
//否则加入任务队列暂存
synchronized (wokers){
if(wokers.size()<coreSize){
Woker woker = new Woker(task);
wokers.add(woker);
woker.start();//会调用run方法
}else{
taskQueue.tryPut(rejectPolicy,task);
}
}
}
测试
ThreadPool pool = new ThreadPool(2, 1000, TimeUnit.MILLISECONDS,10,(queue, task) -> {
// 1) 死等
//queue.put(task);
// 2) 带超时等待
//queue.offer(task, 1500, TimeUnit.MILLISECONDS);
// 3) 让调用者放弃任务执行
// log.debug("放弃{}", task);
// 4) 让调用者抛出异常
// throw new RuntimeException("任务执行失败 " + task);
// 5) 让调用者自己执行任务
// task.run();
});
3. 完整代码
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class TestPool {
public static void main(String[] args) {
//核心线程数为2
//阻塞队列的容量为10
ThreadPool pool = new ThreadPool(2, 1000, TimeUnit.MILLISECONDS,10,(queue, task) -> {
// 1) 死等
//queue.put(task);
// 2) 带超时等待
//queue.offer(task, 1500, TimeUnit.MILLISECONDS);
// 3) 让调用者放弃任务执行
// log.debug("放弃{}", task);
// 4) 让调用者抛出异常
// throw new RuntimeException("任务执行失败 " + task);
// 5) 让调用者自己执行任务
// task.run();
});
for(int i = 0; i < 10; i++){
pool.execute(() ->{
//任务
});
}
}
}
class ThreadPool{
private RejectPolicy<Runnable> rejectPolicy;
private BlockingQueue<Runnable> taskQueue;//任务可以抽象为Runnable
//线程集合
private HashSet wokers = new HashSet();
//线程数
private int coreSize;
//超时时间 一旦超过了超时时间还没有任务,可以让线程报废,不然让他一直跑会占用cpu
private long timeout;
private TimeUnit timeUnit;
//执行任务
public void execute(Runnable task){
//当任务数没有超过coresize,则交给woker对象执行
//否则加入任务队列暂存
synchronized (wokers){
if(wokers.size()<coreSize){
Woker woker = new Woker(task);
wokers.add(woker);
woker.start();//会调用run方法
}else{
taskQueue.tryPut(rejectPolicy,task);
}
}
}
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity, RejectPolicy<Runnable> rejectPolicy) {
this.taskQueue = new BlockingQueue<>(queueCapacity);
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.rejectPolicy = rejectPolicy;
}
class Woker extends Thread{
private Runnable task;
public Woker(Runnable task) {
this.task = task;
}
@Override
public void run(){
//如果传来的有task,直接执行
//如果没有 我们想到是想让这个线程不停止去执行,避免上下文切换,去取任务执行任务
while(task!=null||(task= taskQueue.take())!=null){
try{
task.run();
}catch (Exception e){
e.printStackTrace();
}finally {
task = null;
}
}
//上面使用taskQueue.take()并不会移除,因为会在take一直阻塞等待
synchronized (wokers){
wokers.remove(this);
}
}
}
}
class BlockingQueue<T>{
//任务队列
private Deque<T> queue = new ArrayDeque<>();//性能比LinkedList更高
//锁 不能让多个线程同时去把队列头的任务取走
private ReentrantLock lock = new ReentrantLock();
//针对消费的条件变量 没有元素的时候 阻塞等待
private Condition emptyWaitSet = lock.newCondition();
//针对生产者的条件变量 不能无限制的往阻塞队列加
private Condition fullWaitSet = lock.newCondition();
//容量
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public T take(){
//加锁
//如果没有任务就要阻塞等待任务
lock.lock();
try{
while(queue.isEmpty()){
try {
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
}finally {
lock.unlock();
}
}
//带超时的阻塞获取
public T poll(long timeout, TimeUnit timeUnit){
lock.lock();
try{
long nanos = timeUnit.toNanos(timeout);
while(queue.isEmpty()){
try {
if (nanos<=0){//如果等待的时间超时了,直接返回
return null;
}
nanos = emptyWaitSet.awaitNanos(nanos);//返回值是总需要等待的时间-以及等待了的时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
}finally {
lock.unlock();
}
}
public void put(T element){
lock.lock();
try {
while(queue.size() == capacity){
try {
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(element);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
}
//带超时时间的阻塞添加
public boolean offer(T task, long timeout, TimeUnit timeUnit){
lock.lock();
try {
long nanos = timeUnit.toNanos(timeout);
while(queue.size() == capacity){
try {
if(nanos<0){
return false;
}
fullWaitSet.awaitNanos(nanos);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.addLast(task);
emptyWaitSet.signal();
}finally {
lock.unlock();
}
return true;
}
public int size(){
lock.lock();
try{
return queue.size();
}finally {
lock.unlock();
}
}
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
if(queue.size() == capacity) {
rejectPolicy.reject(this, task);
} else { // 有空闲
queue.addLast(task);
emptyWaitSet.signal();
}
} finally {
lock.unlock();
}
}
}
//拒绝策略
interface RejectPolicy<T>{
void reject(BlockingQueue<T> queue, T task);
}