背景
先看一个现象,我们在创建线程池执行线程的时候,经常会写到如下的代码:
//new一个线程池对象
ThreadPoolExecutor executor=new ThreadPoolExecutor(2, 4, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t=new Thread(r);
t.setName("abcdefg");
return t;
}
},new ThreadPoolExecutor.AbortPolicy());
//执行一个runnable对象
executor.execute(()->{
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
});
我们关注下ThreadPoolExecutor.execute这个方法之后,会发现,他这里会将我们传给他的这个Runnable对象包装成一个Worker对象,但其实最终执行的当然还是我们传进去的这个Runnable的run()方法,就像下面这样:
//线程池执行execute方法的时候会调用这个addWorker方法
private boolean addWorker(Runnable firstTask, boolean core) {
//这里主要是做一些判断,然后把表示当前正在运行的线程数的计数器加1
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//新建了一个worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//启动这个新创建的线程
//t.start()->worker.run()->runWorker(this)->我们自己写的run方法的内容
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
//这个runWorker方法是线程池在启动一个线程的时候所调用的
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//最终执行的还是我们自己传递进去的run方法的内容
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();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
看了上面的几段源码之后,我们大概知道了线程池是怎么创建线程去工作的了。。。
但我们接下来要说的就和Condition的await方法signal方法关系密切了。。
我们知道线程池中使用了一个BlockingQueue(阻塞队列)来解决了当新添加进来Runnable之时核心线程满了的问题,这个时候就把新添加进来的这个Runnable对象添加到BlockingQueue中记录起来。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//不能再添加核心线程了,那么放到阻塞队列中去保存起来
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//重新检查一下,如果这个时候线程池不再是运行中状态了,就把这个任务从队列中删掉
if (! isRunning(recheck) && remove(command))
//如果删除成功,就执行拒绝策略
reject(command);
//两种情况:1.线程池还处于运行中状态 2.线程池不运行了,但是这个任务删除失败了(似乎一般不会有这种删除失败的情况。。)
else if (workerCountOf(recheck) == 0)
//这个时候,如果正巧当前正在工作的线程数为0,那么就意味着没有线程处理这个runnable任务了,那么就创建一个非核心线程,去执行阻塞队列中的任务
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
而addWorker方法中,会开辟一个新的、任务内容为空的线程,再调用getTask()方法去BlockingQueue中去获取一个Runnable对象去执行,而这些Runnable对象就是之前因为核心线程满了而被offer进去的。其中getTask方法是这样执行的:
while (task != null || (task = getTask()) != null)
这个方法会阻塞:
private Runnable getTask() {
//是否超时,指的是获取Runnable对象这个行为
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//如果线程池状态是结束等状态或者阻塞队列为空,则返回null
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
//如果阻塞队列是空的,那么工作线程数减1
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
别的先不用管,主要看这个方法:ArrayBlockingQueue.take()
(因为我这里用的是ArrayBlockingQueue来举个例子,不用纠结别的类型的BlockingQueue)
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
我们看到如果count==0的话,说明阻塞队列中没有runnable对象,那么就会调用await方法阻塞在那里,一直到队列中有元素为止。
此处,终于到了我们接下来要研究的Condition的await和signal方法了:
我们针对这两个问题做一个解答:
1.await方法为什么会阻塞?
2.什么时候接触阻塞状态?
我们可以将这个Condition对象想象成为一个防错机制,或者说是一个检查机制。首先我们通过
final ReentrantLock lock = this.lock;
获得了锁对象,然后调用lock方法(这里先不管是否这把锁能够被打断)进入锁的内部,这一步只是为了防止在这把锁释放之前,运行的代码段是必须是线程安全的。而这时,还需要第二层判断那就是BlockingQueue里面必须要有可以取出来的元素,也就是Runnable对象,如果没有就一直等在那里直到等到有一个runnable对象。所以这里调用了
notEmpty.await();
方法(等待元素非空)。而相对应的,总有另一个地方需要去通知这些被阻塞的线程,告诉他们:你们可以再去做新的判断非空的尝试了。所以在ArrayBlockingQueue.enqueue方法中我们看到了
private void enqueue(E e) {
// assert lock.isHeldByCurrentThread();
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = e;
if (++putIndex == items.length) putIndex = 0;
count++;
//最后,有一成功添加了一个Runnable对象,所以此时大概率会使由于元素为空而被阻塞的线程可以解除阻塞,因此signal通知他们解除他们的阻塞状态,做下一次的元素是非为空的判断了
notEmpty.signal();
}
至此,上面提出的两个问题都可以找到结论了。
最后贴一段《java核心技术》一书中对于Condition对象的await和signal方法的描述:
通常, 线程进人临界区,却发现在某一条件满足之后它才能执行。要使用一个条件对
象来管理那些已经获得了一个锁但是却不能做有用工作的线程。在这一节里, 我们介绍
Java 库中条件对象的实现。(由于历史的原因, 条件对象经常被称为条件变量( conditional
variable )。)
。。。。这里书中给出了一个银行账户之间转账的例子。。。。
其中转账操作调用了transfer方法,而sufficientFunds是一个条件对象,代表着“有充足金额进行转账”的条件
如果 transfer 方法发现余额不足,它调用
sufficientFunds.await();
当前线程现在被阻塞了,并放弃了锁。我们希望这样可以使得另一个线程可以进行增加
账户余额的操作。
等待获得锁的线程和调用 await 方法的线程存在本质上的不同。一旦一个线程调用 await
方法, 它进人该条件的等待集。当锁可用时,该线程不能马上解除阻塞。相反,它处于阻塞
状态,直到另一个线程调用同一条件上的 signalAll 方法时为止。
这一调用重新激活因为这一条件而等待的所有线程。当这些线程从等待集当中移出时,
它们再次成为可运行的,调度器将再次激活它们。同时, 它们将试图重新进人该对象。一旦
锁成为可用的,它们中的某个将从 await 调用返回, 获得该锁并从被阻塞的地方继续执行。
此时, 线程应该再次测试该条件。 由于无法确保该条件被满足—signalAll 方法仅仅是
通知正在等待的线程:此时有可能已经满足条件, 值得再次去检测该条件。