农行1面:Java如何保证线程T1,T2,T3 顺序执行?

简介: 本文探讨了如何保证线程T1、T2、T3的顺序执行,这是农行面试中的一道题目,旨在考察候选人对多线程基础、同步机制、线程间通信及Java并发包的掌握情况。文章详细介绍了六种方法:`join()`、`CountDownLatch`、`Semaphore`、单线程池、`synchronized` 和 `CompletableFuture`,并通过示例代码展示了每种方法的具体实现。这些方法不仅适用于面试备考,还能帮助开发者更好地理解和掌握线程同步技术。

你好,我是猿java。

线程是 Java执行的最小单元,通常意义上来说,多个线程是为了加快速速且无需保序,这篇文章,我们来分析一道农行的面试题目:如要保证线程T1, T2, T3顺序执行?

考察意图

在面试中出现这道问题,通常是为了考察候选人的以下几个知识点:

1. 多线程基础知识: 希望了解候选人是否熟悉Java多线程的基本概念,包括线程的创建、启动和同步机制。

2. 同步机制的理解:候选人需要展示对Java中各种同步工具的理解,如join()CountDownLatchSemaphoreCyclicBarrier等,并知道如何在不同场景下应用这些工具。

3. 线程间通信:希望候选人理解线程间通信的基本原理,例如如何使用wait()notify()来协调线程。

4. 对Java并发包的熟悉程度: 希望候选人了解Java并发包(java.util.concurrent)中的工具和类,展示其对现代Java并发编程的掌握。

保证线程顺序执行的方法

在分析完面试题的考察意图之后,我们再分析如何保证线程顺序执行,这里列举了几种常见的方式。

join()

join()方法是Thread类的一部分,可以让一个线程等待另一个线程完成执行。 当你在一个线程T上调用T.join()时,调用线程将进入等待状态,直到线程T完成(即终止)。因此,可以通过在每个线程启动后调用join()来实现顺序执行。

如下示例代码,展示了join()如何保证线程顺序执行:

Thread t1 = new Thread(() -> {
   
   // 线程T1的任务
});

Thread t2 = new Thread(() -> {
   
   // 线程T2的任务
});

Thread t3 = new Thread(() -> {
   
   // 线程T3的任务
});

t1.start();
t1.join(); // 等待t1完成

t2.start();
t2.join(); // 等待t2完成

t3.start();
t3.join(); // 等待t3完成

CountDownLatch

CountDownLatch通过一个计数器来实现,初始时,计数器的值由构造函数设置,每次调用countDown()方法,计数器的值减1。当计数器的值变为零时,所有等待在await()方法上的线程都将被唤醒,继续执行。

CountDownLatch是Java并发包(java.util.concurrent)中的一个同步辅助类,用于协调多个线程之间的执行顺序。它允许一个或多个线程等待另外一组线程完成操作。

如下示例代码,展示了CountDownLatch如何保证线程顺序执行:

CountDownLatch latch1 = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);

Thread t1 = new Thread(() -> {
   
   // 线程T1的任务
   latch1.countDown(); // 完成后递减latch1
});

Thread t2 = new Thread(() -> {
   
   try {
   
       latch1.await(); // 等待T1完成
       // 线程T2的任务
       latch2.countDown(); // 完成后递减latch2
   } catch (InterruptedException e) {
   
       Thread.currentThread().interrupt();
   }
});

Thread t3 = new Thread(() -> {
   
   try {
   
       latch2.await(); // 等待T2完成
       // 线程T3的任务
   } catch (InterruptedException e) {
   
       Thread.currentThread().interrupt();
   }
});

t1.start();
t2.start();
t3.start();

CountDownLatch关键方法解析:

  • CountDownLatch(int count) : 构造函数,创建一个CountDownLatch实例,计数器的初始值为count。
  • void await() : 使当前线程等待,直到计数器的值变为零。
  • boolean await(long timeout, TimeUnit unit) : 使当前线程等待,直到计数器的值变为零或等待时间超过指定的时间。
  • void countDown() : 递减计数器的值。当计数器的值变为零时,所有等待的线程被唤醒。

Semaphore

Semaphore通过一个计数器来管理许可,计数器的初始值由构造函数指定,表示可用许可的数量。线程可以通过调用acquire()方法请求许可,如果许可可用则授予访问权限,否则线程将阻塞。使用完资源后,线程调用release()方法释放许可,从而允许其他阻塞的线程获取许可。

如下示例代码,展示了Semaphore如何保证线程顺序执行:

Semaphore semaphore1 = new Semaphore(0);
Semaphore semaphore2 = new Semaphore(0);

Thread t1 = new Thread(() -> {
   
   // 线程T1的任务
   semaphore1.release(); // 释放一个许可
});

Thread t2 = new Thread(() -> {
   
   try {
   
       semaphore1.acquire(); // 获取许可,等待T1完成
       // 线程T2的任务
       semaphore2.release(); // 释放一个许可
   } catch (InterruptedException e) {
   
       Thread.currentThread().interrupt();
   }
});

