③. Callable接口(创建线程)
- ①. Callable接口中的call方法和Runnable接口中的run方法的区别
- 是否有返回值(Runnable接口没有返回值 Callable接口有返回值)
- 是否抛异常(Runnable接口不会抛出异常 Callable接口会抛出异常)
- 落地方法不一样,一个是call() ,一个是run()
- ②. Future接口概述
- FutureTask是Future接口的唯一的实现类
- FutureTask同时实现了Runnable、Future接口。它既可以作为Runnable被线程执行,又可以作为Futrue得到Callable的返回值
/* 创建线程的方式三: 实现callable接口 ---JDK 5.0 新增 1.创建一个实现Callable接口的实现类 2.实现call方法,将此线程需要执行的操作声明在call()中 3.创建callable接口实现类的对象 4.将此callable的对象作为参数传入到FutureTask构造器中,创建FutureTask的对象 5.将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用star 6.获取callable接口中call方法的返回值 * */ public class ThreadNew { public static void main(String[] args) { //3.创建callable接口实现类的对象 NumThead m=new NumThead(); //4.将此callable的对象作为参数传入到FutureTask构造器中,创建FutureTask的对象 FutureTask futureTask = new FutureTask(m); //5.将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法 //FutureTask类继承了Runnable接口 //new Runnable = futrueTask; new Thread(futureTask).start(); //6.获取callable接口中call方法的返回值 try { //get()方法返回值即为FutureTask构造器参数callable实现类重写的call方法的返回值 Object sum = futureTask.get(); System.out.println("总和是:"+sum); } catch (Exception e) { e.printStackTrace(); } } } //1.创建一个实现Callable接口的实现类 class NumThead implements Callable{ // class NumThead implements Callable<Integer>{ //2.实现call方法,将此线程需要执行的操作声明在call()中 @Override public Object call() throws Exception { //public Integer call() throws Exception { int sum=0; for(int i=1;i<=100;i++){ System.out.println(i); sum+=i; } return sum; } }
③.FutureTask原理解析
有了Runnable,为什么还要有Callable接口?我们假设一共有四个程序需要执行,第三个程序时间很长 | Runnable接口会按照顺序去执行,会依次从上到下去执行,会等第三个程序执行完毕,才去执行第四个 | Callable接口会把时间长的第三个程序单独开启一个线程去执行,第1、2、4 线程执行不受影响
比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务。子线程就去做其他的事情,过一会儿才去获取子任务的执行结果
例子: (1). 老师上着课,口渴了,去买水不合适,讲课线程继续,我可以单起个线程找班长帮忙 买水,水买回来了放桌上,我需要的时候再去get。 (2). 4个同学,A算1+20,B算21+30,C算31*到40,D算41+50,是不是C的计算量有点大啊, FutureTask单起个线程给C计算,我先汇总ABD,最后等C计算完了再汇总C,拿到最终结果 (3). 高考:会做的先做,不会的放在后面做
- ④. 注意事项
- get( )方法建议放在最后一行,防止线程阻塞(一旦调用了get( )方法,不管是否计算完成都会阻塞)
- 一个FutureTask,多个线程调用call( )方法只会调用一次
- 如果需要调用call方法多次,则需要多个FutureTask
public class CallableDemo { public static void main(String[] args) throws Exception{ CallAble c=new CallAble(); FutureTask<Integer> futureTask=new FutureTask<>(c); new Thread(futureTask,"线程A").start(); new Thread(futureTask,"线程B").start(); Integer integer = futureTask.get(); System.out.println("integer = " + integer); } } class CallAble implements Callable<Integer>{ @Override public Integer call() throws Exception { System.out.println("欢迎你调用call方法"); return 6; } }
- ⑤. isDone()轮询(后面我们会用CompletableFuture来解决get( )阻塞的问题)
- 轮询的方式会消耗无畏的CPU资源,而且也不见得能及时地得到计算的结果
- 如果想要异步获取结果,通常都会以轮询的方式去获取结果,尽量不要阻塞
public class FutureTaskTest { public static void main(String[] args) throws Exception{ FutureTask futureTask = new FutureTask(()->{ try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();} System.out.println(Thread.currentThread().getName()+"\t"+"coming......"); return 1024; }); new Thread(futureTask).start(); //1.果futureTask.get()放到main线程前面,会导致main线程阻塞 //Object o = futureTask.get(); /*Object o = futureTask.get();//不见不散,只要出现了get()方法就会阻塞 System.out.println("不见不散,只要出现了get()方法就会阻塞,获取到的值为:"+o);*/ //2.过时不候 // System.out.println(Thread.currentThread().getName()+"\t"+"线程来了....."); // Object o2 = futureTask.get(2L, TimeUnit.SECONDS); //3.使用轮询 while(true){ if(futureTask.isDone()){ System.out.println("使用轮询来解决,值为:"+futureTask.get()); break; }else{ System.out.println("阻塞中**********"); } } } }