Semaphone、CountDownLatch、CylicBarrier

85 阅读4分钟

Semaphone、CountDownLatch、CylicBarrier

当涉及多线程编程时,Java 并发包提供了一些同步工具来协调线程的执行顺序和控制并发访问资源。其中,SemaphoreCountDownLatchCyclicBarrier 是常用的同步工具,它们各自具有不同的特点和用途。

Semaphore(信号量): Semaphore 是一种计数信号量,它维护了一个可用的许可数量,线程可以通过获取许可来执行操作,执行完后释放许可。信号量常用于控制同时访问某一资源的线程数量。

import java.util.concurrent.Semaphore;
​
public class SemaphoreExample {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(2); // 允许两个线程同时访问
​
        Runnable task = () -> {
            try {
                semaphore.acquire(); // 获取许可
                System.out.println("Thread " + Thread.currentThread().getId() + " is accessing the resource.");
                Thread.sleep(2000); // 模拟线程执行
                semaphore.release(); // 释放许可
                System.out.println("Thread " + Thread.currentThread().getId() + " released the resource.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
​
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        Thread thread3 = new Thread(task);
​
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

CountDownLatch: CountDownLatch 允许一个或多个线程等待一组事件发生后再继续执行。它的计数器初始值为一个正整数,每个线程执行完一个事件后计数器减一,当计数器为零时,等待的线程可以继续执行。

import java.util.concurrent.CountDownLatch;
​
public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(3); // 计数器初始值为3
​
        Runnable task = () -> {
            System.out.println("Thread " + Thread.currentThread().getId() + " completed its task.");
            latch.countDown(); // 计数减一
        };
​
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        Thread thread3 = new Thread(task);
​
        thread1.start();
        thread2.start();
        thread3.start();
​
        latch.await(); // 等待计数器归零
        System.out.println("All threads have completed their tasks.");
    }
}

CyclicBarrier(循环屏障): CyclicBarrier 允许一组线程在某一点上等待彼此,然后同时继续执行。一旦等待的线程数量达到设定值,它们会被释放,并且可以再次循环使用。

import java.util.concurrent.CyclicBarrier;
​
public class CyclicBarrierExample {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(3); // 等待3个线程
​
        Runnable task = () -> {
            try {
                System.out.println("Thread " + Thread.currentThread().getId() + " is waiting.");
                barrier.await(); // 等待所有线程达到屏障点
                System.out.println("Thread " + Thread.currentThread().getId() + " continues.");
            } catch (Exception e) {
                e.printStackTrace();
            }
        };
​
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        Thread thread3 = new Thread(task);
​
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

在这些示例中,SemaphoreCountDownLatchCyclicBarrier 都被用于不同的同步场景,它们能够协调线程的执行顺序,控制并发访问资源,以及等待一组线程完成后继续执行。

使用join方法可以达到CountDownLatch类似的效果

使用 join() 方法可以达到类似于 CountDownLatch 的效果,但它们之间有一些区别和适用场景的差异。

相似之处:

  • join() 方法也可以用于等待其他线程的执行完成。当一个线程在另一个线程上调用 join() 方法时,它会被阻塞,直到被等待的线程执行完成。
  • join() 方法可以用于协调多个线程的执行顺序,确保某些线程在其他线程执行完成后再继续。

区别和注意点:

  • CountDownLatch 是一种更通用、更灵活的同步机制,可以等待一组线程执行完成,而 join() 方法只能等待单个线程。
  • CountDownLatch 可以在不同线程之间共享,而 join() 方法必须在主线程中调用。

示例代码使用 join() 方法模拟 CountDownLatch 的效果:

public class JoinExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            System.out.println("Thread 1 completed its task.");
        });
​
        Thread thread2 = new Thread(() -> {
            System.out.println("Thread 2 completed its task.");
        });
​
        Thread thread3 = new Thread(() -> {
            System.out.println("Thread 3 completed its task.");
        });
​
        thread1.start();
        thread2.start();
        thread3.start();
​
        // 等待所有线程执行完成
        thread1.join();
        thread2.join();
        thread3.join();
​
        System.out.println("All threads have completed their tasks.");
    }
}

虽然 join() 方法可以实现类似于 CountDownLatch 的等待效果,但在涉及多个线程的情况下,CountDownLatch 更加直观且适用。 CountDownLatch 还可以进行动态的倒计数,而 join() 方法只能等待指定线程的执行完成。

CountDownLatch会阻塞主线程吗?

使用 CountDownLatch 的主线程会在调用 await() 方法时阻塞,直到计数器归零或等待超时。在 CountDownLatch 的工作原理中,主要有两个方面需要注意:

  1. 计数器归零: 主线程调用 await() 方法后会等待,直到计数器归零。只有在计数器变为零后,主线程才会被唤醒继续执行。
  2. 等待超时: await(long timeout, TimeUnit unit) 方法允许设置等待超时时间。如果等待时间超过指定的时间,主线程会被唤醒,不必等待所有线程完成。

下面是一个示例,演示了使用 CountDownLatch 阻塞主线程的情况:

import java.util.concurrent.CountDownLatch;
​
public class CountDownLatchBlocking {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2); // 计数器初始值为2
​
        Runnable task = () -> {
            System.out.println("Thread " + Thread.currentThread().getId() + " completed its task.");
            latch.countDown(); // 计数减一
        };
​
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
​
        thread1.start();
        thread2.start();
​
        System.out.println("Main thread is waiting for threads to complete.");
        latch.await(); // 主线程等待计数器归零
        System.out.println("All threads have completed their tasks. Main thread continues.");
    }
}

在这个示例中,主线程在调用 latch.await() 后会被阻塞,直到计数器归零。只有当两个线程都执行完毕,计数器减为零后,主线程才会继续执行。