Thread t3 = new Thread(() -> {
   
   try {
   
       semaphore2.acquire(); // 获取许可,等待T2完成
       // 线程T3的任务
   } catch (InterruptedException e) {
   
       Thread.currentThread().interrupt();
   }
});

t1.start();
t2.start();
t3.start();

Semaphore关键方法分析:

  • Semaphore(int permits) :构造一个具有给定许可数的Semaphore。
  • Semaphore(int permits, boolean fair) :构造一个具有给定许可数的Semaphore,并指定是否是公平的。公平性指的是线程获取许可的顺序是否是先到先得。
  • void acquire() :获取一个许可,如果没有可用许可,则阻塞直到有许可可用。
  • void acquire(int permits) :获取指定数量的许可。
  • void release() :释放一个许可。
  • void release(int permits) :释放指定数量的许可。
  • int availablePermits() :返回当前可用的许可数量。
  • boolean tryAcquire() :尝试获取一个许可,立即返回true或false。
  • boolean tryAcquire(long timeout, TimeUnit unit) :在给定的时间内尝试获取一个许可。

单线程池

单线程池(Executors.newSingleThreadExecutor())可以确保任务按提交顺序依次执行。所有任务都会在同一个线程中运行,保证了顺序性。

如下示例代码展示了单线程池如何保证线程顺序执行:

ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(new T1());
executor.submit(new T2());
executor.submit(new T3());
executor.shutdown();

单线程这种方法简单易用,适合需要顺序执行的场景。

synchronized

synchronized 是Java中的一个关键字,用于实现线程同步,确保多个线程对共享资源的访问是互斥的。它通过锁机制来保证同一时刻只有一个线程可以执行被Synchronized保护的代码块,从而避免数据不一致和线程安全问题。

如下示例代码,展示了synchronized如何保证线程顺序执行:

class Task {
   
    synchronized void executeTask(String taskName) {
   
        System.out.println(taskName + " 执行");
    }
}

public class Main {
   
    public static void main(String[] args) {
   
        Task task = new Task();
        new Thread(() -> task.executeTask("T1")).start();
        new Thread(() -> task.executeTask("T2")).start();
        new Thread(() -> task.executeTask("T3")).start();
    }
}

CompletableFuture

CompletableFuture是 Java 8 引入的类,属于 java.util.concurrent 包。它是一个功能强大的工具,用于处理异步编程。CompletableFuture 允许创建、操作和组合任务,可以说是处理异步任务的一个灵活和强大的解决方案。

如下示例代码,展示了Semaphore如何保证线程顺序执行:

import java.util.concurrent.CompletableFuture;
public class CompletableFutureSequentialExecution {
   

    public static void main(String[] args) {
   

        // 创建第一个任务T1
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
   
            System.out.println("T1 is running on thread: " + Thread.currentThread().getName());
            // 模拟任务运行
            sleep(1000);
        });

        // 链接任务T2到T1之后
        future = future.thenRunAsync(() -> {
   
            System.out.println("T2 is running on thread: " + Thread.currentThread().getName());
            sleep(1000);
        });

        // 链接任务T3到T2之后
        future = future.thenRunAsync(() -> {
   
            System.out.println("T3 is running on thread: " + Thread.currentThread().getName());
            sleep(1000);
        });

        // 等待所有任务完成
        future.join();
    }

    // 辅助方法用于模拟延迟
    private static void sleep(long milliseconds) {
   
        try {
   
            Thread.sleep(milliseconds);
        } catch (InterruptedException e) {
   
            Thread.currentThread().interrupt();
        }
    }
}

代码解释:

  • runAsync 方法:使用 CompletableFuture.runAsync() 方法来异步执行任务。runAsync 不会返回任何结果,因此只在任务结束时传递控制权。
  • 链式调用 thenRunAsync:使用 thenRunAsync 方法来在前一个任务完成后启动下一个任务。这样确保了任务按顺序执行。
  • join 方法:join 方法等待 CompletableFuture 的计算完成。这相当于调用 get,但不会抛出检查异常。

总结

本文,我们分析了6种保证线程T1,T2,T3顺序执行的方法,依次如下:

  1. join()
  2. CountDownLatch
  3. Semaphore
  4. 单线程池
  5. synchronized
  6. CompletableFuture

在实际开发中,这种需要在业务代码中去保证线程执行顺序的情况几乎不会出现,因此,这个面试题其实缺乏实际的应用场景,纯粹是为了面试存在。尽管是面试题,还是可以帮助我们更好地去了解和掌握线程。

学习交流

如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注:猿java,持续输出硬核文章。

目录
相关文章
|
10天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
18天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
1天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
1天前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
16 1
|
9天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
9天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
8天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
14天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
41 9
|
11天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
17天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
下一篇
无影云桌面