SakuraMotion 2021 - 2025年5月
type: channel
link: https://yingwu2021.xyz/index.php/2025/05/
description: 长期的应试教育让我花了太久才明白：学习其实是一件只与自己相关的事。 现在的我，更愿意主动探索那些真正能激发好奇心、让生活变得更有趣的事物。 如果你有能够启发我、让我产生兴趣的想法、机会或故事，欢迎写信给我： （ljr0115@outlook.com） 我会优先回复那些 与我的时间安排契合、或与我当下关注方向相吻合 的邮件。
items: ThreadLocal 详解
items: 怎么避免死锁？
items: 05 Socket 网络通信实战
items: 主从复制是怎么实现的？
items: 什么是高可用？可用性的判断标准是啥？
items: 线程池介绍

ThreadLocal 详解
type: item
date: 2025-05-26T04:09:00+08:00
description: AI 摘要 文章系统剖析ThreadLocal：从弱引用key、ThreadLocalMap结构、黄金分割哈希、线性探测解决冲突，到set/get、探测式与启发式清理、扩容机制，再延伸到InheritableThreadLocal及分布式链路traceId实战，覆盖原理、源码与项目踩坑。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: ThreadLocal 详解category: Javatag:Java并发前言全文共 10000+字，31 张图，这篇文章同样耗费了不少的时间和精力才创作完成，原创不易，请大家点点关注+在看，感谢。对于ThreadLocal，大家的第一反应可能是很简单呀，线程的变量副本，每个线程隔离。那这里有几个问题大家可以思考一下：ThreadLocal的 key 是弱引用，那么在 ThreadLocal.get()的时候，发生GC之后，key 是否为null？ThreadLocal中ThreadLocalMap的数据结构？ThreadLocalMap的Hash 算法？ThreadLocalMap中Hash 冲突如何解决？ThreadLocalMap的扩容机制？ThreadLocalMap中过期 key 的清理机制？探测式清理和启发式清理流程？ThreadLocalMap.set()方法实现原理？ThreadLocalMap.get()方法实现原理？项目中ThreadLocal使用情况？遇到的坑？……上述的一些问题你是否都已经掌握的很清楚了呢？本文将围绕这些问题使用图文方式来剖析ThreadLocal的点点滴滴。目录注明： 本文源码基于JDK 1.8ThreadLocal代码演示我们先看下ThreadLocal使用示例：public class ThreadLocalTest { private List<String> messages = Lists.newArrayList(); public static final ThreadLocal<ThreadLocalTest> holder = ThreadLocal.withInitial(ThreadLocalTest::new); public static void add(String message) { holder.get().messages.add(message); } public static List<String> clear() { List<String> messages = holder.get().messages; holder.remove(); System.out.println("size: " + holder.get().messages.size()); return messages; } public static void main(String[] args) { ThreadLocalTest.add("一枝花算不算浪漫"); System.out.println(holder.get().messages); ThreadLocalTest.clear(); } }打印结果：[一枝花算不算浪漫] size: 0ThreadLocal对象可以提供线程局部变量，每个线程Thread拥有一份自己的副本变量，多个线程互不干扰。ThreadLocal的数据结构Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals，也就是说每个线程有一个自己的ThreadLocalMap。ThreadLocalMap有自己的独立实现，可以简单地将它的key视作ThreadLocal，value为代码中放入的值（实际上key并不是ThreadLocal本身，而是它的一个弱引用）。每个线程在往ThreadLocal里放值的时候，都会往自己的ThreadLocalMap里存，读也是以ThreadLocal作为引用，在自己的map里找对应的key，从而实现了线程隔离。ThreadLocalMap有点类似HashMap的结构，只是HashMap是由数组+链表实现的，而ThreadLocalMap中并没有链表结构。我们还要注意Entry， 它的key是ThreadLocal<?> k ，继承自WeakReference， 也就是我们常说的弱引用类型。GC 之后 key 是否为 null？回应开头的那个问题， ThreadLocal 的key是弱引用，那么在ThreadLocal.get()的时候，发生GC之后，key是否是null？为了搞清楚这个问题，我们需要搞清楚Java的四种引用类型：强引用：我们常常 new 出来的对象就是强引用类型，只要强引…

