JAVA 并发编程
Executor 源码分析
Future体系源码分析
创建线程的几种方式:
1 继承thread类
2 实现runnable接口,传参给new thread,创建线程。
3 实现callbale接口,传参给FutureTask包装,传给new thread 创建线程
4 Executor.execute(一个runnable实例对象),本质通过线程工厂在线程池中创建。
题外话,现在正式介绍Future体系
UML图,FutureTask实现了RunnableFuture接口,RunnableFuture继承了
Future和Runnable接口(接口可以继承接口,但是只有类能实现接口)
FutureTask保存了7种状态NEW, INTERRUPTING, COMPLETING, NORMAL,EXCEPTIONAL,CANCELLED,INTERRUPTED分别对应了6个数值。可比较的
//这是一个内部的callable对象,我们通过构造函数传入的callable对象将会保存在这里
//当任务执行完成后,callable会被置为null
private Callable<V> callable;
//保存任务执行的结果或者是get()方法抛出的异常,通过state来实现同步的
private Object outcome;
//执行callable任务的线程,它是CAS操作。
private volatile Thread runner;
//等待线程的Treiber栈,Treiber是一种算法,Treiber栈是一种无阻塞栈。
private volatile WaitNode waiters;
public FutureTask(Callable<V> callable) { //可以看到构造方法可以传callable,或者runnable返回值
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
下面的代码很重要:
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long stateOffset; //任务状态的偏移量
private static final long runnerOffset; //runner线程的偏移量
private static final long waitersOffset; //Treiber栈的偏移量
//有了这些偏移量后,UNSAFE就能得到他们对应的值了
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);
}
}
//任务起动就调这个方法。配合***CAS***算法看
public void run() {
//compareAndSwapObject方法有四个参建:第一个是某个对象,第二个是相对偏移量,有了这个偏移量
//就可以知道,内存块对应变量X的值了,第三个是:预期值,第四个是:更新值,如果X == 预期值,那
//么就将X的值更新为更新值,并返回true,或者返回fasle.「是一个CAS操作」
//如果state == NEW,则runner = Thread.currentThread() ,返回true,取反之后,进入后面的计算。
//如果state不是NEW的情况,说明任务已经被执行了,直接返回
//避免任务重新执行
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return; //一个或运算表明,这两个操作有个一为True,都直接返回,因为状态不为NEW,已经被执行了
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
//这一块的代码,建议大家反复读多几次
//这一个ran变量用得真的是精彩,他主要的为了:他主要是不捕捉set()方法的异常
//如果这里我们直接在 result = c.call();后面直接调set();那么最终的done()方法很可能出现异常
//就会导致 setException()调用了,从而生命周期变成了
//NEW -> COMPLETING -> NORMAL-> EXCEPTIONAL
boolean ran;
try {
result = c.call();//如果任务执行的时间比较少,那么在这里就体现出来了
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
//在任务执行的过程中,可能会调用cancel()
//这里主要是不想让中断操作逃逸到run()方法之外
int s = state;
if (s >= INTERRUPTING) //s为interrupt状态
handlePossibleCancellationInterrupt(s);
}
}
//更改当前任务的状态并把任务执行的结果写入到outcome当中,最后由get()取出来用
protected void set(V v) {
//将当前任务状态置为:COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
//将当前任务状态置为:NORMAL
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
//完成任务后的收尾操作
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
// 将Treiber栈的栈顶置为null,
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
//遍历Treiber栈并唤醒所有节点的线程
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;//把当前节点置为无效节点
LockSupport.unpark(t);//这里唤醒的是awaitDone()阻塞的线程
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
//一个钩子方法,本类中,它是一个空实现,在子类中可以重写它。
done();
//最后把callable置为null.
callable = null; // to reduce footprint
}
//修改当前任务的状态
protected void setException(Throwable t) {
//将当前任务状态置为:COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
//将当前任务状态置为:EXCEPTIONAL
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
//取消任务。如果是false NEW->CANCELLED true: NEW ->INTERRUPTING->INTERRUPTED
public boolean cancel(boolean mayInterruptIfRunning) {
//如果当前任务是新建任务,则将其置为INTERRUPTING或者CANCELLED
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
//如果当前任务不是新建任务,则将其状态置为INTERRUPTING
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
CAS算法
CAS 操作包含三在这里插入代码片
个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”
Unsafe类的compareAndSwapInt()方法为例来说,compareAndSwapInt就是借助C语言和汇编代码来实现的。
public final native boolean compareAndSwapInt(Object o,
long offset,
int expected,
int x);
x86汇编指令cmpxchg本身保证了原子性,其实就是cpu的CAS操作的实现,那么问题来了,为什么保证了原子性还需要在多核处理器中加上lock前缀呢?
答案是:多核处理器中不能保证可见性,lock前缀可以保证这行汇编中所有值的可见性,这个问题的原因是多核CPU中缓存导致的(x86中罪魁祸首是store buffer的存在)。
这样通过lock前缀保障多核处理器的可见性,然后通过cmpxchg指令完成CPU上原子性的CAS操作,完美解决问题!
volatile关键字
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
volatile 作用
1)它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
2)它会强制将对缓存的修改操作立即写入主存;
Java内存模型规定所有的变量都是存在主存(堆)当中(类似于前面说的物理内存),每个线程都有自己的工作内存(栈)(类似于前面的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
3)如果是写操作,它会导致其他CPU中对应的缓存行无效。
参考博文:
https://blog.csdn.net/cringkong/article/details/80533917
https://www.cnblogs.com/dolphin0520/p/3920373.html