Java并发编程---Callable&Future

简单说一下这个future模式,一句话概括"去除了主函数的等待时间,并使得原本需要等待的时间段可以用于处理其他业务逻辑",类似于Ajax异步加载,至于Callable其实就是和Runnable差不多的一个接口,无非就是前者可以有返回值.这样说相信你一定知道个大概了.今天情人节? 今天是初十!!! 过啥洋节都成年人了.

Callable

在Java中,创建线程一般有两种方式,一种是继承Thread类一种是实现Runnable接口。然而,这两种方式的缺点是在线程任务执行结束后,无法获取执行结果。我们一般只能采用共享变量或共享存储区以及线程通信的方式实现获得任务结果的目的。

不过,Java中,也提供了使用Callable和Future来实现获取任务结果的操作。Callable用来执行任务,产生结果,而Future用来获得结果。

Callable接口与Runnable接口是否相似,查看源码,可知Callable接口的定义如下:

@FunctionalInterface
public interface Callable<V> {
    /**
     * 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;
}

可以看到,与Runnable接口不同之处在于,call方法带有泛型返回值V。

Future常用方法

  • V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
  • V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。
  • boolean isDone() :只要任务执行结束,都返回true。
  • boolean isCanceller() :只要任务完成前被取消,则返回true。
  • boolean cancel(boolean mayInterruptRunning) :如果任务还没开始,执行cancel(…)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(…)方法将返回false。
    • mayInterruptRunning 参数表示是否中断执行中的线程。

通过方法分析我们也知道实际上Future提供了3种功能:

  • 能够中断执行中的任务
  • 判断任务是否执行完成
  • 获取任务执行完成后的结果

我们通过简单的例子来体会使用Callable和Future来获取任务结果的用法。

ExecutorService的submit方法:
Java并发编程---Callable&Future_第1张图片

public class TestMain {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		ExecutorService executor = Executors.newCachedThreadPool();
		Future<Integer> future = executor.submit(new AddNumberTask());
		System.out.println(Thread.currentThread().getName() + "线程执行其他任务");
		Integer integer = future.get();
		System.out.println(integer);
		// 关闭线程池
		if (executor != null)
			executor.shutdown();
	}

}

class AddNumberTask implements Callable<Integer> {

	public AddNumberTask() {

	}

	@Override
	public Integer call() throws Exception {
		System.out.println("####AddNumberTask###call()");
		Thread.sleep(5000);
		return 5000;
	}

}

Future模式

Future模式的核心在于:去除了主函数的等待时间,并使得原本需要等待的时间段可以用于处理其他业务逻辑

  • Futrure模式:对于多线程,如果线程A要等待线程B的结果,那么线程A没必要等待B,直到B有结果,可以先拿到一个未来的Future,等B有结果是再取真实的结果。

  • 在多线程中经常举的一个例子就是:网络图片的下载,刚开始是通过模糊的图片来代替最后的图片,等下载图片的线程下载完图片后在替换。而在这个过程中可以做一些其他的事情。
    Java并发编程---Callable&Future_第2张图片

首先客户端向服务器请求RealSubject,但是这个资源的创建是非常耗时的,怎么办呢?这种情况下,首先返回Client一个FutureSubject,以满足客户端的需求,于此同时呢,Future会通过另外一个Thread 去构造一个真正的资源,资源准备完毕之后,在给future一个通知。如果客户端急于获取这个真正的资源,那么就会阻塞客户端的其他所有线程,等待资源准备完毕。

手撸Future模式

/**
 * 公共数据接口,FutureData和RealData都要实现。
 * @author Administrator
 *
 */
public interface Data {
	public  String getRequest();
}
/**
 * FutureData,当有线程想要获取RealData的时候,程序会被阻塞。
 * 等到RealData被写入才会使用getReal()方法。
 * @author Administrator
 *
 */
public class FutureData implements Data {

	public volatile static boolean ISFLAG = false;
	private RealData realData;

	public synchronized void setRealData(RealData realData) {
		// 如果已经获取到结果,直接返回
		if (ISFLAG) {
			return;
		}
		// 如果没有获取到数据,传递真实对象
		this.realData = realData;
		ISFLAG = true;
		// 进行通知
		notify();
	}
	// 最后调用getRequest 待到notify后来获取真实对象数据
	public synchronized String getRequest() {
		while (!ISFLAG) {
			try {
				wait();
			} catch (Exception e) {

			}
		}
		// 获取到数据,直接返回 xianglei
		return realData.getRequest();
	}

}

/**
 * 真实数据RealData
 * @author Administrator
 *
 */
public class RealData implements Data {
	private String result;

	public RealData(String data) {
		System.out.println("正在使用data:" + data + "--"+"网络请求数据,耗时操作需要等待.");
		try {
			Thread.sleep(3000);
		} catch (Exception e) {

		}
		System.out.println("操作完毕,获取真实结果...");
		result = "xianglei";
	}

