java线程-如何优雅地获取线程的执行结果

╰+攻爆jí腚メ 2023-10-13 20:48 117阅读 0赞

为什么要使用Future

线程获取到运行结果有几种方式

  1. java复制代码public class Sum {
  2. private Sum(){}
  3. public static int sum(int n){
  4. int sum = 0;
  5. for (int i = 0; i < n; i++) {
  6. sum += n;
  7. }
  8. return sum;
  9. }
  10. }

Thread.sleep()

  1. java复制代码private static int sum_sleep = 0;
  2. Thread thread = new Thread(() -> sum_sleep = Sum.sum(100));
  3. thread.start();
  4. TimeUnit.SECONDS.sleep(1);
  5. System.out.printf("get result by thread.sleep: %d\n", sum_sleep);

使用sleep()方法获取,这种方法,有不可控性,也许sleep1秒钟,但是线程还没有执行完成,可能会导致获取到的结果不准确。

Thread.join()

  1. java复制代码private static int sum_join = 0;
  2. Thread thread = new Thread(() -> sum_join = Sum.sum(100));
  3. thread.start();
  4. thread.join();
  5. System.out.printf("get result by thread.join: %d\n", sum_join);

循环

  1. java复制代码private static int sum_loop = 0;
  2. private static volatile boolean flag;
  3. Thread thread = new Thread(() -> {
  4. sum_loop = Sum.sum(100);
  5. flag = true;
  6. });
  7. thread.start();
  8. int i = 0;
  9. while (!flag) {
  10. i++;
  11. }
  12. System.out.printf("get result by loopLock: %d\n", sum_loop);

