java 并发编程实战 第五天 ThreadPoolExecutor 源码分析

第八章 线程池

很早之前就久仰这个东西的大名,也是想看看他是有多牛掰! 这次应该会配合上源码一起来阅读,追求得到更加深入的体会.

下面就来看看一个线程池到底是怎么实现的?
   
   
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

  • corePoolSize:核心运行的poolSize,也就是当超过这个范围的时候,就需要将新的Thread放入到等待队列中了;
  • maximumPoolSize:一般你用不到,当大于了这个值就会将Thread由一个丢弃处理机制来处理,但是当你发生:newFixedThreadPool的时候,corePoolSize和maximumPoolSize是一样的,而corePoolSize是先执行的,所以他会先被放入等待队列,而不会执行到下面的丢弃处理中,看了后面的代码你就知道了。
  • workQueue:等待队列,当达到corePoolSize的时候,就向该等待队列放入线程信息(默认为一个LinkedBlockingQueue),运行中的队列属性为:workers,为一个HashSet;内部被包装了一层,后面会看到这部分代码。
  • keepAliveTime:默认都是0,当线程没有任务处理后,保持多长时间,cachedPoolSize是默认60s,不推荐使用。
  • threadFactory:是构造Thread的方法,你可以自己去包装和传递,主要实现newThread方法即可;
  • handler:也就是参数maximumPoolSize达到后丢弃处理的方法,java提供了5种丢弃处理的方法,当然你也可以自己弄,主要是要实现接口:RejectedExecutionHandler中的方法:
    public void rejectedExecution(Runnabler, ThreadPoolExecutor e)
    java默认的是使用:AbortPolicy,他的作用是当出现这中情况的时候会抛出一个异常;其余的还包含:
  1. CallerRunsPolicy:如果发现线程池还在运行,就直接运行这个线程
  2. DiscardOldestPolicy:在线程池的等待队列中,将头取出一个抛弃,然后将当前线程放进去。
  3. DiscardPolicy:什么也不做
  4. AbortPolicy:java默认,抛出一个异常:RejectedExecutionException。



下面是一段execute 方法的源码.这里设计得十分的好.瞬间感叹! 为了减少锁范围 再一次大量使用 if else判断
    
    
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//poolSize 是现在池里面的线程量数. corePoolSize 是预定的数量.如果返回true 直接短路后面的方法.减少锁的范围
//如果线程池里面的线程够用的话是直接进入 addIfUnderCorePoolSize 方法中的.
//如果当当前的poolSize > corePoolSize的时候,或线程池已经不是在running状态的时候 就会返回false ,同时跳进判断条件中.
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
//如果线程池正常工作 ,将线程放入 workQueue(BlockingQueue)中,offer() 方法在队列的尾部写入一个对象.
if (runState == RUNNING && workQueue.offer(command)) {
//因为并发的原因,可能在排上队之后线程池就被shutdown了. 这里可以保证了,排上队的任务能够处理的保障.
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
//线程池不是工作状态 或者 工作队列中已经没有办法放入新加入的线程的时候就会来 判断是否大于最大值.
else if (!addIfUnderMaximumPoolSize(command))
//饱和策略问题
reject(command); // is shutdown or saturated
}
}



     
     
//如果小于CorePoolSize 创建一个新的线程.
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 如果当当前的poolSize > corePoolSize的时候,或线程池已经不是在running状态的时候 就会返回false
//runState 一开始没有赋初值,为0 即 RUNNING 状态
if (poolSize < corePoolSize && runState == RUNNING)
//通过ThreadFactory 产生的线程.
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
if (t == null)
return false;
//同时将线程启动起来.
t.start();
return true;
}
// addIfUnderMaximumPoolSize 这个方法跟上面的差不多. 比较的是Maximum 最大的量

execute 整体部分都已经看完了.但是也不知道不清楚,这个线程池到底是怎么实现我想要的两个目标:
第一个就是能够减少 线程过多的开销, 第二个就是减少 线程之间的相互竞争.
从这里开始用了一种神一样的设计方式!!!厉害!厉害!

首先我们看到的是一个addThread 方法.
     
     
private Thread addThread(Runnable firstTask) {
//Worker 是实现了Runnable 接口的类.
Worker w = new Worker(firstTask);
//工厂模式 直接就返回一个帮你设置好参数的线程类
Thread t = threadFactory.newThread(w);
if (t != null) {
w.thread = t;
//workers 是一个hashSet
workers.add(w);
int nt = ++poolSize;
if (nt > largestPoolSize)
largestPoolSize = nt;
}
return t;
}
要清楚worker 也是一个实现了Runnable接口的方法. 然后里面有几个方法值得我们来看.

首先是worker 里面的重写的run 方法.
      
      
public void run() {
try {
               //firstTask 是用户传进来的一个任务.
Runnable task = firstTask;
firstTask = null;
                  //1.这里的判断又是牛逼 这里要分情况,如果是用户自己传进来的任务, task!=null 返回 true 直接短路 后面不看 运行 runTask(..) 方法.
                  //2.如果第一次的完了 这里开始就神奇了.这个worker线程不是直接挂了.而是继续带着.在while 循环里面 用 getTask() 来获取最新的任务
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}

      
      
private void runTask(Runnable task) {
final ReentrantLock runLock = this.runLock;
runLock.lock();
try {
if (runState < STOP && Thread . interrupted () && runState >= STOP )
thread.interrupt();
boolean ran = false ;
                  //这个方法是空的,可以通过继承重写,来实现一些功能
beforeExecute(thread, task);
try {
                       //在这里可以看到传进来的task 不是新启一个线程运行start 方法,而是直接用run 方法.也因为如此导致无法获得自己线程的状态.
task.run();
ran = true;
                         //这个方法是空的,可以通过继承重写,来实现一些功能
afterExecute(task, null);
++completedTasks;
} catch (RuntimeException ex) {
if (!ran)
afterExecute(task, ex);
throw ex;
}
} finally {
runLock.unlock();
}
}

在看getTask 之前可能要补充一下简单的 BlockingQueue中的知识  干货不怕多吃点!!!
           http://blog.csdn.net/vking_wang/article/details/9952063

      
      
Runnable getTask() {
for (;;) {
try {
int state = runState;
if (state > SHUTDOWN)
return null;
Runnable r;
if (state == SHUTDOWN) // Help drain queue
r = workQueue.poll();
                 //这个判断条件就是看看需不需要重复使用现在这个线程来执行下一个任务,简单除暴的用了poll 来等待着
else if (poolSize > corePoolSize || allowCoreThreadTimeOut)
r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
else
                       //这里也是在队列中阻塞 等待任务来.
r = workQueue.take();
if (r != null)
return r;
if (workerCanExit()) {
if (runState >= SHUTDOWN) // Wake up others
interruptIdleWorkers();
return null;
}
// Else retry
} catch (InterruptedException ie) {
// On interruption, re-check runState
}
}
}


看到这里我已经大概的清楚. 
1.首先当线程池初始化各种参数.
2.用户发送任务过来线程池,先放到工作队列中,如果买了就另行 饱和策略 或者遇到线程池出了别的问题就另作处理.
3.进入工作队列之后就能 通过 Worker 这个实现Runnable 类来 取得任务. Worker 类是一直是在 WorkerQueue.take() /WorkerQueue.poll() 等待工作的来临
4.获得工作的 Worker 在通过   Thread t = threadFactory . newThread ( w );   工厂来配置线程.
5.最后外部启动start()执行worker 内部的 覆盖的run 方法.然后回到第三步. ----->workerDone ( this );





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值