download:大前端2022版全面升级完结无密内置文档资料
FutureTask源码深度分析
在JDK的FutureTask当中会运用到一个工具LockSupport,在正式引见FutureTask之前我们先熟习一下这个工具。
LockSupport主要是用于阻塞和唤醒线程的,它主要是经过包装UnSafe类,经过UnSafe类当中的办法停止完成的,他底层的办法是经过依赖JVM完成的。在LockSupport当中主要有以下三个办法:
unpark(Thread thread))办法,这个办法能够给线程thread发放一个答应证,你能够经过屡次调用这个办法给线程发放答应证,每次调用都会给线程发放一个答应证,但是这个答应证不可以停止累计,也就是说一个线程可以具有的最大的答应证的个数是1一个。
park()办法,这个线程会消费调用这个办法的线程一个答应证,由于线程的默许答应证的个数是0,假如调用一次那么答应证的数目就变成-1,当答应证的数目小于0的时分线程就会阻塞,因而假如线程历来没用调用unpark办法的话,那么在调用这个办法的时分会阻塞,假如线程在调用park办法之前,有线程调用unpark(thread)办法,给这个线程发放一个答应证的话,那么调用park办法就不会阻塞。
parkNanos(long nanos)办法,同park办法一样,nanos表示最长阻塞超时时间,超时后park办法将自动返回,假如调用这个办法的线程有答应证的话也不会阻塞。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
public class Demo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
LockSupport.park(); // 没有答应证 阻塞住这个线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("阻塞完成");
});
thread.start();
TimeUnit.SECONDS.sleep(2);
LockSupport.unpark(thread); //给线程 thread 发放一个答应证
System.out.println("线程启动");
}
}
复制代码
上面代码的执行结果
线程启动
阻塞完成
复制代码
从上面代码我们能够晓得LockSupport.park()能够阻塞一个线程,由于假如没有阻塞的话肯定会先打印阻塞完成,由于打印这句话的线程只休眠一秒,主线程休眠两秒。
在源代码当中你能够会遇到UNSAFE.compareAndSwapXXX的代码,这行代码主要是停止原子交流操作CAS,比方:
UNSAFE.compareAndSwapInt(this, stateOffset, NEW, CANCELLED)))
复制代码
上面的代码主要是将this对象当中的内存偏移地址为stateOffset的对象拿出来与NEW停止比拟,假如等于NEW那就将这个值设置为CANCELLED,这整个操作是原子的(由于可能多个线程同时调用这个函数,因而需求保证操作是原子的),假如操作胜利返回true反之返回false。假如你目前不是很了解也没关系,只需求晓得它是将对象this的内存偏移为stateOffset的值交换为CANCELLED就行,假如这个操作胜利返回true,不胜利返回false。
FutureTask回忆
我们首先来回忆一下FutureTask的编程步骤:
写一个类完成Callable接口。
@FunctionalInterface
public interface Callable
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
复制代码
完成接口就完成call即可,能够看到这个函数是有返回值的,而FutureTask返回给我们的值就是这个函数的返回值。
new一个FutureTask对象,并且new一个第一步写的类,new FutureTask<>(callable完成类)。
最后将刚刚得到的FutureTask对象传入Thread类当中,然后启动线程即可new Thread(futureTask).start();。
然后我们能够调用FutureTask的get办法得到返回的结果futureTask.get();。
可能你会对FutureTask的运用方式觉得困惑,或者不是很分明,如今我们来认真捋一下思绪。
首先启动一个线程要么是继承自Thread类,然后重写Thread类的run办法,要么是给Thread类传送一个完成了Runnable的类对象,当然能够用匿名内部类完成。
既然我们的FutureTask对象能够传送给Thread类,阐明FutureTask肯定是完成了Runnable接口,事实上也的确如此
能够发现的是FutureTask的确完成了Runnable接口,同时还完成了Future接口,这个Future接口主要提供了后面我们运用FutureTask的一系列函数比方get。
看到这里你应该可以大致想到在FutureTask中的run办法会调用Callable当中完成的call办法,然后将结果保管下来,当调用get办法的时分再将这个结果返回。