Java线程同步和协作的5种方式

文章介绍了在Java中如何实现线程A和B执行完成后才开始执行线程C的四种方法:使用CountDownLatch、CyclicBarrier、join()方法以及wait()和notifyAll()。每个方法都有相应的示例代码,展示了如何通过这些同步机制控制线程间的执行顺序。
摘要由CSDN通过智能技术生成

问题

有3个线程为A,B,C,同时启动,C必须等待A和B完成才能继续执行,如何实现?
要求:仅使用 java 语言和它原生API

解法

  1. 使用 Java 中提供的 CountDownLatch 类实现线程之间的同步和协作

  2. 使用 Java 中提供的 CyclicBarrier 类实现线程之间的同步和协作

  3. 使用join()方法:Thread类提供了join()方法,可以让一个线程等待另一个线程执行完毕后再继续执行。在本例中,C线程可以在启动A和B线程后,分别调用它们的join()方法等待它们完成执行,然后再执行自己的任务。

  4. 使用wait()和notifyAll()方法:可以通过使用对象的wait()和notifyAll()方法来实现线程间的协作。在本例中,可以创建一个共享对象,在A、B线程执行完毕后调用该对象的notifyAll()方法通知C线程可以继续执行,而C线程在启动前需要调用该对象的wait()方法等待A、B线程执行完毕并通知自己。

  5. 使用Semaphore类:Semaphore类是一种计数信号量,可以限制同一时间内对共享资源的访问数量。在本例中,可以使用Semaphore类来创建一个初始值为0的信号量,A和B线程执行完毕后释放信号量,C线程需要先获取到两个信号量才能继续执行。

下面分别是演示:

CountDownLatch

CountDownLatch是Java中的一个工具类,可以用于控制线程执行顺序。它的作用就是允许一个或多个线程等待另外一组线程完成操作后再继续执行。

CountDownLatch包含一个计数器,这个计数器被初始化为一个正整数,表示需要等待完成的线程数量。当线程完成它的任务时,计数器的值减1。当计数器的值变为0时,表示所有需要等待的线程都已经完成了任务,此时等待中的线程会被唤醒继续执行。

下面是CountDownLatch的一个简单示例:

import java.util.concurrent.CountDownLatch;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3); // 初始化计数器为 3

        Thread t1 = new Thread(() -> {
            System.out.println("Task 1 is running");
            try {
                Thread.sleep(1000); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Task 1 is done");
            latch.countDown(); // 计数器减一
        });

        Thread t2 = new Thread(() -> {
            System.out.println("Task 2 is running");
            try {
                Thread.sleep(3000); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Task 2 is done");
            latch.countDown(); // 计数器减一
        });

        Thread t3 = new Thread(() -> {
            System.out.println("Task 3 is running");
            try {
                Thread.sleep(2000); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Task 3 is done");
            latch.countDown(); // 计数器减一
        });

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

        latch.await(); // 等待计数器归零

        System.out.println("All tasks are done");
    }
}

在这个例子中,我们创建了一个CountDownLatch对象,并初始化计数器为3。然后启动了三个线程,每个线程执行一些耗时操作,并在最后调用countDown()方法将计数器减一。最后,调用await()方法等待计数器归零,然后输出"All tasks are done"。

需要注意的是,如果计数器的值已经变为0,那么对countDown()方法的调用不会产生任何效果。而对await()方法的调用会立即返回。因此,在使用CountDownLatch时,通常要保证计数器的值不会被错误地减多或减少。

使用 CyclicBarrier

CyclicBarrier 是另一种同步工具类,它可以使一组线程相互等待,直到所有线程都达到某个公共屏障点后再继续执行。在本例中,可以创建一个 CyclicBarrier 对象,设置等待的线程数为 2,当 A、B 线程完成任务后调用 await() 方法等待其他线程到达屏障点,而 C 线程在启动前需要调用 await() 方法等待 A、B 线程到达屏障点后再继续执行。

具体实现如下:

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        CyclicBarrier barrier = new CyclicBarrier(2, () -> {
            System.out.println("Thread C is running");

            try {
                Thread.sleep(1000); // 模拟C的耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
             // 当A和B线程完成后,继续执行C线程的任务
            System.out.println("Thread C is done");
           
        });

        Thread A = new Thread(() -> {
            // 线程A的任务
            try {
                System.out.println("Thread A is running");
                Thread.sleep(1000); // // 模拟耗时操作
                System.out.println("Thread A is done");

                barrier.await();

            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

        Thread B = new Thread(() -> {

            try {
                // 线程B的任务
                System.out.println("Thread B is running");
                Thread.sleep(1000); // // 模拟耗时操作
                System.out.println("Thread B is done");

                barrier.await();

            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }

        });

      

        A.start();
        B.start();

    }
}

使用 join 方法

是的,可以使用Java中的wait()和notifyAll()方法实现这种线程同步。

下面是一个示例代码,其中线程A和线程B工作完成后会调用notifyAll()方法,通知等待的线程C继续执行:
可以使用Java中的线程同步机制来实现这个需求,具体实现如下:

  1. 创建三个线程A、B、C,并且让它们同时执行:
Thread A = new Thread(() -> {
    // 线程A的任务
});

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

Thread C = new Thread(() -> {
    // 线程C的任务
});
A.start();
B.start();
C.start();
  1. 在C线程中加入等待A和B线程的逻辑:
try {
    // 等待A线程完成
    A.join();
    
    // 等待B线程完成
    B.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}

// 当A和B线程完成后,继续执行C线程的任务

完整代码如下所示:

public class Main {
    public static void main(String[] args) throws InterruptedException {

        Thread a = new Thread(() -> {
            System.out.println("Thread A is running");
            try {
                Thread.sleep(3000); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread A is done");
        });

        Thread b = new Thread(() -> {
            System.out.println("Thread B is running");
            try {
                Thread.sleep(2000); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread B is done");
        });

        Thread c = new Thread(() -> {
            try {
                // 等待其它线程执行完毕
                a.join();
                b.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread C is running");
        });

        a.start();
        b.start();
        c.start();

        c.join();

        System.out.println("All threads are done");
    }

}

使用 notify/wait 方法

是的,可以使用Java中的wait()和notifyAll()方法实现这种线程同步。

下面是一个示例代码,其中线程A和线程B工作完成后会调用notifyAll()方法,通知等待的线程C继续执行:

public class ThreadSync {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        boolean aDone = false;
        boolean bDone = false;

        // Thread A
        Thread threadA = new Thread(() -> {
            // Do some work
            System.out.println("Thread A is doing some work...");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // Signal that work is done
            synchronized (lock) {
                aDone = true;
                lock.notifyAll();
            }
        });

        // Thread B
        Thread threadB = new Thread(() -> {
            // Do some work
            System.out.println("Thread B is doing some work...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // Signal that work is done
            synchronized (lock) {
                bDone = true;
                lock.notifyAll();
            }
        });

        // Thread C
        Thread threadC = new Thread(() -> {
            synchronized (lock) {
                while (!(aDone && bDone)) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // Do some work
                System.out.println("Thread C is doing some work...");
            }
        });

        // Start all threads
        threadA.start();
        threadB.start();
        threadC.start();

        // Wait for all threads to finish
        threadA.join();
        threadB.join();
        threadC.join();

        System.out.println("All threads have finished.");
    }
}

在这个示例中,我们通过创建一个共享的锁对象,并使用它来同步不同线程之间的访问。当A和B线程完成工作时,它们会调用notifyAll()方法,通知等待的线程C继续执行。而线程C会在while循环内部等待,直到A和B都完成工作并调用notifyAll()方法后才继续执行。

需要注意的是,在使用wait()和notifyAll()方法时,必须在同步块内部调用它们。否则,将抛出IllegalMonitorStateException异常。

使用信号量 Semaphore

下面是使用Java语言和Semaphore实现的示例代码:

import java.util.concurrent.Semaphore;

public class Main {
    
    private static Semaphore semaphoreA = new Semaphore(1);
    private static Semaphore semaphoreB = new Semaphore(1);
    private static Semaphore semaphoreC = new Semaphore(0);

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            try {
                semaphoreA.acquire();
                System.out.println("Thread A is running");
                Thread.sleep(1000); // 模拟线程A执行时间
                System.out.println("Thread A is done");
                semaphoreC.release(); // 执行完释放semaphoreC
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphoreA.release();
            }
        });

        Thread threadB = new Thread(() -> {
            try {
                semaphoreB.acquire();
                System.out.println("Thread B is running");
                Thread.sleep(2000); // 模拟线程B执行时间
                System.out.println("Thread B is done");
                semaphoreC.release(); // 执行完释放semaphoreC
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphoreB.release();
            }
        });

        Thread threadC = new Thread(() -> {
            try {
                semaphoreC.acquire(2); // 等待2个信号量,即等待线程A和线程B执行完成
                System.out.println("Thread C is running");
                Thread.sleep(3000); // 模拟线程C执行时间
                System.out.println("Thread C is done");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        threadA.start();
        threadB.start();
        threadC.start();

        threadA.join();
        threadB.join();
        threadC.join();

        System.out.println("All threads are done");
    }
}

在上面的代码中,我们定义了三个Semaphore,分别为semaphoreA、semaphoreB和semaphoreC。semaphoreA和semaphoreB的初始信号量都设置为1,而semaphoreC的初始信号量为0。

线程A和线程B获取各自的Semaphore,执行完逻辑后释放semaphoreC信号量,线程C获取两个信号量后开始执行逻辑。

通过Semaphore的特性,实现了线程C必须等待线程A和线程B都执行完成才能开始执行的需求。

在这个问题描述中,有3个线程A、B、C,其中线程C必须等待线程A和线程B都执行完成才能开始执行。因此,我们需要使用Semaphore来控制线程的执行顺序。

在这个问题描述中,semaphoreA 和 semaphoreB 不是必须的。

定义了semaphoreA是为了保证在任何时候只有一个线程可以访问线程A的代码块。如果没有semaphoreA,可能会出现多个线程同时执行线程A的代码块,导致并发问题。

具体地说,在上面的Java代码示例中,我们定义了一个初始信号量为1的semaphoreA。当线程A需要执行其代码块时,它首先要获取semaphoreA的信号量,以保证只有一个线程可以进入代码块中执行。当线程A执行完了其代码块后,它会释放semaphoreA的信号量,以允许其他线程(如线程B)进入代码块中执行。

注意,semaphoreA只用于控制线程A的代码块的访问,而不影响线程B和线程C的执行。线程B也有自己的semaphoreB来控制其代码块的访问,而线程C则使用semaphoreC来等待线程A和线程B执行完成后再开始执行。

以上是几种常见的实现线程同步和协作的方式,您可以根据具体的需求选择最适合的方案。

本文完,希望能帮到你

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值