Java并发编程之CountDownLatch与Semaphore详解

58 阅读2分钟

线程安全与并发工具类详解

在现代软件开发中,多线程编程是一个无法避免的话题。为了确保程序在多线程环境下的正确运行,理解和掌握线程安全以及如何利用并发工具类是非常重要的。

什么是线程安全?

当多个线程同时执行某段代码时,如果没有适当的同步机制,可能会导致不可预测的结果。线程安全就是指这段代码能够正确地处理多个线程的同时访问,保证数据的一致性和完整性。

CountDownLatch 的使用

CountDownLatch 是一个同步工具类,它允许一个或多个线程等待直到其他线程完成操作。下面是一个简单的例子来展示如何使用 CountDownLatch:

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(2);

        Thread thread1 = new Thread(() -> {
            System.out.println("Thread 1 starting.");
            // 模拟线程1的工作
            try { Thread.sleep(1000); } catch (InterruptedException e) {};
            System.out.println("Thread 1 finished.");
            latch.countDown();
        });

        Thread thread2 = new Thread(() -> {
            System.out.println("Thread 2 starting.");
            // 模拟线程2的工作
            try { Thread.sleep(2000); } catch (InterruptedException e) {};
            System.out.println("Thread 2 finished.");
            latch.countDown();
        });

        thread1.start();
        thread2.start();

        System.out.println("Waiting for threads to finish.");
        latch.await();
        System.out.println("All threads have finished.");
    }
}

Semaphore 的使用

Semaphore 类似于 CountDownLatch,但它维护了一个许可池。线程可以通过获取许可进入临界区,然后释放许可。

下面的例子展示了如何使用 Semaphore 来控制同时访问资源的数量:

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        final Semaphore semaphore = new Semaphore(3);

        Runnable worker = () -> {
            try {
                semaphore.acquire();
                System.out.println("Thread " + Thread.currentThread().getName() + ": Started working");
                Thread.sleep(2000);
                System.out.println("Thread " + Thread.currentThread().getName() + ": Finished working");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        };

        for (int i = 0; i < 10; i++) {
            new Thread(worker).start();
        }
    }
}

应用场景

假设我们正在构建一个网站的后台系统,该系统需要处理大量的并发请求,其中包括了用户注册功能。我们可以使用 CountDownLatch 来确保在数据库初始化完成之前不允许任何线程开始处理用户注册请求。

// 初始化数据库
final CountDownLatch initLatch = new CountDownLatch(1);
new Thread(() -> {
    try {
        initializeDatabase();
        initLatch.countDown();
    } catch (Exception e) {
        e.printStackTrace();
    }
}).start();

// 用户注册逻辑
new Thread(() -> {
    try {
        initLatch.await();
        registerUser();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}).start();

通过这样的设计,我们可以确保在数据库准备好之前,不会有任何注册请求被处理,从而避免了潜在的数据不一致问题。

本文提供了详细的线程安全概念介绍以及如何使用 CountDownLatchSemaphore 这两个并发工具类来实现线程间的协作。通过实际的代码示例,读者可以更好地理解这些概念并将其应用于自己的项目中。