notifyAll() / wait()

  1. java复制代码private static class NotifyAndWaitTest {
  2. private Integer sum = null;
  3. private synchronized void sum_wait_notify() {
  4. sum = Sum.sum(100);
  5. notifyAll();
  6. }
  7. private synchronized Integer getSum() {
  8. while (sum == null) {
  9. try {
  10. wait();
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. return sum;
  16. }
  17. }
  18. private static void getResultByNotifyAndWait() throws Exception {
  19. NotifyAndWaitTest test = new NotifyAndWaitTest();
  20. new Thread(test::sum_wait_notify).start();
  21. System.out.printf("get result by NotifyAndWait: %d\n", test.getSum());
  22. }

Lock & Condition

  1. java复制代码private static class LockAndConditionTest {
  2. private Integer sum = null;
  3. private final Lock lock = new ReentrantLock();
  4. private final Condition condition = lock.newCondition();
  5. public void sum() {
  6. try {
  7. lock.lock();
  8. sum = Sum.sum(100);
  9. condition.signalAll();
  10. } catch (Exception e) {
  11. e.printStackTrace();
  12. } finally {
  13. lock.unlock();
  14. }
  15. }
  16. public Integer getSum() {
  17. try {
  18. lock.lock();
  19. while (Objects.isNull(sum)) {
  20. try {
  21. condition.await();
  22. } catch (Exception e) {
  23. throw new RuntimeException(e);
  24. }
  25. }
  26. } catch (Exception e) {
  27. e.printStackTrace();
  28. } finally {
  29. lock.unlock();
  30. }
  31. return sum;
  32. }
  33. }
  34. private static void getResultByLockAndCondition() throws Exception {
  35. LockAndConditionTest test = new LockAndConditionTest();
  36. new Thread(test::sum).start();
  37. System.out.printf("get result by lock and condition: %d\n", test.getSum());
  38. }

BlockingQueue

  1. java复制代码BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1);
  2. new Thread(() -> queue.offer(Sum.sum(100))).start();
  3. System.out.printf("get result by blocking queue: %d\n", queue.take());

CountDownLatch

  1. java复制代码private static int sum_countDownLatch = 0;
  2. private static void getResultByCountDownLatch() {
  3. CountDownLatch latch = new CountDownLatch(1);
  4. new Thread(
  5. () -> {
  6. sum_countDownLatch = Sum.sum(100);
  7. latch.countDown();
  8. })
  9. .start();
  10. try {
  11. latch.await();
  12. } catch (Exception e) {
  13. e.printStackTrace();
  14. }
  15. System.out.printf("get result by countDownLatch: %d\n", sum_countDownLatch);
  16. }

CyclicBarrier

  1. java复制代码private static int sum_cyclicBarrier = 0;
  2. private static void getResultByCycleBarrier() {
  3. CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
  4. new Thread(
  5. () -> {
  6. sum_cyclicBarrier = Sum.sum(100);
  7. try {
  8. cyclicBarrier.await();
  9. } catch (Exception e) {
  10. e.printStackTrace();
  11. }
  12. })
  13. .start();
  14. try {
  15. cyclicBarrier.await();
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. }
  19. System.out.printf("get result by cyclicBarrier: %d\n", sum_cyclicBarrier);
  20. }

Semaphore

  1. java复制代码private static int sum_semaphore = 0;
  2. private static void getResultBySemaphore() {
  3. Semaphore semaphore = new Semaphore(0);
  4. new Thread(
  5. () -> {
  6. sum_semaphore = Sum.sum(100);
  7. semaphore.release();
  8. })
  9. .start();
  10. try {
  11. semaphore.acquire();
  12. System.out.printf("get result by semaphore: %d\n", sum_semaphore);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }

上面提到的获取线程执行结果的方法,暂时基于之前学到的内容,我只能想到这些。这些实现方式也不是很优雅,不是最佳实践。

线程池,利用ThreadPoolExecutor的execute(Runnable command)方法,利用这个方法虽说可以提交任务,但是却没有办法获取任务执行结果。

那么我们如果需要获取任务的执行结果并且优雅的实现,可以通过Future接口和Callable接口配合实现, 本文将会通过具体的例子讲解如何使用Future。

Future最主要的作用是,比如当做比较耗时运算的时候,如果我们一直在原地等待方法返回,显然是不明智的,整体程序的运行效率会大大降低。我们可以把运算的过程放到子线程去执行,再通过Future去控制子线程执行的计算过程,最后获取到计算结果。这样一来就可以把整个程序的运行效率提高,是一种异步的思想。

如何使用Future

要想使用Future首先得先了解一下Callable。Callable 接口相比于 Runnable 的一大优势是可以有返回结果,那这个返回结果怎么获取呢?就可以用 Future 类的 get 方法来获取 。因此,Future 相当于一个存储器,它存储了 Callable 的call方法的任务结果。

一般情况下,Future,Callable,ExecutorService是一起使用的,ExecutorService里相关的代码如下:

  1. java复制代码// 提交 Runnable 任务
  2. // 由于Runnable接口的run方法没有返回值,所以,Future仅仅是用来断言任务已经结束,有点类似join();
  3. Future<?> submit(Runnable task);
  4. // 提交 Callable 任务
  5. // Callable里的call方法是有返回值的,所以这个方法返回的Future对象可以通过调用其get()方法来获取任务的执
  6. //行结果。
  7. <T> Future<T> submit(Callable<T> task);
  8. // 提交 Runnable 任务及结果引用
  9. // Future的返回值就是传给submit()方法的参数result。
  10. <T> Future<T> submit(Runnable task, T result);

具体使用方法如下:

  1. java复制代码ExecutorService executor = Executors.newCachedThreadPool();
  2. Future<Integer> future = executor.submit(() -> Sum.sum(100));
  3. System.out.printf("get result by Callable + Future: %d\n", future.get());
  4. executor.shutdown();

Future实现原理

Future基本概述

Future接口5个方法:

  1. java复制代码// 取消任务
  2. boolean cancel(boolean mayInterruptIfRunning);
  3. // 判断任务是否已取消
  4. boolean isCancelled();
  5. // 判断任务是否已结束
  6. boolean isDone();
  7. // 获得任务执行结果 阻塞,被调用时,如果任务还没有执行完,那么调用get()方法的线程会阻塞。直到任务执行完
  8. // 才会被唤醒
  9. get();
  10. // 获得任务执行结果,支持超时
  11. get(long timeout, TimeUnit unit);
  • cancel(boolean mayInterruptIfRunning):

    • 用来取消异步任务的执行。
    • 如果异步任务已经完成或者已经被取消,或者由于某些原因不能取消,则会返回false。
    • 如果任务还没有被执行,则会返回true并且异步任务不会被执行。
    • 如果任务已经开始执行了但是还没有执行完成,若mayInterruptIfRunning为true,则会立即中断执行任务的线程并返回true,若mayInterruptIfRunning为false,则会返回true且不会中断任务执行线程。
  • isCanceled():

    • 判断任务是否被取消。
    • 如果任务在结束(正常执行结束或者执行异常结束)前被取消则返回true,否则返回false。
  • isDone():

    • ·判断任务是否已经完成,如果完成则返回true,否则返回false。
    • 任务执行过程中发生异常、任务被取消也属于任务已完成,也会返回true。
  • get():

    • 获取任务执行结果,如果任务还没完成则会阻塞等待直到任务执行完成。
    • 如果任务被取消则会抛出CancellationException异常。
    • 如果任务执行过程发生异常则会抛出ExecutionException异常。
    • 如果阻塞等待过程中被中断则会抛出InterruptedException异常。
  • get(long timeout,Timeunit unit):

    • 带超时时间的get()版本,上面讲述的get()方法,同样适用这里。
    • 如果阻塞等待过程中超时则会抛出TimeoutException异常。

使用IDEA,查看Future的实现类其实有很多,比如FutureTask,ForkJoinTask,CompletableFuture等,其余基本是继承了ForkJoinTask实现的内部类。

79ef790ec04a4cc6bd949b583b5aece

本篇文章主要讲解FutureTask的实现原理

FutureTask基本概述

FutureTask 为 Future 提供了基础实现,如获取任务执行结果(get)和取消任务(cancel)等。如果任务尚未完成,获取任务执行结果时将会阻塞。一旦执行结束,任务就不能被重启或取消(除非使用runAndReset执行计算)。FutureTask 常用来封装 Callable 和 Runnable,也可以作为一个任务提交到线程池中执行。除了作为一个独立的类之外,此类也提供了一些功能性函数供我们创建自定义 task 类使用。FutureTask 的线程安全由CAS来保证。

  1. java复制代码// 创建 FutureTask
  2. FutureTask<Integer> futureTask = new FutureTask<>(()-> 1+2);
  3. // 创建线程池
  4. ExecutorService es = Executors.newCachedThreadPool();
  5. // 提交 FutureTask
  6. es.submit(futureTask);
  7. // 获取计算结果
  8. Integer result = futureTask.get();
  9. java复制代码// 创建 FutureTask
  10. FutureTask<Integer> futureTask
  11. = new FutureTask<>(()-> 1+2);
  12. // 创建并启动线程
  13. Thread T1 = new Thread(futureTask);
  14. T1.start();
  15. // 获取计算结果
  16. Integer result = futureTask.get();

FutureTask可以很容易获取子线程的执行结果。

FutureTask实现原理

构造函数

  1. java复制代码public FutureTask(Callable<V> callable) {
  2. if (callable == null)
  3. throw new NullPointerException();
  4. this.callable = callable;
  5. this.state = NEW; // ensure visibility of callable
  6. }
  7. public FutureTask(Runnable runnable, V result) {
  8. this.callable = Executors.callable(runnable, result);
  9. this.state = NEW; // ensure visibility of callable
  10. }

FutureTask提供了两个构造器

  • Callable接口有返回,将callable赋值给this.callable。

  • Runnable接口无返回,如果想要获取到执行结果,需要传V result给FutureTask,FutureTask将Runnable和result封装成Callable,再将callable赋值给this.callable。

  • 状态初始化状态为NEW

FutureTask内置状态有:

  1. java复制代码private volatile int state; // 可见性
  2. private static final int NEW = 0;
  3. private static final int COMPLETING = 1;
  4. private static final int NORMAL = 2;
  5. private static final int EXCEPTIONAL = 3;
  6. private static final int CANCELLED = 4;
  7. private static final int INTERRUPTING = 5;
  8. private static final int INTERRUPTED = 6;
  • NEW 初始状态
  • COMPLETING 任务已经执行完(正常或者异常),准备赋值结果,但是这个状态会时间会比较短,属于中间状态。
  • NORMAL 任务已经正常执行完,并已将任务返回值赋值到结果
  • EXCEPTIONAL 任务执行失败,并将异常赋值到结果
  • CANCELLED 取消
  • INTERRUPTING 准备尝试中断执行任务的线程
  • INTERRUPTED 对执行任务的线程进行中断(未必中断到)

状态转换

Future状态转移.png

get_code_ZTJjYzZmOTcwYTg4YmNmZTA5NzlhYjQ5MzQzYzY5MDQsMTY4OTgyMzYwODUxMg

run()执行流程

  1. java复制代码public void run() {
  2. if (state != NEW ||
  3. !RUNNER.compareAndSet(this, null, Thread.currentThread()))
  4. return;
  5. try {
  6. Callable<V> c = callable;
  7. if (c != null && state == NEW) {
  8. V result;
  9. boolean ran;
  10. try {
  11. result = c.call();
  12. ran = true;
  13. } catch (Throwable ex) {
  14. result = null;
  15. ran = false;
  16. setException(ex);
  17. }
  18. if (ran)
  19. set(result);
  20. }
  21. } finally {
  22. // runner must be non-null until state is settled to
  23. // prevent concurrent calls to run()
  24. runner = null;
  25. // state must be re-read after nulling runner to prevent
  26. // leaked interrupts
  27. int s = state;
  28. if (s >= INTERRUPTING)
  29. handlePossibleCancellationInterrupt(s);
  30. }
  31. }

format_png

set()

  1. java复制代码protected void set(V v) {
  2. // state变量,通过CAS操作,将NEW->COMPLETING
  3. if (STATE.compareAndSet(this, NEW, COMPLETING)) {
  4. // 将结果赋值给outcome属性
  5. outcome = v;
  6. // state状态直接赋值为NORMAL,不需要CAS
  7. STATE.setRelease(this, NORMAL); // final state
  8. finishCompletion();
  9. }
  10. }

setException()

  1. java复制代码protected void setException(Throwable t) {
  2. // state变量,通过CAS操作,将NEW->COMPLETING
  3. if (STATE.compareAndSet(this, NEW, COMPLETING)) {
  4. // 将异常赋值给outcome属性
  5. outcome = t;
  6. // state状态直接赋值为EXCEPTIONAL,不需要CAS
  7. STATE.setRelease(this, EXCEPTIONAL); // final state
  8. finishCompletion();
  9. }
  10. }

finishCompletion()

set()和setException()两个方法最后都调用了finishCompletion()方法,完成一些善后工作,具体流程如下:

  1. java复制代码private void finishCompletion() {
  2. // assert state > COMPLETING;
  3. for (WaitNode q; (q = waiters) != null;) {
  4. // 移除等待线程
  5. if (WAITERS.weakCompareAndSet(this, q, null)) {
  6. // 自旋遍历等待线程
  7. for (;;) {
  8. Thread t = q.thread;
  9. if (t != null) {
  10. q.thread = null;
  11. // 唤醒等待线程
  12. LockSupport.unpark(t);
  13. }
  14. WaitNode next = q.next;
  15. if (next == null)
  16. break;
  17. q.next = null; // unlink to help gc
  18. q = next;
  19. }
  20. break;
  21. }
  22. }
  23. // 任务完成后调用函数,自定义扩展
  24. done();
  25. callable = null; // to reduce footprint
  26. }

handlePossibleCancellationInterrupt()

  1. java复制代码private void handlePossibleCancellationInterrupt(int s) {
  2. if (s == INTERRUPTING)
  3. // 在中断者中断线程之前可能会延迟,所以我们只需要让出CPU时间片自旋等待
  4. while (state == INTERRUPTING)
  5. Thread.yield(); // wait out pending interrupt
  6. }

get()执行流程

  1. java复制代码public V get() throws InterruptedException, ExecutionException {
  2. int s = state;
  3. if (s <= COMPLETING)
  4. // awaitDone用于等待任务完成,或任务因为中断或超时而终止。返回任务的完成状态。
  5. s = awaitDone(false, 0L);
  6. return report(s);
  7. }

具体流程:

FutureTask-执行get().png

get_code_Y2U5NmRmZjk4MjYxYTE2MmE0NjY5ZTllYzgwNTUyN2YsMTY4OTgyMzYwODUxMg

awaitDone()

  1. java复制代码private int awaitDone(boolean timed, long nanos)
  2. throws InterruptedException {
  3. long startTime = 0L; // Special value 0L means not yet parked
  4. WaitNode q = null;
  5. boolean queued = false;
  6. for (;;) {
  7. // 获取到当前状态
  8. int s = state;
  9. // 如果当前状态不为NEW或者COMPLETING
  10. if (s > COMPLETING) {
  11. if (q != null)
  12. q.thread = null;
  13. // 直接返回state
  14. return s;
  15. }
  16. // COMPLETING是一个很短暂的状态,调用Thread.yield期望让出时间片,之后重试循环。
  17. else if (s == COMPLETING)
  18. Thread.yield();
  19. // 如果阻塞线程被中断则将当前线程从阻塞队列中移除
  20. else if (Thread.interrupted()) {
  21. removeWaiter(q);
  22. throw new InterruptedException();
  23. }
  24. // 新进来的线程添加等待节点
  25. else if (q == null) {
  26. if (timed && nanos <= 0L)
  27. return s;
  28. q = new WaitNode();
  29. }
  30. else if (!queued)
  31. /*
  32. * 这是Treiber Stack算法入栈的逻辑。
  33. * Treiber Stack是一个基于CAS的无锁并发栈实现,
  34. * 更多可以参考https://en.wikipedia.org/wiki/Treiber_Stack
  35. */
  36. queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q);
  37. else if (timed) {
  38. final long parkNanos;
  39. if (startTime == 0L) { // first time
  40. startTime = System.nanoTime();
  41. if (startTime == 0L)
  42. startTime = 1L;
  43. parkNanos = nanos;
  44. } else {
  45. long elapsed = System.nanoTime() - startTime;
  46. if (elapsed >= nanos) {
  47. // 超时,移除栈中节点。
  48. removeWaiter(q);
  49. return state;
  50. }
  51. parkNanos = nanos - elapsed;
  52. }
  53. // nanoTime may be slow; recheck before parking
  54. // 未超市并且状态为NEW,阻塞当前线程
  55. if (state < COMPLETING)
  56. LockSupport.parkNanos(this, parkNanos);
  57. }
  58. else
  59. LockSupport.park(this);
  60. }
  61. }

removeWaiter()

  1. java复制代码private void removeWaiter(WaitNode node) {
  2. if (node != null) {
  3. node.thread = null;
  4. retry:
  5. for (;;) { // restart on removeWaiter race
  6. for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
  7. s = q.next;
  8. // 如果当前节点仍有效,则置pred为当前节点,继续遍历。
  9. if (q.thread != null)
  10. pred = q;
  11. /*
  12. * 当前节点已无效且有前驱,则将前驱的后继置为当前节点的后继实现删除节点。
  13. * 如果前驱节点已无效,则重新遍历waiters栈。
  14. */
  15. else if (pred != null) {
  16. pred.next = s;
  17. if (pred.thread == null) // check for race
  18. continue retry;
  19. }
  20. /*
  21. * 当前节点已无效,且当前节点没有前驱,则将栈顶置为当前节点的后继。
  22. * 失败的话重新遍历waiters栈。
  23. */
  24. else if (!WAITERS.compareAndSet(this, q, s))
  25. continue retry;
  26. }
  27. break;
  28. }
  29. }
  30. }

