Java 多线程编程:并发工具类(Lock, Semaphore等)

在Java中,多线程编程是实现高效、响应迅速的应用程序的关键。为了更好地管理线程之间的交互和资源共享,Java提供了一系列的并发工具类。这些工具类包括LockSemaphoreCountDownLatchCyclicBarrier等。本文将详细介绍这些并发工具类的使用、优缺点以及注意事项,并提供丰富的示例代码。

1. Lock 接口

1.1 概述

Lock接口是Java并发包中的一个重要接口,提供了比synchronized关键字更灵活的锁机制。Lock接口的实现类主要有ReentrantLockReentrantReadWriteLock

1.2 ReentrantLock

ReentrantLockLock接口的一个实现,支持重入锁的特性。

1.2.1 使用示例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private static final Lock lock = new ReentrantLock();
    private static int counter = 0;

    public static void increment() {
        lock.lock(); // 获取锁
        try {
            counter++;
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(ReentrantLockExample::increment);
            threads[i].start();
        }

        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Final counter value: " + counter);
    }
}

1.2.2 优点

  • 灵活性:可以尝试获取锁(tryLock()),可以设置超时(tryLock(long timeout, TimeUnit unit))。
  • 公平性:可以选择公平锁(new ReentrantLock(true)),确保线程按照请求锁的顺序获取锁。

1.2.3 缺点

  • 复杂性:需要手动管理锁的获取和释放,容易导致死锁。
  • 性能开销:相较于synchronizedLock的性能开销更大。

1.2.4 注意事项

  • 确保在finally块中释放锁,以避免死锁。
  • 尽量缩小锁的范围,减少锁的持有时间。

2. Semaphore

2.1 概述

Semaphore是一个计数信号量,用于控制对共享资源的访问。它维护了一组许可,线程在访问资源之前必须获取许可。

2.2 使用示例

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    private static final Semaphore semaphore = new Semaphore(3); // 允许3个线程同时访问
    private static int counter = 0;

    public static void accessResource() {
        try {
            semaphore.acquire(); // 获取许可
            System.out.println(Thread.currentThread().getName() + " acquired a permit.");
            counter++;
            Thread.sleep(1000); // 模拟资源访问
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + " released a permit.");
            semaphore.release(); // 释放许可
        }
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(SemaphoreExample::accessResource);
            threads[i].start();
        }

        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Final counter value: " + counter);
    }
}

2.3 优点

  • 控制并发:可以限制同时访问某个资源的线程数量。
  • 灵活性:可以动态调整许可数量。

2.4 缺点

  • 复杂性:需要手动管理许可的获取和释放。
  • 可能导致饥饿:某些线程可能长时间无法获取许可。

2.5 注意事项

  • 确保在finally块中释放许可。
  • 适当设置许可数量,以避免过多线程竞争导致性能下降。

3. CountDownLatch

3.1 概述

CountDownLatch是一个同步辅助类,允许一个或多个线程等待直到在其他线程中执行的一组操作完成。

3.2 使用示例

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    private static final CountDownLatch latch = new CountDownLatch(3);

    public static void task() {
        try {
            Thread.sleep(1000); // 模拟任务执行
            System.out.println(Thread.currentThread().getName() + " completed.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            latch.countDown(); // 任务完成,计数减一
        }
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[3];
        for (int i = 0; i < 3; i++) {
            threads[i] = new Thread(CountDownLatchExample::task);
            threads[i].start();
        }

        try {
            latch.await(); // 等待所有任务完成
            System.out.println("All tasks completed.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3.3 优点

  • 简单易用:提供了简单的API来实现线程间的协调。
  • 适用于多线程任务的等待:可以有效地等待多个线程完成任务。

3.4 缺点

  • 一次性使用CountDownLatch只能使用一次,不能重用。

3.5 注意事项

  • 确保在所有线程完成任务后调用countDown()
  • 使用await()时要处理InterruptedException

4. CyclicBarrier

4.1 概述

CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到所有线程都到达某个公共屏障点。

4.2 使用示例

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    private static final CyclicBarrier barrier = new CyclicBarrier(3, () -> {
        System.out.println("All parties have arrived at the barrier, let's proceed.");
    });

    public static void task() {
        try {
            System.out.println(Thread.currentThread().getName() + " is doing some work.");
            Thread.sleep(1000); // 模拟工作
            barrier.await(); // 等待其他线程
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread[] threads = new Thread[3];
        for (int i = 0; i < 3; i++) {
            threads[i] = new Thread(CyclicBarrierExample::task);
            threads[i].start();
        }
    }
}

4.3 优点

  • 灵活性:可以重用,适合多次使用的场景。
  • 支持回调:可以在所有线程到达屏障后执行某个操作。

4.4 缺点

  • 复杂性:需要确保所有线程都能到达屏障,否则会导致死锁。
  • 性能开销:在高并发情况下,可能会引入性能开销。

4.5 注意事项

  • 确保所有线程都能到达屏障,避免死锁。
  • 处理BrokenBarrierExceptionInterruptedException

总结

Java的并发工具类为多线程编程提供了强大的支持。通过合理使用LockSemaphoreCountDownLatchCyclicBarrier等工具类,可以有效地管理线程之间的交互和资源共享。每种工具类都有其独特的优缺点和适用场景,开发者应根据具体需求选择合适的工具类。在使用这些工具类时,务必注意线程安全和资源管理,以避免潜在的并发问题。