	public String getRequest() {
		return result;
	}
}
public class FutureClient {
	// 请求参数
	public Data request(final String queryStr) {
		final FutureData furureData = new FutureData();
		// 立马返回一个假的数据的同时开启线程去耗时获取真数据 子线程发生阻塞3s
		new Thread(new Runnable() {

			public void run() {
				
				RealData realData = new RealData(queryStr);
				furureData.setRealData(realData);
			}
		}).start();
		
		return furureData;

	}

}
/**
 * 调用者请求资源,client.request("name"); 完成对数据的准备
当要获取资源的时候,data.getResult() ,
如果资源没有准备好isReady = false;那么就会阻塞该线程。直到资源获取然后该线程被唤醒。
 * @author Administrator
 *
 */
public class MainTest {
	public static void main(String[] args) {
		FutureClient futureClient = new FutureClient();
		Data request = futureClient.request("110&120");
		System.out.println("请求发送成功!");
		System.out.println("执行其他任务...");
		// 如果没有准备好资源则阻塞在这里
		String result = request.getRequest();
		System.out.println("获取到结果..." + result);
	}

}

代码思路梳理
  • 一个公共接口定义一个getRequest()方法

  • FutureData类实现Data接口,开始ISFLAG为false在循环里面就一直在等待着不会执行 realData.getRequest();

    public synchronized String getRequest() {
    		while (!ISFLAG) {
    			try {
    				wait();
    			} catch (Exception e) {
    
    			}
    		}
    		// 获取到数据,直接返回
    		return realData.getRequest();
    	}
    
  • 至于FutureData中的setRealData方法,是当真实的对象设置进来后再通知notify() – getRequest()方法返回真实对象.

    	public synchronized void setRealData(RealData realData) {
    		// 如果已经获取到结果,直接返回
    		if (ISFLAG) {
    			return;
    		}
    		// 如果没有获取到数据,传递真实对象
    		this.realData = realData;
    		ISFLAG = true;
    		// 进行通知
    		notify();
    	}
    
  • RealData类中的构造方法复制模拟一个耗时的获取数据的操作,最后将xianglei设置到类的result变量里面

    public RealData(String data) {
    		System.out.println("正在使用data:" + data + "--"+"网络请求数据,耗时操作需要等待.");
    		try {
    			Thread.sleep(3000);
    		} catch (Exception e) {
    
    		}
    		System.out.println("操作完毕,获取真实结果...");
    		result = "xianglei";
    	}
    
  • FutureClient类则是对访问获取数据操作的封装

    public Data request(final String queryStr) {
       	final FutureData furureData = new FutureData();
       	//
       	new Thread(new Runnable() {
    
       		public void run() {
       		//这里会发生阻塞
       			RealData realData = new RealData(queryStr);
       			//  这里已经通知了
       			furureData.setRealData(realData);
       		}
       	}).start();
       	
       	return furureData;
    
       }
    

    用Future&Callable实现

    	
    import java.util.concurrent.Callable;
    
    public class RealData implements Callable<String> {
        private String Data;
    
        public RealData(String Data) {
            this.Data = Data;
        }
    
        public String call() throws Exception {
            //利用sleep来表示任务处理
            Thread.sleep(2000);
    
            return "这是处理"+Data+"结果";
        }
    }
    
    
    
    public class Main {
    
        public static void main(String[] args) throws Exception {
            Long start = System.currentTimeMillis();
    
            FutureTask<String> futureTask = new FutureTask<>(new RealData("hello,world"));
            ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(1);
            newFixedThreadPool.submit(futureTask);
    
            // 表示正在处理其他逻辑,或者业务
            Thread.sleep(1000);
    
            System.out.println("最后结果-->" + futureTask.get());
    
        }
    
    }
    

再来一个例子? 先去过节 晚上回来补上 打个标记?

使用Future模式实现一个买菜做饭的场景.
package test;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class FutureCook {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        // 第一步 网购厨具
        Callable<Chuju> onlineShopping = new Callable<Chuju>() {

            @Override
            public Chuju call() throws Exception {
                System.out.println("第一步:下单");
                System.out.println("第一步:等待送货");
                Thread.sleep(5000);  // 模拟送货时间
                System.out.println("第一步:快递送到");
                return new Chuju();
            }
            
        };
        FutureTask<Chuju> task = new FutureTask<Chuju>(onlineShopping);
        new Thread(task).start();
        // 第二步 去超市购买食材
        Thread.sleep(2000);  // 模拟购买食材时间
        Shicai shicai = new Shicai();
        System.out.println("第二步:食材到位");
        // 第三步 用厨具烹饪食材
        if (!task.isDone()) {  // 联系快递员,询问是否到货
            System.out.println("第三步:厨具还没到");
        }
        Chuju chuju = task.get();
        System.out.println("第三步:厨具到位,开始展现厨艺");
        cook(chuju, shicai);
        
        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
    
    //  用厨具烹饪食材
    static void cook(Chuju chuju, Shicai shicai) {}
    
    // 厨具类
    static class Chuju {}
    
    // 食材类
    static class Shicai {}

}

结果:

第一步:下单
第一步:等待送货
第二步:食材到位
第三步:厨具还没到
第一步:快递送到
第三步:厨具到位,开始展现厨艺
总共用时5005ms

总结

Runnable和Callable都是接口

不同之处:

  1. Callable可以返回一个类型V,而Runnable不可以
  2. Callable能够抛出checked exception,而Runnable不可以。
  3. Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的
  4. Callable和Runnable都可以应用于executors。而Thread类只支持Runnable.

上面只是简单的不同,其实这两个接口在用起来差别还是很大的。Callable与executors联合在一起,在任务完成时可立刻获得一个更新了的Future。而Runable却要自己处理.

你可能感兴趣的:(《并发编程》系列)