解锁Java多线程:如何控制线程T1、T2、T3的执行顺序(二)?

268 阅读4分钟

除了之前提到的几种方法(如join()CountDownLatchSemaphore、单线程池、synchronizedCompletableFuture)之外,确实还有一些其他方法能够保证线程按顺序执行。下面是一些常见的替代方案:

解锁Java多线程:如何控制线程T1、T2、T3的执行顺序(一)?

1. CyclicBarrier

解释: CyclicBarrierJava并发包中的另一个同步工具,通常用于多个线程都到达某个共同的屏障点时一起继续执行。虽然它的主要用途是同步多个线程的执行,但也可以通过适当的设置来控制线程顺序。

使用场景: 当需要多个线程在同一时刻开始或继续执行,或者在特定时刻同步时,CyclicBarrier可以帮助实现这种控制。

代码示例:

CyclicBarrier barrier = new CyclicBarrier(3, () -> {
    System.out.println("所有线程到达屏障点,开始执行");
});

Thread t1 = new Thread(() -> {
    try {
        System.out.println("线程T1开始");
        barrier.await();  // 等待其他线程
        System.out.println("线程T1继续");
    } catch (Exception e) {
        Thread.currentThread().interrupt();
    }
});

Thread t2 = new Thread(() -> {
    try {
        System.out.println("线程T2开始");
        barrier.await();  // 等待其他线程
        System.out.println("线程T2继续");
    } catch (Exception e) {
        Thread.currentThread().interrupt();
    }
});

Thread t3 = new Thread(() -> {
    try {
        System.out.println("线程T3开始");
        barrier.await();  // 等待其他线程
        System.out.println("线程T3继续");
    } catch (Exception e) {
        Thread.currentThread().interrupt();
    }
});

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

2. Lock(显式锁)

解释: ReentrantLock 是一种显式的锁机制,比 synchronized 更加灵活。通过在多个线程间获取和释放锁,ReentrantLock 可以确保线程按顺序执行。它可以避免死锁,并允许指定公平锁(保证线程获取锁的顺序是按照请求顺序)。

使用场景: 当需要更多控制权和锁的灵活性时,ReentrantLock 是一个很好的选择。

代码示例:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

Thread t1 = new Thread(() -> {
    lock.lock();
    try {
        System.out.println("线程T1");
        condition.signal();  // 唤醒T2
    } finally {
        lock.unlock();
    }
});

Thread t2 = new Thread(() -> {
    lock.lock();
    try {
        condition.await();  // 等待T1
        System.out.println("线程T2");
        condition.signal();  // 唤醒T3
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        lock.unlock();
    }
});

Thread t3 = new Thread(() -> {
    lock.lock();
    try {
        condition.await();  // 等待T2
        System.out.println("线程T3");
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    } finally {
        lock.unlock();
    }
});

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

3. Exchanger

解释: Exchanger 是一个用于在两个线程之间交换数据的同步工具,通常用于两个线程在某个点交换信息时。通过这种机制,也可以实现线程间的顺序执行,尤其是在线程之间需要交换某些状态信息时。

使用场景: 适用于两个线程需要在某个同步点交换信息的场景。

代码示例:

Exchanger<String> exchanger = new Exchanger<>();

Thread t1 = new Thread(() -> {
    try {
        String result = "T1 finished";
        exchanger.exchange(result);  // 交换数据
        System.out.println("线程T1");
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});

Thread t2 = new Thread(() -> {
    try {
        exchanger.exchange(null);  // 等待T1完成
        System.out.println("线程T2");
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});

Thread t3 = new Thread(() -> {
    try {
        exchanger.exchange(null);  // 等待T2完成
        System.out.println("线程T3");
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});

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

4. Phaser

解释: Phaser 是一种比 CountDownLatchCyclicBarrier 更加灵活的同步工具,它支持动态注册和注销线程。它允许线程在不同的阶段同步,并且支持跨多个阶段的同步。

使用场景: 适用于多阶段的任务执行,尤其是线程执行的阶段数量不确定的情况下。

代码示例:

Phaser phaser = new Phaser(1);  // 注册一个主线程

Thread t1 = new Thread(() -> {
    System.out.println("线程T1");
    phaser.arriveAndAwaitAdvance();  // 等待信号继续
});

Thread t2 = new Thread(() -> {
    phaser.arriveAndAwaitAdvance();  // 等待T1
    System.out.println("线程T2");
    phaser.arriveAndAwaitAdvance();  // 准备继续下一阶段
});

Thread t3 = new Thread(() -> {
    phaser.arriveAndAwaitAdvance();  // 等待T2
    System.out.println("线程T3");
    phaser.arriveAndAwaitAdvance();  // 结束
});

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

5. Thread.sleep()(不推荐)

解释: 虽然不推荐使用 Thread.sleep() 来控制线程顺序,但它可以简单地使线程休眠指定时间,间接地控制线程的执行顺序。这个方法的缺点是它不保证准确的顺序,并且可能导致性能问题。

使用场景: 在一些不那么严格的需求中,临时控制线程的执行顺序。

代码示例:

Thread t1 = new Thread(() -> {
    try {
        System.out.println("线程T1");
        Thread.sleep(100);  // 暂停一定时间
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});

Thread t2 = new Thread(() -> {
    try {
        Thread.sleep(100);  // 等待T1稍微完成
        System.out.println("线程T2");
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});

Thread t3 = new Thread(() -> {
    try {
        Thread.sleep(200);  // 等待T2完成
        System.out.println("线程T3");
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});

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

总结

除了常见的 join()CountDownLatchSemaphore 等方法,其他同步工具如 CyclicBarrierReentrantLockExchangerPhaser 也能帮助保证线程顺序执行。每种工具都有其适用的场景,选择合适的工具可以在确保线程执行顺序的同时,也提高程序的效率和可维护性。