作者 | Java圣斗士 | 原创图文,转载请注明出处

全文3500字,阅读需要15分钟,建议收藏

runnable实现的方法(Runnable定义任务跑完就拉倒)(1)

哈喽大家好,我是又皮又Demo的Java圣斗士,今天又给大家带干货来了!快来关注一波!

有一些小伙伴问了,圣斗士呀,你咋总写一些多线程的东西啊?这些东西日常开发都用不上的呀,知道实现线程的方式有两种,一种是继承Thread,另一种是实现Runnable接口不就OK了吗?!

对这些小伙伴我只能说,TOO YOUNG ! TOO SIMPLE !

runnable实现的方法(Runnable定义任务跑完就拉倒)(2)

在这里我要纠正这些抱着这样想法的小盆友,如果你想在技术路上走的更远,多线程是一道必过的坎!我们在开发业务的时候用不到,是因为应用的场景太过简单,无需考虑性能问题,一旦某些简单的业务逻辑增加一些并发的需要、性能的提升,那么你就得GAME OVER!

所以,多线程不仅要学,还要学好!并发的知识可不仅仅是用来应试的花拳绣腿,有了多线程的手艺,走到哪都是个宝,我就把话撂这了!

进入正题,今天给大家讲解一下Runnable的一奶同胞——Callable

这个东西实际上就是带尾巴的Runnable。

runnable实现的方法(Runnable定义任务跑完就拉倒)(3)

什么意思呢?原来Callable接口有一个和run()方法类似的call()方法,不同的是,这个call()可以返回一个值,我们可以通过类型参数来定义返回的类型

runnable实现的方法(Runnable定义任务跑完就拉倒)(4)

有点小伙伴可能就会想了,哎?这个东西居然可以有返回值?那岂不是可以为所欲为?!

呵呵,先别高兴太早,这个东西要想玩得666,还得和前面的知识融合在一起才行。废话不多说,先来简单感受一下:

public static void main(String[] args) { Callable<String> callTask = new Callable<String>() { @Override public String call() throws Exception { return "我是一个Demo的Java圣斗士"; } }; ExecutorService fixedPool = Executors.newFixedThreadPool(3); Future<String> submit = fixedPool.submit(callTask); try { String result = submit.get(); System.out.println(result); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } finally { fixedPool.shutdown(); } }

输出结果:

我是一个Demo的Java圣斗士

咋样?挺高端的吧!上述代码中,我们先是通过匿名类来实现了Callable接口并成功实例化了一个对象callTask。然后,通过昨天介绍的Executors的工厂方法newFixedThreadPool获得一个定额线程池执行器服务fixedPool,通过submit()方法,提交callTask,然后通过具有“状态依赖”的get()方法来获得结果,最后输出,同时捕获受检异常,并在finally块中终止线程池。

Callable对象的创建有两种方法,一种是像上述代码的构造器方式,这种方式在Java 8 后还有另一种写法:

Callable<String> callTask = () -> { return "我是一个Demo的Java圣斗士"; };

这个叫Lambda表达式,是Java 8加入Java语法的正规军,还不知道咋回事的小伙伴,欢迎回看往期文章《大话Java 8 Lambda 表达式(一)》。另一种创建Callable对象的方式是通过一个静态方法:

Callable<String> callTask = Executors.callable(() -> { // do something }, "我是一个Demo的Java圣斗士");

不过我不太推荐这种写法,弊端很明显,返回值需要写在第二个参数上,而并不是通过方法体计算而得,局限性太强。

创建完Callable对象,紧接着,我们通过一个线程池来提交callTask(有对线程池不是很了解的,可以查看《线程池?不懂也会被问到的任务执行器》)有人可能又要问,为什么不是像方法调用那样直接获取返回值?而是要提交给线程池呢?这是因为任务的执行有一个完整的生命周期,它的创建,执行,终止无法精确地得到控制,我们通过submit()方法提交任务,并因此获得一个对任务生命周期进行管理的Future对象,才能将单独分配出一个线程的任务加以管理,这一点和普通的方法调用是截然不同的,前者是多个线程并行执行任务,而后者属于同一个线程。

Future代表未来,它在其语法规范中隐含表示了任务的生命周期只能前进,不能后退,而get方法具有“状态依赖”的内在特性,调用者不需要知道任务的状态,如果任务已经执行完毕,那么get会直接返回一个结果,如果还没有执行完毕,那么就会一直等待直到得出结果(可以根据需要设置超时等待时间的重载方法)。

上述代码是一个非常典型的Callable和Future的应用案例。如果你觉得太麻烦,也许你应该听听FutureTask的故事,FutureTask间接实现了Runnable和Funture<V>两大接口,让你一个对象就可以解决运行任务并接收返回值两大功效,真是完美,来看看它的应用:

public static void main(String[] args) { FutureTask<String> callFutureTask = new FutureTask<>(() -> { return "Hello world!!!"; }); callFutureTask.run(); try { String result = callFutureTask.get(); System.out.println(result); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }

输出结果:

Hello world!!!

不需要什么线程池,不需要什么submit(),想运行任务?直接执行run(),想得到结果?那就直接get()!

到此为止,你已经掌握了有返回值的任务执行方案,可以说已经能够解决绝大多数的执行任务的场景,可是FutureTask是怎么做到的呢?如果你还想继续提升,请接着往下看。

runnable实现的方法(Runnable定义任务跑完就拉倒)(5)

在FutureTask中依赖了一个Callable<V>对象,并定义了一个可变的state,这个state在创建FutureTask之初会设置为0 (static final int NEW = 0):

/** * Creates a {@code FutureTask} that will, upon running, execute the * given {@code Callable}. * * @param callable the callable task * @throws NullPointerException if the callable is null */ public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable }

然后在执行run的时候,调用call方法,并将结果保存在对象属性outcome中。

public void run() { // 省略一些校验逻辑 try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { result = c.call(); ran = true; } catch(...) {...} if (ran) set(result); } } }

然后,当我们调用get()的时候,FutureTask就会进行一些逻辑判断,然后取得这个结果:

public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); Object x = outcome; if (s == NORMAL) return (V)x; }

所以,才会有了这样一个双管齐下的FutureTask方便更多的开发者,真的不得不佩服Doug Lea老爷子的水平!来摩拜一下老爷子的画像:

runnable实现的方法(Runnable定义任务跑完就拉倒)(6)

今天的知识分享就到这里了。希望小伙伴通过今天的学习能够在激起并发学习的兴趣。非常感谢大家的阅读。

往期精彩:

《如何计算2 * 8?100%的面试官都想要这样的答案》

《Comparable、Comparator傻傻分不清?那是你还没遇到我》

《最完整类加载顺序总结,只需要这一篇就足够了》

---欢迎关注【Java圣斗士】,我是你们的小可爱(✪ω✪) Morty---

---专注IT职场经验、IT技术分享的灵魂写手---

---每天带你领略IT的魅力---

---期待与您陪伴!---

,