report()

  1. java复制代码private V report(int s) throws ExecutionException {
  2. Object x = outcome;
  3. if (s == NORMAL)
  4. return (V)x;
  5. if (s >= CANCELLED)
  6. throw new CancellationException();
  7. throw new ExecutionException((Throwable)x);
  8. }

cancel()执行流程

  1. java复制代码public boolean cancel(boolean mayInterruptIfRunning) {
  2. // 状态机不是NEW 或CAS更新状态 流转到INTERRUPTING或者CANCELLED失败,不允许cancel
  3. if (!(state == NEW && STATE.compareAndSet
  4. (this, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
  5. return false;
  6. try { // in case call to interrupt throws exception
  7. // 如果要求中断执行中的任务,则直接中断任务执行线程,并更新状态机为最终状态INTERRUPTED
  8. if (mayInterruptIfRunning) {
  9. try {
  10. Thread t = runner;
  11. if (t != null)
  12. t.interrupt();
  13. } finally { // final state
  14. STATE.setRelease(this, INTERRUPTED);
  15. }
  16. }
  17. } finally {
  18. finishCompletion();
  19. }
  20. return true;
  21. }

经典案例

引用极客时间-java并发编程课程的案例烧水泡茶:

Future-任务分工图.png

get_code_Nzg5ZmM1ODk5ZTA5MGFiNGVjMDVjZGMxZDA1MzUxOGYsMTY4OTgyMzYwODUxMg

并发编程可以总结为三个核心问题:分工,同步和互斥。编写并发程序,首先要做分工。

  1. T1负责洗水壶,烧开水,泡茶这三道工序
  2. T2负责洗茶壶,洗茶杯,拿茶叶三道工序。
  3. T1在执行泡茶这道工序需要等到T2完成拿茶叶的工作。(join,countDownLatch,阻塞队列都可以完成)

Future-关系图.png

get_code_NWVmM2I3OTE1M2E1NmEzMWMwMDBkNDNlMzYyN2IzZTgsMTY4OTgyMzYwODUxMg

  1. java复制代码// 创建任务 T2 的 FutureTask
  2. FutureTask<String> ft2
  3. = new FutureTask<>(new T2Task());
  4. // 创建任务 T1 的 FutureTask
  5. FutureTask<String> ft1
  6. = new FutureTask<>(new T1Task(ft2));
  7. // 线程 T1 执行任务 ft1
  8. Thread T1 = new Thread(ft1);
  9. T1.start();
  10. // 线程 T2 执行任务 ft2
  11. Thread T2 = new Thread(ft2);
  12. T2.start();
  13. // 等待线程 T1 执行结果
  14. System.out.println(ft1.get());
  15. // T1Task 需要执行的任务:
  16. // 洗水壶、烧开水、泡茶
  17. class T1Task implements Callable<String>{
  18. FutureTask<String> ft2;
  19. // T1 任务需要 T2 任务的 FutureTask
  20. T1Task(FutureTask<String> ft2){
  21. this.ft2 = ft2;
  22. }
  23. @Override
  24. String call() throws Exception {
  25. System.out.println("T1: 洗水壶...");
  26. TimeUnit.SECONDS.sleep(1);
  27. System.out.println("T1: 烧开水...");
  28. TimeUnit.SECONDS.sleep(15);
  29. // 获取 T2 线程的茶叶
  30. String tf = ft2.get();
  31. System.out.println("T1: 拿到茶叶:"+tf);
  32. System.out.println("T1: 泡茶...");
  33. return " 上茶:" + tf;
  34. }
  35. }
  36. // T2Task 需要执行的任务:
  37. // 洗茶壶、洗茶杯、拿茶叶
  38. class T2Task implements Callable<String> {
  39. @Override
  40. String call() throws Exception {
  41. System.out.println("T2: 洗茶壶...");
  42. TimeUnit.SECONDS.sleep(1);
  43. System.out.println("T2: 洗茶杯...");
  44. TimeUnit.SECONDS.sleep(2);
  45. System.out.println("T2: 拿茶叶...");
  46. TimeUnit.SECONDS.sleep(1);
  47. return " 龙井 ";
  48. }
  49. }
  50. // 一次执行结果:
  51. //T1: 洗水壶...
  52. //T2: 洗茶壶...
  53. //T1: 烧开水...
  54. //T2: 洗茶杯...
  55. //T2: 拿茶叶...
  56. //T1: 拿到茶叶: 龙井
  57. //T1: 泡茶...
  58. //上茶: 龙井

发表评论

表情:
评论列表 (有 0 条评论,117人围观)

还没有评论,来说两句吧...

相关阅读

    相关 如何优雅停掉线

    很久很久以前,在一个名为“Springboot”的村庄中,住着一群热爱编程的程序员。他们喜欢探索新技术、优化自己的代码,为了打造更好的软件而不断努力着。 在这个村庄中,有一个

    相关 如何获取线执行结果-java

    在日常的项目开发中,我们会经常遇到通过多线程执行程序并需要返回执行结果的场景,下面我们就对获取多线程返回结果的几种方式进行一下归纳,并进行简要的分析与总结。 一、Threa