怎么避免死锁？
type: item
date: 2025-05-24T04:13:00+08:00
description: AI 摘要 文章介绍死锁概念、模拟代码、排查工具后，给出核心避免策略：资源有序分配法，即所有线程按统一顺序获取锁，破坏环路等待条件即可杜绝死锁。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结死锁的概念在多线程编程中，我们为了防止多线程竞争共享资源而导致数据错乱，都会在操作共享资源之前加上互斥锁，只有成功获得到锁的线程，才能操作共享资源，获取不到锁的线程就只能等待，直到锁被释放。那么，当两个线程为了保护两个不同的共享资源而使用了两个互斥锁，那么这两个互斥锁应用不当的时候，可能会造成两个线程都在等待对方释放锁，在没有外力的作用下，这些线程会一直相互等待，就没办法继续运行，这种情况就是发生了死锁。举个例子，樱舞拿了小美房间的钥匙，而樱舞在自己的房间里，小美拿了樱舞房间的钥匙，而小美也在自己的房间里。如果樱舞要从自己的房间里出去，必须拿到小美手中的钥匙，但是小美要出去，又必须拿到樱舞手中的钥匙，这就形成了死锁。死锁只有同时满足以下四个条件才会发生：互斥条件；持有并等待条件；不可剥夺条件；环路等待条件；互斥条件互斥条件是指多个线程不能同时使用同一个资源。比如下图，如果线程 A 已经持有的资源，不能再同时被线程 B 持有，如果线程 B 请求获取线程 A 已经占用的资源，那线程 B 只能等待，直到线程 A 释放了资源。持有并等待条件持有并等待条件是指，当线程 A 已经持有了资源 1，又想申请资源 2，而资源 2 已经被线程 C 持有了，所以线程 A 就会处于等待状态，但是线程 A 在等待资源 2 的同时并不会释放自己已经持有的资源 1。 不可剥夺条件不可剥夺条件是指，当线程已经持有了资源，在自己使用完之前不能被其他线程获取，线程 B 如果也想使用此资源，则只能在线程 A 使用完并释放后才能获取。环路等待条件环路等待条件指的是，在死锁发生的时候，两个线程获取资源的顺序构成了环形链。比如，线程 A 已经持有资源 2，而想请求资源 1，线程 B 已经获取了资源 1，而想请求资源 2，这就形成资源请求等待的环形图。模拟死锁问题的产生Talk is cheap. Show me the code.下面，我们用代码来模拟死锁问题的产生。首先，我们先创建 2 个线程，分别为线程 A 和 线程 B，然后有两个互斥锁，分别是 mutex_A 和 mutex_B，代码如下：pthread_mutex_t mutex_A = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex_B = PTHREAD_MUTEX_INITIALIZER; int main() { pthread_t tidA, tidB; //创建两个线程 pthread_create(&tidA, NULL, threadA_proc, NULL); pthread_create(&tidB, NULL, threadB_proc, NULL); pthread_join(tidA, NULL); pthread_join(tidB, NULL); printf("exit\n"); return 0; }接下来，我们看下线程 A 函数做了什么。//线程函数 A void *threadA_proc(void *data) { printf("thread A waiting get ResourceA \n"); pthread_mutex_lock(&mutex_A); printf("thread A got ResourceA \n"); sleep(1); printf("thread A waiting get ResourceB \n"); pthread_mutex_lock(&mutex_B); printf("thread A got ResourceB \n"); pthread_mutex_unlock(&mutex_B); pthread_mutex_unlock(&mutex_A); return (void *)0; }可以看到，线程 A 函数的过程：先获取互斥锁 A，然后睡眠 1 秒；再获取互斥锁 B，然后释放互斥锁 B；最后释放互斥锁 A；//线程函数 B void *threadB_proc(void *data) { printf("thread B waiting get ResourceB \n"); pthread_mutex_lock(&mutex_B); printf("thread B got ResourceB \n"); sleep(1); printf("thread B waiting get ResourceA \n"); pthread_mutex_lock(&mutex_A)…

05 Socket 网络通信实战
type: item
date: 2025-05-22T16:42:00+08:00
description: AI 摘要 文章通过实战演示 Java Socket 通信：服务端用 ServerSocket 监听，客户端用 Socket 连接，双方以对象流收发 Message。指出单线程只能处理一个连接的缺陷，提出线程池改进，并说明 BIO 模型局限，引出后续用 Netty 实现 NIO 的方案。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结05 Socket 网络通信实战05 Socket 网络通信实战前言guide-rpc-framework 的第一版使用的是 JDK 提供了 socket 进行网络编程。为了搞懂具体原理，我们首先要学会使用 Socket 进行网络通信。什么是 Socket(套接字)Socket 是一个抽象概念，应用程序可以通过它发送或接收数据。在使用 Socket 进行网络通信的时候，通过 Socket 就可以让我们的数据在网络中传输。操作套接字的时候，和我们读写文件很像。套接字是 IP 地址与端口的组合，套接字 Socket=（IP 地址：端口号）。要通过互联网进行通信，至少需要一对套接字：运行于服务器端的 Server Socket。运行于客户机端的 Client Socket在 Java 开发中使用 Socket 时会常用到两个类，都在 java.net 包中：Socket: 一般用于客户端ServerSocket :用于服务端Socket 网络通信过程Socket 网络通信过程如下图所示：https://www.javatpoint.com/socket-programmingSocket 网络通信过程简单来说分为下面 4 步：建立服务端并且监听客户端请求客户端请求，服务端和客户端建立连接两端之间可以传递数据关闭资源对应到服务端和客户端的话，是下面这样的。服务器端：创建 ServerSocket 对象并且绑定地址（ip）和端口号(port)：server.bind(new InetSocketAddress(host, port))通过 accept()方法监听客户端请求连接建立后，通过输入流读取客户端发送的请求信息通过输出流向客户端发送响应信息关闭相关资源客户端：创建Socket 对象并且连接指定的服务器的地址（ip）和端口号(port)：socket.connect(inetSocketAddress)连接建立后，通过输出流向服务器端发送请求信息通过输入流获取服务器响应的信息关闭相关资源Socket 网络通信实战服务端public class HelloServer { private static final Logger logger = LoggerFactory.getLogger(HelloServer.class); public void start(int port) { //1.创建 ServerSocket 对象并且绑定一个端口 try (ServerSocket server = new ServerSocket(port);) { Socket socket; //2.通过 accept()方法监听客户端请求 while ((socket = server.accept()) != null) { logger.info("client connected"); try (ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) { //3.通过输入流读取客户端发送的请求信息 Message message = (Message) objectInputStream.readObject(); logger.info("server receive message:" + message.getContent()); message.setContent("new content"); //4.通过输出流向客户端发送响应信息 objectOutputStream.writeObject(message); objectOutputStream.flush(); } catch (IOException | ClassNotFoundException e) { logger.error("occur exception:", e); } } } catch (IOException e) { logger.error("occur IOException:", e); } } pu…

主从复制是怎么实现的？
type: item
date: 2025-05-19T02:09:00+08:00
description: AI 摘要 Redis主从复制通过全量复制、长连接命令传播和增量复制三阶段实现：首次同步用RDB+缓冲写命令，后续维持TCP长连接实时传播写命令；断网重连后按repl_backlog_buffer环形缓存做增量同步，若缓存不足则退为全量同步。可设“经理”从节点分摊RDB压力，并通过哨兵完成故障自动切换。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结主从复制是怎么实现的？大家好，我是樱舞。我在前两篇已经给大家图解了 AOF 和 RDB，这两个持久化技术保证了即使在服务器重启的情况下也不会丢失数据（或少量损失）。不过，由于数据都是存储在一台服务器上，如果出事就完犊子了，比如：如果服务器发生了宕机，由于数据恢复是需要点时间，那么这个期间是无法服务新的请求的；如果这台服务器的硬盘出现了故障，可能数据就都丢失了。要避免这种单点故障，最好的办法是将数据备份到其他服务器上，让这些服务器也可以对外提供服务，这样即使有一台服务器出现了故障，其他服务器依然可以继续提供服务。多台服务器要保存同一份数据，这里问题就来了。这些服务器之间的数据如何保持一致性呢？数据的读写操作是否每台服务器都可以处理？Redis 提供了主从复制模式，来避免上述的问题。这个模式可以保证多台服务器的数据一致性，且主从服务器之间采用的是「读写分离」的方式。主服务器可以进行读写操作，当发生写操作时自动将写操作同步给从服务器，而从服务器一般是只读，并接受主服务器同步过来写操作命令，然后执行这条命令。也就是说，所有的数据修改只在主服务器上进行，然后将最新的数据同步给从服务器，这样就使得主从服务器的数据是一致的。同步这两个字说的简单，但是这个同步过程并没有想象中那么简单，要考虑的事情不是一两个。我们先来看看，主从服务器间的第一次同步是如何工作的？第一次同步多台服务器之间要通过什么方式来确定谁是主服务器，或者谁是从服务器呢？我们可以使用 replicaof（Redis 5.0 之前使用 slaveof）命令形成主服务器和从服务器的关系。比如，现在有服务器 A 和 服务器 B，我们在服务器 B 上执行下面这条命令：# 服务器 B 执行这条命令 replicaof <服务器 A 的 IP 地址> <服务器 A 的 Redis 端口号>接着，服务器 B 就会变成服务器 A 的「从服务器」，然后与主服务器进行第一次同步。主从服务器间的第一次同步的过程可分为三个阶段：第一阶段是建立链接、协商同步；第二阶段是主服务器同步数据给从服务器；第三阶段是主服务器发送新写操作命令给从服务器。为了让你更清楚了解这三个阶段，我画了一张图。接下来，我在具体介绍每一个阶段都做了什么。第一阶段：建立链接、协商同步执行了 replicaof 命令后，从服务器就会给主服务器发送 psync 命令，表示要进行数据同步。psync 命令包含两个参数，分别是主服务器的 runID 和复制进度 offset。runID，每个 Redis 服务器在启动时都会自动生产一个随机的 ID 来唯一标识自己。当从服务器和主服务器第一次同步时，因为不知道主服务器的 run ID，所以将其设置为 "?"。offset，表示复制的进度，第一次同步时，其值为 -1。主服务器收到 psync 命令后，会用 FULLRESYNC 作为响应命令返回给对方。并且这个响应命令会带上两个参数：主服务器的 runID 和主服务器目前的复制进度 offset。从服务器收到响应后，会记录这两个值。FULLRESYNC 响应命令的意图是采用全量复制的方式，也就是主服务器会把所有的数据都同步给从服务器。所以，第一阶段的工作时为了全量复制做准备。那具体怎么全量同步呀呢？我们可以往下看第二阶段。第二阶段：主服务器同步数据给从服务器接着，主服务器会执行 bgsave 命令来生成 RDB 文件，然后把文件发送给从服务器。从服务器收到 RDB 文件后，会先清空当前的数据，然后载入 RDB 文件。这里有一点要注意，主服务器生成 RDB 这个过程是不会阻塞主线程的，因为 bgsave 命令是产生了一个子进程来做生成 RDB 文件的工作，是异步工作的，这样 Redis 依然可以正常处理命令。但是，这期间的写操作命令并没有记录到刚刚生成的 RDB 文件中，这时主从服务器间的数据就不一致了。那么为了保证主从服务器的数据一致性，主服务器在下面这三个时间间隙中将收到的写操作命令，写入到 replication buffer 缓冲区里：主服务器生成 RDB 文件期间；主服务器发送 RDB 文件给从服务器期间；「从服务器」加载 RDB 文件期间；第三阶段：主服务器发送新写操作命令给从服务器在主服务器生成的 RDB 文件发送完，从服务器收到 RDB 文件后，丢弃所有旧数据，将 RDB 数据载…

什么是高可用？可用性的判断标准是啥？
type: item
date: 2025-05-13T01:45:00+08:00
description: AI 摘要 高可用指系统长期持续可服务，用“几个9”衡量，如99.9999%。提升方法：严把代码质量、集群化、限流、超时重试、熔断、异步、缓存、灰度发布、监控报警、定期换硬件等。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: 高可用系统设计指南category: 高可用icon: design什么是高可用？可用性的判断标准是啥？高可用描述的是一个系统在大部分时间都是可用的，可以为我们提供服务的。高可用代表系统即使在发生硬件故障或者系统升级的时候，服务仍然是可用的。一般情况下，我们使用多少个 9 来评判一个系统的可用性，比如 99.9999% 就是代表该系统在所有的运行时间中只有 0.0001% 的时间是不可用的，这样的系统就是非常非常高可用的了！当然，也会有系统如果可用性不太好的话，可能连 9 都上不了。除此之外，系统的可用性还可以用某功能的失败次数与总的请求次数之比来衡量，比如对网站请求 1000 次，其中有 10 次请求失败，那么可用性就是 99%。哪些情况会导致系统不可用？黑客攻击；硬件故障，比如服务器坏掉。并发量/用户请求量激增导致整个服务宕掉或者部分服务不可用。代码中的坏味道导致内存泄漏或者其他问题导致程序挂掉。网站架构某个重要的角色比如 Nginx 或者数据库突然不可用。自然灾害或者人为破坏。……有哪些提高系统可用性的方法？注重代码质量，测试严格把关我觉得这个是最最最重要的，代码质量有问题比如比较常见的内存泄漏、循环依赖都是对系统可用性极大的损害。大家都喜欢谈限流、降级、熔断，但是我觉得从代码质量这个源头把关是首先要做好的一件很重要的事情。如何提高代码质量？比较实际可用的就是 CodeReview，不要在乎每天多花的那 1 个小时左右的时间，作用可大着呢！另外，安利几个对提高代码质量有实际效果的神器：Sonarqube；Alibaba 开源的 Java 诊断工具 Arthas；阿里巴巴 Java 代码规范（Alibaba Java Code Guidelines）；IDEA 自带的代码分析等工具。使用集群，减少单点故障先拿常用的 Redis 举个例子！我们如何保证我们的 Redis 缓存高可用呢？答案就是使用集群，避免单点故障。当我们使用一个 Redis 实例作为缓存的时候，这个 Redis 实例挂了之后，整个缓存服务可能就挂了。使用了集群之后，即使一台 Redis 实例挂了，不到一秒就会有另外一台 Redis 实例顶上。限流流量控制（flow control），其原理是监控应用流量的 QPS 或并发线程数等指标，当达到指定的阈值时对流量进行控制，以避免被瞬时的流量高峰冲垮，从而保障应用的高可用性。——来自 alibaba-Sentinel 的 wiki。超时和重试机制设置一旦用户请求超过某个时间的得不到响应，就抛出异常。这个是非常重要的，很多线上系统故障都是因为没有进行超时设置或者超时设置的方式不对导致的。我们在读取第三方服务的时候，尤其适合设置超时和重试机制。一般我们使用一些 RPC 框架的时候，这些框架都自带的超时重试的配置。如果不进行超时设置可能会导致请求响应速度慢，甚至导致请求堆积进而让系统无法再处理请求。重试的次数一般设为 3 次，再多次的重试没有好处，反而会加重服务器压力（部分场景使用失败重试机制会不太适合）。熔断机制超时和重试机制设置之外，熔断机制也是很重要的。 熔断机制说的是系统自动收集所依赖服务的资源使用情况和性能指标，当所依赖的服务恶化或者调用失败次数达到某个阈值的时候就迅速失败，让当前系统立即切换依赖其他备用服务。 比较常用的流量控制和熔断降级框架是 Netflix 的 Hystrix 和 alibaba 的 Sentinel。异步调用异步调用的话我们不需要关心最后的结果，这样我们就可以用户请求完成之后就立即返回结果，具体处理我们可以后续再做，秒杀场景用这个还是蛮多的。但是，使用异步之后我们可能需要 适当修改业务流程进行配合，比如用户在提交订单之后，不能立即返回用户订单提交成功，需要在消息队列的订单消费者进程真正处理完该订单之后，甚至出库后，再通过电子邮件或短信通知用户订单成功。除了可以在程序中实现异步之外，我们常常还使用消息队列，消息队列可以通过异步处理提高系统性能（削峰、减少响应所需时间）并且可以降低系统耦合性。使用缓存如果我们的系统属于并发量比较高的话，如果我们单纯使用数据库的话，当大量请求直接落到数据库可能数据库就会直接挂掉。使用缓存缓存热点数据，因为缓存存储在内存中，所以速度相当地快！其他核心应用和服务优先使用更好的硬件监控系统资源使用情况增加报警设置。注意备份，必要时候回滚。灰度发布： 将服务器集群分成若干部分，每天只发布一部分机器，观察运行稳定没有故障，第…

线程池介绍
type: item
date: 2025-05-11T22:06:00+08:00
description: AI 摘要 文章系统梳理Java线程池：从池化价值、Executor框架、ThreadPoolExecutor七大参数与饱和策略，到execute流程与addWorker源码，对比Runnable/Callable、execute/submit、shutdown/shutdownNow，剖析Fixed、Single、Cached、Scheduled四种内置池的缺陷，给出ThreadPoolExecutor手动创建及阻塞队列选型最佳实践。 此摘要根据文章内容自动生成，仅用于文章内容的解释与总结title: Java 线程池详解category: Javatag:Java并发池化技术想必大家已经屡见不鲜了，线程池、数据库连接池、HTTP 连接池等等都是对这个思想的应用。池化技术的思想主要是为了减少每次获取资源的消耗，提高对资源的利用率。这篇文章我会详细介绍一下线程池的基本概念以及核心原理。线程池介绍顾名思义，线程池就是管理一系列线程的资源池，其提供了一种限制和管理线程资源的方式。每个线程池还维护一些基本统计信息，例如已完成任务的数量。这里借用《Java 并发编程的艺术》书中的部分内容来总结一下使用线程池的好处：降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。提高响应速度。当任务到达时，任务可以不需要等到线程创建就能立即执行。提高线程的可管理性。线程是稀缺资源，如果无限制的创建，不仅会消耗系统资源，还会降低系统的稳定性，使用线程池可以进行统一的分配，调优和监控。线程池一般用于执行多个不相关联的耗时任务，没有多线程的情况下，任务顺序执行，使用了线程池的话可让多个不相关联的任务同时执行。Executor 框架介绍Executor 框架是 Java5 之后引进的，在 Java 5 之后，通过 Executor 来启动线程比使用 Thread 的 start 方法更好，除了更易管理，效率更好（用线程池实现，节约开销）外，还有关键的一点：有助于避免 this 逃逸问题。this 逃逸是指在构造函数返回之前其他线程就持有该对象的引用，调用尚未构造完全的对象的方法可能引发令人疑惑的错误。Executor 框架不仅包括了线程池的管理，还提供了线程工厂、队列以及拒绝策略等，Executor 框架让并发编程变得更加简单。Executor 框架结构主要由三大部分组成：1、任务(Runnable /Callable)执行任务需要实现的 Runnable 接口 或 Callable接口。Runnable 接口或 Callable 接口 实现类都可以被 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。2、任务的执行(Executor)如下图所示，包括任务执行机制的核心接口 Executor ，以及继承自 Executor 接口的 ExecutorService 接口。ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 这两个关键类实现了 ExecutorService 接口。这里提了很多底层的类关系，但是，实际上我们需要更多关注的是 ThreadPoolExecutor 这个类，这个类在我们实际使用线程池的过程中，使用频率还是非常高的。注意： 通过查看 ScheduledThreadPoolExecutor 源代码我们发现 ScheduledThreadPoolExecutor 实际上是继承了 ThreadPoolExecutor 并实现了 ScheduledExecutorService ，而 ScheduledExecutorService 又实现了 ExecutorService，正如我们上面给出的类关系图显示的一样。ThreadPoolExecutor 类描述://AbstractExecutorService实现了ExecutorService接口 public class ThreadPoolExecutor extends AbstractExecutorServiceScheduledThreadPoolExecutor 类描述://ScheduledExecutorService继承ExecutorService接口 public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService3、异步计算的结果(Future)Future 接口以及 Future 接口的实现类 FutureTask 类都可以代表异步计算的结果。当我们把 Runnable接口 或 Callable 接口 的实现类提…