Java 多线程编程:并发工具类(Lock, Semaphore等)
在Java中,多线程编程是实现高效、响应迅速的应用程序的关键。为了更好地管理线程之间的交互和资源共享,Java提供了一系列的并发工具类。这些工具类包括Lock
、Semaphore
、CountDownLatch
、CyclicBarrier
等。本文将详细介绍这些并发工具类的使用、优缺点以及注意事项,并提供丰富的示例代码。
1. Lock 接口
1.1 概述
Lock
接口是Java并发包中的一个重要接口,提供了比synchronized
关键字更灵活的锁机制。Lock
接口的实现类主要有ReentrantLock
和ReentrantReadWriteLock
。
1.2 ReentrantLock
ReentrantLock
是Lock
接口的一个实现,支持重入锁的特性。
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 缺点
- 复杂性:需要手动管理锁的获取和释放,容易导致死锁。
- 性能开销:相较于
synchronized
,Lock
的性能开销更大。
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 注意事项
- 确保所有线程都能到达屏障,避免死锁。
- 处理
BrokenBarrierException
和InterruptedException
。
总结
Java的并发工具类为多线程编程提供了强大的支持。通过合理使用Lock
、Semaphore
、CountDownLatch
和CyclicBarrier
等工具类,可以有效地管理线程之间的交互和资源共享。每种工具类都有其独特的优缺点和适用场景,开发者应根据具体需求选择合适的工具类。在使用这些工具类时,务必注意线程安全和资源管理,以避免潜在的并发问题。