怎么用wait、notify巧妙的设计一个Future模式?(wait与notify使用场景)

网友投稿 240 2022-08-09

怎么用wait、notify巧妙的设计一个Future模式?(wait与notify使用场景)

我们知道多线程可以实现同时执行多个任务(只是看起来是同时,其实是CPU的时间片切换特别快我们没感觉而已)。

现在假设一个做饭的场景,你没有厨具也没有食材。你可以去网上买一个厨具,但是这段时间,你不需要闲着啊,可以同时去超市买食材。

设想这是两个线程,主线程去买食材,然后开启一个子线程去买厨具。但是,子线程是需要返回一个厨具的。 如果用普通的线程,只有一个Run方法,而Run方法是没有返回值的,这个时候该怎么办呢?

我们就可以用JDK提供的Future模式。在主线程买完食材之后,可以主动去获取子线程的厨具。(本文认为读者了解Future,因此不对Future用法做过多介绍)

代码如下:

public class FutureCook {

static class Chuju {

}

static class Shicai{

}

public static void cook(Chuju chuju,Shicai shicai){

System.out.println("最后:烹饪中...");

}

public static void main(String[] args) throws InterruptedException, ExecutionException {

//第一步,网购厨具

Callable shopping = new Callable(){

@Override

public Chuju call() throws Exception {

System.out.println("第一步:下单");

System.out.println("第一步:等待送货");

Thread.sleep(5000); //模拟送货时间

System.out.println("第一步:快递送到");

return new Chuju();

}

};

FutureTask task = new FutureTask(shopping);

new Thread(task).start();

//第二步,购买食材

Thread.sleep(2000);

Shicai shicai = new Shicai();

System.out.println("第二步:食材到位");

//第三步,烹饪

if(!task.isDone()){ //是否厨具到位

System.out.println("第三步:厨具还没到,请等待,也可以取消");

//①

// task.cancel(true);

// System.out.println("已取消");

// return;

}

//尝试获取结果,如果获取不到,就会进入等待状态

// 即main线程等待子线程执行结束才能继续往下执行

Chuju chuju = task.get();

System.out.println("第三步:厨具到位,可以烹饪了");

cook(chuju,shicai);

}

}

返回结果:

第一步:下单

第一步:等待送货

第二步:食材到位

第三步:厨具还没到,请等待,也可以取消

第一步:快递送到

第三步:厨具到位,可以烹饪了

最后:烹饪中...

以上代码表示,子线程购买厨具消耗的时间比较长(假定5秒),而主线程购买食材比较快(2秒),所以我在第三步烹饪之前,先去判断一下买厨具的线程是否执行完毕。此处肯定返回false,然后主线程可以选择继续等待,也可以选择取消。(把①注释打开即可测试取消)

我们可以看到,利用Future模式,可以把原本同步执行的任务改为异步执行,可以充分利用CPU资源,提高效率。

现在,我用wait、notify的方式来实现和以上Future模式一模一样的效果。

大概思想就是,创建一个FutureClient端去发起请求,通过FutureData先立即返回一个结果(此时相当于只返回一个请求成功的通知),然后再去开启一个线程异步地执行任务,获取真实数据RealData。此时,主线程可以继续执行其他任务,当需要数据的时候,就可以调用get方法拿到真实数据。

1)定义一个数据接口,包含获取数据的get方法,判断任务是否执行完毕的isDone方法,和取消任务的cancel方法。

public interface Data {

T get();

boolean isDone();

boolean cancel();

}

2)定义真实数据的类,实现Data接口,用来执行实际的任务和返回真实数据。

public class RealData implements Data{

private T result ;

public RealData (){

this.prepare();

}

private void prepare() {

//准备数据阶段,只有准备完成之后才可以继续往下走

try {

System.out.println("第一步:下单");

System.out.println("第一步:等待送货");

Thread.sleep(5000);

System.out.println("第一步:快递送到");

} catch (InterruptedException e) {

System.out.println("被中断:"+e);

//重新设置中断状态

Thread.currentThread().interrupt();

}

Main.Chuju chuju = new Main.Chuju();

result = (T)chuju;

}

@Override

public T get() {

return result;

}

@Override

public boolean isDone() {

return false;

}

@Override

public boolean cancel() {

return true;

}

}

prepare方法用来准备数据,其实就是执行的实际任务。get方法用来返回任务的执行结果。

3)定义一个代理类FutureData用于给请求端FutureClient暂时返回一个假数据。等真实数据拿到之后,再装载真实数据。

public class FutureData implements Data{

private RealData realData ;

private boolean isReady = false;

private Thread runningThread;

public synchronized void setRealData(RealData realData) {

//如果已经装载完毕了,就直接返回

if(isReady){

return;

}

//如果没装载,进行装载真实对象

this.realData = realData;

isReady = true;

//进行通知

notify();

}

@Override

public synchronized T get() {

//如果没装载好 程序就一直处于阻塞状态

while(!isReady){

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

//装载好直接获取数据即可

return realData.get();

}

public boolean isDone() {

return isReady;

}

@Override

public boolean cancel() {

if(isReady){

return false;

}

runningThread.interrupt();

return true;

}

public void setRunningThread(){

runningThread = Thread.currentThread();

}

}

如果get方法被调用,就会去判断数据是否已经被加载好(即判断isReady的值),如果没有的话就调用wait方法进入等待。

setRealData用于去加载真实的数据,加载完毕之后就把isReady设置为true,然后调用notify方法通知正在等待的线程。此时,get方法收到通知就继续执行,然后返回真实数据realData.get().

另外也简单的实现了一个取消任务的方法cancel,去中断正在执行子任务的线程。

4)FutureClient客户端用于发起请求,异步执行任务。

public class FutureClient {

public Data call(){

//创建一个代理对象FutureData,先返回给客户端(无论是否有值)

final FutureData futureData = new FutureData();

//启动一个新的线程,去异步加载真实的对象

new Thread(new Runnable() {

@Override

public void run() {

//此处注意需要记录一下异步加载真实数据的线程,以便后续可以取消任务。

futureData.setRunningThread();

RealData realData = new RealData();

//等真实数据处理完毕之后,把结果赋值给代理对象

futureData.setRealData(realData);

}

}).start();

return futureData;

}

}

5)测试

public class Main {

static class Chuju{

}

static class Shicai{

}

public static void main(String[] args) throws InterruptedException {

FutureClient fc = new FutureClient();

Data data = fc.call();

Thread.sleep(2000);

Shicai shicai = new Shicai();

System.out.println("第二步:食材到位");

if(!data.isDone()){

System.out.println("第三步:厨具还没到,请等待或者取消");

//②

// data.cancel();

// System.out.println("已取消");

// return;

}

//真正需要数据的时候,再去获取

Chuju chuju = (Chuju)data.get();

System.out.println("第三步:厨具到位,可以烹饪了");

cook(chuju,shicai);

}

public static void cook (Chuju chuju, Shicai shicai){

System.out.println("最后:烹饪中...");

}

}

执行结果和用JDK提供的Future模式是一模一样的。我们也可以把②出的代码打开,测试任务取消的结果。

第一步:下单

第一步:等待送货

第二步:食材到位

第三步:厨具还没到,请等待或者取消

已取消

被中断:java.lang.InterruptedException: sleep interrupted

执行取消之后,执行RealData的子线程就会被中断,然后结束任务。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:详解CopyOnWrite容器及其源码(copyonwrite原理)
下一篇:高并发之——不得不说的线程池与ThreadPoolExecutor类浅析
相关文章

 发表评论

暂时没有评论,来抢沙发吧~