目录
字段state,各种状态常量,callable,outcome,runner,waiters
方法report,2个构造函数,isCancelled,isDone,cancel,2个get
方法done,set,setException,run,runAndReset,handlePossibleCancellationInterrupt
内部类WaitNode,方法finishCompletion,awaitDone,removeWaiter,字段UNSAFE,3个Offset
简介
package java.util.concurrent;
import java.util.concurrent.locks.LockSupport;
/**
* 一种可取消的异步计算。
* 这个类提供了Future的一个基本实现,带有启动和取消计算、查询计算是否完成以及检索计算结果的方法。
* 只有在计算完成后才可以检索结果;
* 如果计算尚未完成,则get方法将阻塞。
* 计算完成后,不能重新启动或取消计算(除非使用runAndReset调用计算)。
*
* <p>FutureTask可用于包装Callable或Runnable的对象。
* 因为FutureTask实现了Runnable,所以可以将FutureTask提交给Executor执行。
*
* <p>除了作为一个独立的类,这个类还提供了受保护的功能,在创建自定义任务类时可能会很有用。
*
* FutureTask整合了Callable对象,使得我们能够异步地获取task执行结果。
* 执行FutureTask.run()的线程就相当于生产者,生产出执行结果给outcome。
*
* 执行FutureTask.get()的线程就相当于消费者,它们会阻塞等待直到执行结果产生。
* 如果生产者线程已经开始执行Callable.call(),那么消费者调用cancel,实际上是无法终止生产者的运行的。
*
* @since 1.5
* @author Doug Lea
* @param <V> The result type returned by this FutureTask's {@code get} methods
*/
public class FutureTask<V> implements RunnableFuture<V>
字段state,各种状态常量,callable,outcome,runner,waiters
/*
* 修订说明:这与依赖于AbstractQueuedSynchronizer的该类的以前版本不同,主要是为了避免在取消竞争期间保留中断状态让用户感到意外。
* 当前设计中的同步控制依赖于通过CAS更新的“state”字段来跟踪完成,以及一个简单的Treiber堆栈来保存等待线程。
*
* 风格注意:像往常一样,我们绕过了使用AtomicXFieldUpdaters的开销,而是直接使用Unsafe的内在特性。
*/
/**
* 这个任务的运行状态,最初是NEW。
* 只有在方法set、setException和cancel中,运行状态才会转变为终端状态。
* 在完成过程中,状态可能呈现为COMPLETING(当结果被设置时)或INTERRUPTING(仅当中断运行以满足cancel(true))的瞬态值。
* 从这些中间状态到最终状态的转换使用更便宜的有序/惰性写入,因为值是唯一的,不能进一步修改。
*
* FutureTask的重点在于对task(Callable.call())的执行的管理,而FutureTask通过一个volatile的int值来管理task的执行状态。
*
* 这种状态的转换都是不可逆的,某些过程中还可能存在中间状态,但这种中间状态存在的时间很短,且马上也会变成相应的最终状态。
* 所以可以认为,只要状态不是NEW的话,就可以认为生产者执行task已经完毕。关于这一点,isDone函数可以替我作证:
*
* 调用这些函数可以使得状态发生转移(具体过程后面讲解):
* set(V v)使得NEW -> COMPLETING -> NORMAL。
* setException(Throwable t)使得NEW -> COMPLETING -> EXCEPTIONAL。
*
* cancel(boolean mayInterruptIfRunning)可能有两种状态转移:
* 当mayInterruptIfRunning为false时,使得NEW -> CANCELLED。
* 当mayInterruptIfRunning为true时,使得NEW -> INTERRUPTING -> INTERRUPTED。
* 注意,图中的“取消”二字打了引号,因为消费者实际上不可能使得正在执行的生产者线程咔嚓终止掉。
*
* 可能的状态变化:
* NEW -> COMPLETING -> NORMAL 正常执行完成
* NEW -> COMPLETING -> EXCEPTIONAL 执行task途中抛出异常,与消费者无关
* NEW -> CANCELLED 消费者取消了task
* NEW -> INTERRUPTING -> INTERRUPTED 消费者取消了task,并且中断了生产者线程
*/
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
/** 底层的调用;运行后为空 */
private Callable<V> callable;
/** 从get()返回的结果或抛出的异常
* outcome是Object类型,可以存任何类型对象。这样既可以存泛型类型V,也可以存异常对象。
* */
private Object outcome; // non-volatile, 受状态读写保护
/** 运行可调用对象的线程;
* 当调用new Thread(FutureTask对象).start()时,生产者线程便创建并开始运行了,
* 并且会在FutureTask#run()的刚开始就把生产者线程存放到runner中。
* */
private volatile Thread runner;
/** Treiber 栈中等待的线程
* 当调用FutureTask对象.get()时,如果task还未执行完毕,
* 当前消费者线程会被包装成一个节点扔到栈中去。
* */
private volatile WaitNode waiters;
方法report,2个构造函数,isCancelled,isDone,cancel,2个get
/**
* 为已完成的任务返回结果或抛出异常。
*
* 最后,我们再回到两个get函数的report(s),分析report()函数之前,先看看这个实参s可能是什么值。
* 首先这个值是awaitDone调用后返回的。
* 如果是无限阻塞地调用awaitDone,那么只可能返回s > COMPLETING的值。
* 如果是超时阻塞地调用awaitDone,虽然可能返回NEW或COMPLETING,但是在get(long timeout, TimeUnit unit)中会马上抛出超时异常的。
* 总之,实参s只能是s > COMPLETING的值。
*
* @param s completed state value
*/
@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {
Object x = outcome;//不管当前outcome是不是null,直接赋值
if (s == NORMAL)//如果状态是NORMAL,则outcome肯定不是null
return (V)x;
if (s >= CANCELLED)//如果状态是取消的状态(这里其实不可能是INTERRUPTING)
throw new CancellationException();
throw new ExecutionException((Throwable)x);//如果状态是EXCEPTIONAL,那么抛出这个异常对象
}
/**
* 创建一个FutureTask,在运行时执行给定的可调用对象。
*
* 两个构造器都保证了初始时状态为NEW。
* 除了可以接受Callable之外,还可以接受Runnable,
* 但也是马上通过适配器模式把Runnable包装成一个Callable而已。
*
* @param callable the callable task
* @throws NullPointerException if the callable is null
*/
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
/**
* 创建一个FutureTask,在运行时执行给定的Runnable,并安排get在成功完成时返回给定的结果。
*
* @param runnable the runnable task
* @param result the result to return on successful completion. If
* you don't need a particular result, consider using
* constructions of the form:
* {@code Future<?> f = new FutureTask<Void>(runnable, null)}
* @throws NullPointerException if the runnable is null
*/
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
public boolean isCancelled() {
return state >= CANCELLED;
}
public boolean isDone() {
return state != NEW;
}
/**
* 之前说过,消费者实际上不可能使得正在执行的生产者线程咔嚓终止掉,接下来将解释。
* 我们先来回顾一下,生产者线程在run函数中,直到执行set或setException之前,
* 都在正常执行task中,而既然没有执行这两个函数,说明这段时间state还是为NEW的。
*
* 而cancel函数执行前提就是state是NEW,在生产者线程执行set或setException之前,都是可以CAS成功的。
*
* 如果消费者是在生产者线程执行run方法的if (c != null && state == NEW)之前就执行了cancel函数,那么才可以终止生产者执行task。
*
* 如果消费者是在生产者线程执行run方法的if (c != null && state == NEW)之后才执行的cancel函数,那么将不能终止生产者。
*
* 如果参数是false,state从NEW修改为CANCELLED。但修改state,并不能使得生产者线程运行终止。
*
* 如果参数是true,state从NEW修改为INTERRUPTING,中断生产者线程后,再修改为INTERRUPTED。
* 我们知道,中断一个正在运行的线程,线程运行状态不会发生变化的,只是会设置一下线程的中断状态。
* 也就是说,这也不能使得生产者线程运行终止。除非生产者线程运行的代码(Callable.call())时刻在检测自己的中断状态。
*
* 那你可能会问,这种情况既然不能真的终止生产者线程,那么这个cancel函数有什么用,其实还是有用的:
*
* 如果参数为true,那么会去中断生产者线程。但生产者线程能否检测到,取决于生产者线程运行的代码(Callable.call())。
* 状态肯定会变成CANCELLED或INTERRUPTED,新来的消费者线程会直接发现,然后在get函数中不去调用awaitDone。
* 对于生产者线程来说,执行task期间不会影响。但最后执行set或setException,会发现这个state,然后不去设置outcome。
* 最后执行了finishCompletion函数,唤醒所有的消费者线程。
*
* 另外,注意,多个消费者来调用cancel函数,最多只有一个能够成功,即返回true。
*/
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
// 在实现中可以看到有时候使用普通写的语义,有时候使用CAS写。
// 比如cancel函数中的UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED)这里使用的普通写,其他线程可能不能马上看到,但这没关系。因为:
// 一来,这个状态转移是唯一的。INTERRUPTING只能变成INTERRUPTED。其他线程暂时看不到 INTERRUPTED 也没关系。
//(注意,暂时看不到 INTERRUPTING,会导致handlePossibleCancellationInterrupt自旋)
// 二来,finishCompletion中也有对其他volatile字段的CAS写操作。这样做会把之前的普通写都刷新到内存中去。
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
// 此函数判断当前state,并根据情况调用awaitDone进行阻塞等待。
// 如果state为NEW,那么生产者还没开始执行呢,肯定得阻塞等待。
// 如果state为COMPLETING,那么生产者线程马上执行完了,
// 并且是 生产者正常执行完的过程(即前两种状态转移),那么也阻塞等待,即使马上会被唤醒。
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
/**
* @throws CancellationException {@inheritDoc}
*/
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
// 而这个函数是get的超时版本,所以调用awaitDone的实参设置不一样。
// 并且在退出awaitDone函数时,要检查返回值。
// 返回值总是state的最新值:
// 如果state为NEW,那么说明返回是因为超时,因为生产者执行task期间state一直都为NEW(直到执行了set或setException才会改变),
// 所以说明返回时,要么生产者还没执行完task,要么生产者根本还没开始执行。
// 如果state为COMPLETING,那么说明生产者即将执行完毕,但还没有设置返回值。虽然运气不好,但也只好算作超时。
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
方法done,set,setException,run,runAndReset,handlePossibleCancellationInterrupt
/**
* 当任务转换到状态isDone时调用的受保护方法(无论是正常还是通过取消)。
* 默认的实现不做任何事情。子类可能会覆盖这个方法来调用补全回调或执行bookkeeping。
* 注意,您可以在此方法的实现中查询状态,以确定此任务是否已取消。
*/
protected void done() { }
/**
* 将该future的结果设置为给定的值,除非该future已经被设置或取消。
*
* <p>此方法在计算成功完成后由run方法在内部调用。
*
* 如果顺利执行完task,outcome会被赋值为执行结果。
* 另外,状态转移是NEW -> COMPLETING -> NORMAL的过程,并且这个中间状态是只存在很短的时间的。
*
* @param v the value
*/
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
/**
* 使这个future以给定的throwable作为它的原因来报告一个ExecutionException,
* 除非这个future已经被设置或者被取消了。
*
* <p>此方法在计算失败时由run方法内部调用。
*
* 如果执行task途中抛出异常,outcome会被赋值为抛出的异常对象。
* 另外,状态转移是NEW -> COMPLETING -> EXCEPTIONAL的过程,并且这个中间状态是只存在很短的时间的。
*
* @param t the cause of failure
*/
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
public void run() {
// 在函数的一开始,需要检测state是否为NEW,且当前线程对象需要占领runner,并且在退出run函数之前,一直都会占领着runner。
if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
return;
//执行到这里,说明此时state为NEW,且生产者位置已经被当前线程占领
try {
Callable<V> c = callable;
if (c != null && state == NEW) { //直到这里,还会检查一次state是否为NEW
// 如果为NEW,就开始执行task。生产者自己执行task时(c.call()),有两种情况:
// 顺利执行完task,然后调用set(result)。
// 执行task途中抛出异常,然后调用setException(ex)。
V result;
boolean ran;
try {
result = c.call(); // 执行task
ran = true;
} catch (Throwable ex) { // 生产者自身执行task过程中,抛出了异常
result = null;
ran = false;
setException(ex);
}
if (ran) // 生产者顺利执行完了task
set(result);
}
} finally {
// 最后还有finally块,先执行runner = null,执行后不用担心FutureTask对象.run()被调用两次,因为此时state肯定不是NEW了,run方法的第一句肯定通不过。
// 再判断当前是否为>= INTERRUPTING,这种情况可能是遇到了state的中间状态,
// NEW -> INTERRUPTING -> INTERRUPTED,所以需要调用handlePossibleCancellationInterrupt自旋等待直到最终状态。
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
/**
* 在不设置结果的情况下执行计算,然后将这个future重置为初始状态,
* 如果计算遇到异常或被取消,则不能这样做。
* 这是为那些本质上执行多次的任务而设计的。
*
* @return {@code true} if successfully run and reset
*/
protected boolean runAndReset() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return false;
boolean ran = false;
int s = state;
try {
Callable<V> c = callable;
if (c != null && s == NEW) {
try {
c.call(); // don't set result
ran = true;
} catch (Throwable ex) {
setException(ex);
}
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
return ran && s == NEW;
}
/**
* 确保来自可能的cancel(true)的任何中断只在运行或runAndReset时交付给任务。
*
*/
private void handlePossibleCancellationInterrupt(int s) {
// It is possible for our interrupter to stall before getting a
// chance to interrupt us. Let's spin-wait patiently.
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); // wait out pending interrupt
// assert state == INTERRUPTED;
// We want to clear any interrupt we may have received from
// cancel(true). However, it is permissible to use interrupts
// as an independent mechanism for a task to communicate with
// its caller, and there is no way to clear only the
// cancellation interrupt.
//
// Thread.interrupted();
}
内部类WaitNode,方法finishCompletion,awaitDone,removeWaiter,字段UNSAFE,3个Offset
/**
* 在Treiber栈中记录等待线程的简单链表节点。
* 请参阅其他类,如Phaser和SynchronousQueue获得更详细的解释。
*
* 前面提到,对同一个FutureTask对象调用get的不同线程的都属于消费者,
* 当生产者还没有执行完毕task时,调用get会阻塞。
* 而做法是将消费者线程包装成一个链表节点,
* 放到一个链表中,等到task执行完毕,再唤醒链表中的每个节点的线程。
*
* 这种做法类似于AQS的条件队列和signalAll。
* 反正最终链表上的所有节点都将被唤醒,所以链表是栈的逻辑结构,这样只用保存栈顶head指针,稍微简单一点。
*/
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
/**
* 移除并通知所有等待的线程,调用done(),并使callable为空。
*
* 由set和setException方法调用
*/
private void finishCompletion() {
// 保证调用此函数时,state > COMPLETING;
// 此函数负责唤醒所有消费者线程,原理很简单,内层循环遍历链表的每个节点,唤醒每个节点的线程对象。
// 而外层循环在刚开始时,负责给局部变量q赋值,在退出外层循环时,负责检查waiters是否已经被赋值为null(当然检查结果肯定成立)。
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
// 最后还会调用done函数,但这只是空实现,这是用来给使用者拓展用的,可以让生产者线程在执行完毕前多做一点善后工作。
done();
callable = null; // to reduce footprint
}
/**
* 等待完成或在中断或超时时中止。
*
* @param timed true if use timed waits
* @param nanos time to wait, if timed
* @return state upon completion
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false; //代表入栈成功
for (;;) {
// 刚开始检查消费者线程的中断状态,如果被中断,说明消费者线程不应该再等待了。
// 那么从栈中移除节点,并抛出中断异常。比如消费者线程在LockSupport.park(this)后被中断而唤醒。
if (Thread.interrupted()) { //消费者线程被中断
removeWaiter(q);
throw new InterruptedException();
}
int s = state; //获取最新的state
// 接下来检查当前state是什么(可能是 第一次循环执行到这里,也可能是 阻塞后被唤醒下一次循环执行到这里),分为两种情况:
// 如果是NORMAL或EXCEPTIONAL,说明生产者正常执行完task,没有受到消费者的取消动作干扰。这两种都是最终状态,直接返回即可。
// 如果是CANCELLED、INTERRUPTING、INTERRUPTED,那么说明别的消费者“取消”了task。其中有一种中间状态,这无所谓,因为这三种状态都代表了取消。
// 另外,这里是return s返回局部变量,而不是return state返回最新成员。因为大部分状态都是最终状态,即使有一个中间状态,它的最终状态也是已知的了。
// 注意,返回的是return state,这里其实除了返回NEW以外,其他state也是都是可能返回的,在调用removeWaiter(q)期间可能会发现一些事情。
// 比如,在此期间,生产者线程正在执行set,那么state可能是COMPLETING或NORMAL;
// 比如,在此期间,别的消费者线程正在执行cancel,那么state可能是CANCELLED、INTERRUPTING、INTERRUPTED。
// 关于上面这一点,其实就是给已经超时的超时操作多个机会,说不定执行完removeWaiter(q),state就变成NORMAL了呢。
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // 正常执行完的中间状态,自旋等待
Thread.yield();
// 接下来就是正常阻塞前的流程:
// 发现state还是NEW,所以新建节点。
// 新建节点后,发现还没有入队,那么入队。
// 入队完毕后,当前线程就可能马上阻塞了。
else if (q == null) // state为NEW,说明生产者线程还没执行完
q = new WaitNode();
else if (!queued) // 节点已经创建,但还没入栈
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,//CAS保证了入栈的正确性
q.next = waiters, q);//先把新节点q的next指向旧栈顶,然后更新栈顶为q
// 此时节点已经入队,但是state还是NEW,只能阻塞等待了
// 阻塞根据参数有两种版本:
// 无限阻塞。直接调用LockSupport.park(this)。
// 超时阻塞。先计算得到剩余时间还有多少(正常阻塞前的流程也会花点时间的),然后调用LockSupport.parkNanos(this, nanos)。
// 注意,如果超时阻塞后因为超时而唤醒时,也会走到这里,然后发现已经超时,那么栈中移除节点,并返回最新state。
else if (timed) { //如果是超时版本
nanos = deadline - System.nanoTime(); //获得剩余时间
if (nanos <= 0L) { //剩余时间小于等于0,说明已经超时,但task还没执行完。
removeWaiter(q);
return state; //注意这里返回的不是局部变量,而是最新值。运气好的话,可能返回的不是NEW
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
/**
* 尝试断开超时或中断的等待节点的链接,以避免垃圾累积。
* 在没有CAS的情况下,内部节点可以简单地不拼接,因为如果由释放器遍历它们,这是无害的。
* 为了避免从已经删除的节点中取消拼接的影响,在使用可见跟踪时将重新遍历列表。
* 当有很多节点时,这是缓慢的,但我们不希望列表足够长,以超过较高的开销方案。
*
* 来看一下removeWaiter(q)是怎么移除节点的,注意实参是可能为null的,当q局部变量还没创建,当前线程就被中断时。
*/
private void removeWaiter(WaitNode node) {
// 如果实参为null,那么啥也不做
if (node != null) {
// 此函数参数node的thread被标记为null后,node就没有作用了。
// 因为遍历过程中,我们是去寻找所有的被标记的节点,然后尝试移除它们。
// 如果发现一个取消节点是首节点,那么使得head下移一个节点即可。
// 如果发现一个取消节点不是首节点,那么将pred -> q -> s变成pred -> s(执行pred.next = s)。如下图所示,从链表任意节点出发,都不能到达这个q节点。
// pred -> q -> s 变成 pred -> s q -> s
node.thread = null;
retry:
for (;;) { // 致力于寻找pred -> q(thread == null) -> s的结构
for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
s = q.next;
if (q.thread != null) //发现q是未取消节点,更新pred和q
pred = q;
// 发现q是取消节点,但有两种情况
else if (pred != null) { // 如果q不是首节点
pred.next = s; //将pred -> q -> s变成 pred -> s
// 不过有时候执行pred.next = s其实是一个无效操作,实际上并没有把q移除,此时需要重新开始循环continue retry
//(关于这一点,LinkedTransferQueue#unsplice也有一样的判断)。
// 这种情况是pred是一个取消节点时,如下图所示,q节点还是处于链表中:
if (pred.thread == null) // 如果发现pred也是一个取消节点,这说明q还在链表中
continue retry;
}
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, //如果q是首节点
q, s)) //那就使得栈顶下移一个节点即可
continue retry;
}
break;
}
}
}
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long stateOffset;
private static final long runnerOffset;
private static final long waitersOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = FutureTask.class;
stateOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("state"));
runnerOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("runner"));
waitersOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("waiters"));
} catch (Exception e) {
throw new Error(e);
}
}