在面试的时候, 有时候是不是会遇到面试会问你, Java 中实现多线程的方式有几种? 你知道吗? 你知道 Java 中有可以返回值的线程吗? 在具体的用法你知道吗? 如果两个线程同时来调用同一个计算对象, 计算对象的 call 方法会被调用几次你知道吗? 如果这些你知道, 那么凯哥 (凯哥 Java:kaigejava) 恭喜你, 本文你可以不用看了. 如果你不知道这些, 那么凯哥同样要恭喜你, 看了凯哥这篇文章之后, 就知道这些了. 来看看这篇文章我们能学到什么
本节主要内容
一: 三种获取多线程的的写法
二: 分析第三种写法的思想思路 - 使用了适配器模式
三: 第三种方法怎么使用
四: 多个线程调用同一个 futrueTask 后, future 的 call 方法会被执行几次?
一: 三种获取线程的写法
我们已经知道 Java 中常用的两种线程实现方式: 分别是继承 Thread 类和实现 Runnable 接口.
如下图:
从上图中, 我们可以看到, 第三种实现 Callable 接口的线程, 而且还带有返回值的. 我们来对比下实现 Runnable 和实现 Callable 接口的两种方式不同点:
1: 需要实现的方法名称不一样: 一个 run 方法, 一个 call 方法
2: 返回值不同: 一个 void 无返回值, 一个带有返回值的. 其中返回值的类型和泛型 V 是一致的.
3: 异常: 一个无需抛出异常, 一个需要抛出异常. 在后面使用场景中, 凯哥会讲解到的
二: callable 接口的设计思路?
我们先来看看 Thread 类: 这个类是 Java 中获取线的对象. 一般我们获取并启动线程调用的是 start 方. 从 JDK 的 API 中, 我们可以看到, start 方法是 JVM 调用的
再来看看常写的方法:
- Thread t1 = new Thread();
- t1.start();
我们来看看其构造器:
三个构造器: 无参构造器, 一个参数构造器和两个参数构造器. 但是就没有我们 Callable 作为参数的构造器. 那么, 我们想要获取到线程, 通过 callable 怎么获取呢 ?
就拿凯哥刚到帝都找房子的案例来说吧. 凯哥刚到帝都人生地不熟的, 想要找房子怎么办呢?
房东有房子, 凯哥想要找房子, 那么这两个本来没有直接联系的通过房屋中介公司就产生了关系. 凯哥要想找到房子, 先要找到房屋中介公司, 然后房屋中介公司又有房东的联系方式, 然后凯哥就通过中介公司租到房东的房子了(中介公司从中间收取手续费). 这个现实案例我想大家都遇到过吧.
好了, 我们通过上面案例在回到 Thread 类和 Callable 类来看, 这两个对象之间有没有中间商呢?
从上图中我们发现, Threa 的有参构造都是 Runnable 接口的. 那么, 有没有一个类既实现了 Runnable 接口又实现了 Callable 接口呢? 如果有这样的一个类存在的话, callable 就与 Thread 类产生了关系, 就可以使用了. 我们来看看 Runnable 接口的 API 吧
我们可以已知的子类有个 RunnableFuture<V>. 这个接口的形式和我们 Callable 接口的形式很像啊, 如下图:
我们从上图对比中可以看到, 两个接口中的 V 都是方法返回值的类型. 那么 Callable 和 Thread 两个类之间的桥梁就是这个类 (RunnableFuture) 或者是这个类的子类呢? 我们接着来看看这个对象的子类.
其中 SwingWorker 这个我们不用看. 这个是图形化的 Swing 相关的. 我们不用, 那么我们就来看看 FutureTask 这个类:
从这个类中, 我们可以看到其实现了 Runnable 接口, 在构造器中, 我们可以看到:
FutureTask(Callable<V> callable)
创建一个 FutureTask , 它将在运行时执行给定的 Callable .
如下图:
这个类是不是既有 Callable 接口又有 Runnable 接口了? 这个就是我们的中间类.
所以, 我们通过上面分析就可以得到下图的关系:
这种就是设计模式中的适配器模式(PS: 在后面, 凯哥会重新分享 23 种设计模式的). 在 Java 中的中间商是不会赚取差价的, 放心. O(∩_∩)O
三: callable 怎么使用及怎么获取返回值
知道了 Callable 的设计思路之后, 那么我们怎么来使用呢?
步骤:
1: 同样创建一个类实现 Callable 接口;
2: 通过 futureTask 类使用其传递 Callable 接口作为参数的有参构造方法;
3: 使用 thread 的有参构造;
4:t1.start()启动线程
5: 启动线程后, 通过 futureTask.get()方法获取到线程的返回值.
如下图:
我们来查看运行结果:
进入了 callable 接口且获取到了返回值: 1024. 说明 callable 的使用正确了.
需要注意: futrueTask.get()方法放到最后, 这样就不会影响主线程了. 如果 get 方法放在前面的话, 会造成主线程阻塞, 等到 futrueTask 运行完成之后, 才继续执行自己的逻辑. 这样就失去了开启线程的意义了!!!
四: 多个线程同时调用结果
我们可以看到 t1 和 t2 都 start 了, 说明两个线程都启动了. 而且都是用的是同一个 futureTask 对象. 问题: MyThread3 中的 call 方法会被调用几次呢?
来源: https://www.cnblogs.com/kaigejava/p/12900055.html