前言
Callable,Future,Executor都是java.util.concurrent包下的工具類盅安,作者李二狗唤锉,為了徹底吃透它們的概念,今天就假設(shè)這些類都不存在别瞭,自己通過實(shí)際場景封裝出這些工具的山寨版
需求
假設(shè)你需要寫一個(gè)簡單的方法窿祥,兩個(gè)值求和,非常簡單
public int sum(int x, int y) {
return x + y;
}
但需求增加了蝙寨,需要計(jì)算的過程在一個(gè)新線程中執(zhí)行晒衩,這代碼該怎么寫?就會(huì)出現(xiàn)以下兩個(gè)問題:
- 怎么獲取到線程執(zhí)行的結(jié)果墙歪?
- 怎么知道新線程什么時(shí)候執(zhí)行完听系?
實(shí)現(xiàn)
首先第一個(gè)問題,如何獲取新線程結(jié)果虹菲,這個(gè)也好解決靠胜,雖然新線程里的變量我取不到,但內(nèi)存是線程共享的啊毕源,只要提前定義一個(gè)結(jié)果變量即可
private volatile int outcome;
public int sum(int x, int y) {
new Thread(()->{
outcome = x + y;
}).start();
return outcome;
}
很明顯浪漠,最終結(jié)果肯定不對(duì),因?yàn)榉祷氐臅r(shí)候新線程可能都沒開始運(yùn)行霎褐,這就是第二個(gè)問題:怎么知道新線程什么時(shí)候執(zhí)行完址愿?
解決的方案當(dāng)然也很多,只要線程之間進(jìn)行通訊一下即可瘩欺,我們用LockSupport方法來實(shí)現(xiàn)通訊
public class OperationTest {
private volatile int outcome;
private Thread waitThread;
public int sum(int x, int y) throws InterruptedException {
waitThread = Thread.currentThread();
new Thread(() -> {
outcome = x + y;
// 計(jì)算完成通知等待線程
LockSupport.unpark(waitThread);
}).start();
// 等待計(jì)算完成
LockSupport.park(this);
return outcome;
}
public static void main(String[] args) throws InterruptedException {
System.out.println(new OperationTest().sum(2,3));
}
}
此時(shí)我們就完成了這個(gè)需求必盖,但看一下代碼真的好麻煩啊,明明就是一個(gè)1+1等于幾的事俱饿,寫了這么多代碼歌粥,如果明天再來個(gè)寫減法的需求,我還要重寫這么一堆
封裝
其實(shí)上面解決的兩個(gè)問題拍埠,完全就是通用性的問題失驶,加法這樣處理,減法一樣也是這個(gè)解決思路枣购,那么我們就可以把加法嬉探、減法等有返回值的方法用函數(shù)式接口給抽象化
// 使用泛型兼容各種類型返回
@FunctionalInterface
public interface Callable<V> {
V call();
}
接下來我們就要封裝一個(gè)開啟新線程執(zhí)行它的工具擦耀,提供如下功能:
- 只要傳入一個(gè)方法作為參數(shù),就可以開啟一個(gè)新線程去執(zhí)行這個(gè)方法涩堤,并返回執(zhí)行結(jié)果
- 也可以開啟新線程執(zhí)行普通的Runable方法
這個(gè)工具命名為ExecutorService
public interface ExecutorService {
/**
* 執(zhí)行callable并返回執(zhí)行結(jié)果
* @param task
* @param <T>
*/
<T> T submit(Callable<T> task);
/**
* 也可以執(zhí)行Runnable
* @param runnable
*/
void execute(Runnable runnable);
}
然后開始實(shí)現(xiàn)這個(gè)工具眷蜓,暫時(shí)叫做NewThreadExecutor
(新線程執(zhí)行器)
public class NewThreadExecutor implements ExecutorService {
private volatile Object outcome;
private Thread waitThread;
@Override
public <T> T submit(Callable<T> task) {
waitThread = Thread.currentThread();
execute(()->{
outcome = task.call();
// 計(jì)算完成通知等待線程
LockSupport.unpark(waitThread);
});
// 等待計(jì)算完成
LockSupport.park(this);
return (T) outcome;
}
@Override
public void execute(Runnable runnable) {
// 執(zhí)行方式就是開啟一個(gè)線程去執(zhí)行
new Thread(runnable).start();
}
}
這時(shí)我們?cè)賹?shí)現(xiàn)上面的需求就輕而易舉了
int x = 2;
int y = 3;
Integer sub = new NewThreadExecutor().submit(() -> {
return x + y;
});
System.out.println(sub);
這樣通過我們的邏輯和執(zhí)行解耦,可以方便使用工具執(zhí)行減法胎围、乘法或其它復(fù)雜運(yùn)算邏輯
多線程
再回頭看一下我們這個(gè)工具吁系,實(shí)際上非常不合理,開了一個(gè)新線程去執(zhí)行函數(shù)白魂,整個(gè)過程主線程卻全程傻等
相當(dāng)于一個(gè)主管帶一個(gè)員工干活汽纤,而員工干活時(shí),主管干不了別的事只能等著福荸,那干脆主管自己干得了唄蕴坪,何必聘請(qǐng)這么一個(gè)員工
而我們希望開啟新線程后主線程可以去干別的(比如分配新任務(wù)給其它線程執(zhí)行),等全分配完任務(wù)再統(tǒng)一獲取結(jié)果敬锐,這樣才算是多線程并行作業(yè)
那么如何改造代碼吶背传?
首先,調(diào)用submit
方法不能阻塞滞造,應(yīng)該直接返回一個(gè)對(duì)象续室,主線程再想要獲取的時(shí)候,才通過這個(gè)對(duì)象阻塞獲取結(jié)果
這個(gè)對(duì)象不是運(yùn)行結(jié)果谒养,但通過它可以獲得結(jié)果挺狰,他就像一個(gè)未來的約定,我們先使用代碼給它抽象出來买窟,命名為Future
public interface Future<V> {
// 是否運(yùn)行完成
boolean isDone();
// 獲取運(yùn)行結(jié)果
V get();
}
此時(shí)我們的ExecutorService返回結(jié)果變?yōu)镕uture對(duì)象
public interface ExecutorService {
/**
* 執(zhí)行callable并返回future
* @param task
* @param <T>
*/
<T> Future<T> submit(Callable<T> task);
/**
* 也可以執(zhí)行Runnable
* @param runnable
*/
void execute(Runnable runnable);
}
那么此時(shí)如何改造NewThreadExecutor這個(gè)實(shí)現(xiàn)吶丰泊?
首先要實(shí)現(xiàn)Future抽象,這個(gè)對(duì)象可以獲取到執(zhí)行結(jié)果始绍,那么它肯定可以訪問到存儲(chǔ)執(zhí)行結(jié)果的對(duì)象(outcome)和等待線程對(duì)象(waitThread)瞳购,那不妨就把這兩個(gè)對(duì)象放入Future實(shí)現(xiàn)中,同時(shí)最終執(zhí)行的Runable方法也要可以訪問到這兩個(gè)對(duì)象亏推,那不妨就讓Future的實(shí)現(xiàn)同時(shí)就是最終執(zhí)行的Runable学赛,即可執(zhí)行的Future,取名為FutureTask
public class FutureTask<V> implements Future<V>, Runnable {
private Callable<V> callable; // 要執(zhí)行的方法
private volatile Object outcome; // 執(zhí)行結(jié)果
private Thread waitThread; // 等待的線程
public FutureTask(Callable<V> callable) {
this.callable = callable;
}
@Override
public boolean isDone() {
return outcome!=null;
}
@Override
public V get() {
waitThread = Thread.currentThread();
if (isDone()) { // 如果已經(jīng)執(zhí)行完直接返回
return (V) outcome;
}
// 否則等待
LockSupport.park(this);
return (V) outcome;
}
@Override
public void run() {
// 開始執(zhí)行
outcome = callable.call();
// 計(jì)算完成通知等待線程
LockSupport.unpark(waitThread);
}
}
此時(shí)NewThreadExecutor改造如下
public class NewThreadExecutor implements ExecutorService {
@Override
public <T> Future<T> submit(Callable<T> task) {
FutureTask<T> futureTask = new FutureTask<>(task);
execute(futureTask);
return futureTask; // 直接返回
}
@Override
public void execute(Runnable runnable) {
// 執(zhí)行方式就是開啟一個(gè)線程去執(zhí)行
new Thread(runnable).start();
}
}
這時(shí)我們就可以讓兩個(gè)子線程分別同時(shí)計(jì)算兩個(gè)結(jié)果吞杭,最終主線程求和(真正的做到多線程計(jì)算)
Future<Integer> future1 = new NewThreadExecutor().submit(() -> {
return 3 + 4;
});
Future<Integer> future2 = new NewThreadExecutor().submit(() -> {
return 1 + 2;
});
System.out.println(future1.get()+future2.get());
擴(kuò)展
以上封裝的工具盏浇,達(dá)到了傳入一個(gè)方法開啟一個(gè)新線程計(jì)算的功能,并且使用future概念避免了阻塞
但工具還能再擴(kuò)展一下芽狗,比如有一天領(lǐng)導(dǎo)讓實(shí)現(xiàn)傳入一個(gè)方法指定某一線程執(zhí)行绢掰,或傳入方法從幾個(gè)固定線程中選一個(gè)空閑的去執(zhí)行(線程池)
由于我們做到了邏輯和執(zhí)行的分離解耦,所以只要重寫一下execute
就可以了,而無論如何執(zhí)行submit
的邏輯是不變的滴劲,我們可以繼續(xù)給它抽象出來攻晒,命名為AbstractExecutorService
public abstract class AbstractExecutorService implements ExecutorService {
@Override
public <T> Future<T> submit(Callable<T> task) {
FutureTask<T> futureTask = new FutureTask<>(task);
execute(futureTask);
return futureTask; // 直接返回
}
}
此時(shí)它的繼承者就可以傳入方法并返回future,只需關(guān)注如何執(zhí)行即可班挖,比如我們的開啟新線程執(zhí)行工具
public class NewThreadExecutor extends AbstractExecutorService {
@Override
public void execute(Runnable runnable) {
// 執(zhí)行方式就是開啟一個(gè)線程去執(zhí)行
new Thread(runnable).start();
}
}
再比如使用線程池去執(zhí)行
public class ThreadPoolExecutor extends AbstractExecutorService {
@Override
public void execute(Runnable runnable) {
// 從線程池中選一個(gè)線程去執(zhí)行
}
}
最后
以上代碼的命名基本參照jdk的源碼鲁捏,可以自行對(duì)照,相信再看源碼就會(huì)非常清晰聪姿,也可以結(jié)合Executor源碼詳解解讀源碼