Java线程安全与并发工具类详解(CountDownLatch & Semaphore)

59 阅读3分钟

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

在多线程编程中,线程安全是一个非常重要的概念。Java提供了多种机制来确保线程安全,其中包括synchronized关键字、volatile变量以及一些高级的并发工具类如CountDownLatch和Semaphore。

CountDownLatch

CountDownLatch是一个同步辅助类,在完成一组正在等待的操作之前,它允许一个或多个线程一直等待。这个类非常适合用于启动信号或者结束信号的情况。

使用示例

import java.util.concurrent.CountDownLatch;

public class CountdownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int N = 4; // 工作线程数量
        CountDownLatch doneSignal = new CountDownLatch(N);

        for (int i = 0; i < N; ++i) { // 创建并启动工作线程
            new Thread(new WorkerRunnable(doneSignal)).start();
        }

        System.out.println("等待所有工作线程完成...");
        doneSignal.await(); // 等待所有工作线程完成
        System.out.println("所有工作线程已完成。");
    }
}

class WorkerRunnable implements Runnable {
    private final CountDownLatch doneSignal;

    public WorkerRunnable(CountDownLatch doneSignal) {
        this.doneSignal = doneSignal;
    }

    @Override
    public void run() {
        try {
            doWork();
        } finally {
            doneSignal.countDown(); // 完成后减少计数器
        }
    }

    void doWork() {
        // 模拟工作任务
        try {
            Thread.sleep((long)(Math.random() * 1000));
        } catch (InterruptedException ex) {}
        System.out.println(Thread.currentThread().getName() + "完成了任务");
    }
}

在这个例子中,我们创建了一个CountDownLatch实例,并将其传递给每个工作线程。当每个线程完成其工作时,它会调用countDown()方法来减少计数器。主线程调用await()方法来阻塞直到计数器变为零。

Semaphore

Semaphore是用来控制同时访问的资源的数量,例如流量控制。它可以用来实现资源池或者公平锁。

使用示例

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        // 假设只有两个停车位
        Semaphore parkingSpace = new Semaphore(2);

        for (int i = 0; i < 5; i++) {
            new Thread(new Car(parkingSpace, "Car-" + (i+1))).start();
        }
    }
}

class Car implements Runnable {
    private String name;
    private Semaphore parkingSpace;

    public Car(Semaphore parkingSpace, String name) {
        this.parkingSpace = parkingSpace;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            System.out.println(name + "尝试进入停车场...");
            parkingSpace.acquire(); // 获取停车位置
            System.out.println(name + "已停放。");
            Thread.sleep(2000); // 停车两秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            parkingSpace.release(); // 释放停车位置
            System.out.println(name + "离开停车场。");
        }
    }
}

在这个例子中,我们创建了一个Semaphore实例表示有两个停车位。每当一辆车到达时,它试图获取一个车位。如果车位已被占满,则车辆必须等待直到有空闲车位。当车辆离开时,它释放车位供其他车辆使用。

应用场景设计

假设我们要开发一个简单的Web爬虫系统,该系统需要从多个网站抓取数据。我们可以使用CountDownLatch来协调不同爬虫线程的工作,并且可以使用Semaphore来限制同时进行网络请求的数量以避免过载。

Web爬虫系统示例

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class WebCrawler {
    public static void main(String[] args) {
        int numThreads = 10; // 爬虫线程数量
        ExecutorService executor = Executors.newFixedThreadPool(numThreads);
        CountDownLatch latch = new CountDownLatch(numThreads);
        Semaphore requestLimit = new Semaphore(3); // 最多三个并发请求

        for (int i = 0; i < numThreads; i++) {
            executor.execute(new CrawlerTask(latch, requestLimit, "http://example.com/page" + i));
        }

        try {
            latch.await(); // 等待所有爬虫任务完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        executor.shutdown();
        System.out.println("所有页面抓取完毕。");
    }
}

class CrawlerTask implements Runnable {
    private CountDownLatch latch;
    private Semaphore requestLimit;
    private String url;

    public CrawlerTask(CountDownLatch latch, Semaphore requestLimit, String url) {
        this.latch = latch;
        this.requestLimit = requestLimit;
        this.url = url;
    }

    @Override
    public void run() {
        try {
            requestLimit.acquire(); // 获取许可
            System.out.println(Thread.currentThread().getName() + "开始抓取" + url);
            // 模拟抓取过程
            Thread.sleep((long)(Math.random() * 1000));
            System.out.println(Thread.currentThread().getName() + "成功抓取" + url);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            requestLimit.release(); // 释放许可
            latch.countDown(); // 减少latch计数
        }
    }
}

在这个Web爬虫系统中,我们使用了ExecutorService来管理线程池,CountDownLatch来协调所有爬虫任务的完成状态,以及Semaphore来限制同时进行的网络请求数量。这样可以有效地管理和优化资源利用。

通过上述示例,我们可以看到如何在实际项目中运用这些并发工具类来解决复杂的问题。希望这篇文章能够帮助你更好地理解Java中的线程安全与并发工具类。