Java 中的同步与并发工具类
Java 中的同步与并发工具类
在并发编程中,一个重要的课题就是如何在多个线程之间共享数据时,保证数据的正确性和一致性。这就涉及到了同步(synchronization)的问题。为了控制多个线程对共享资源的访问,Java 提供了多种同步机制,如 synchronized
关键字和显示锁(ReentrantLock
)等。
此外,Java 还提供了多种并发工具类,它们可以帮助我们在复杂的并发场景下更好地协调和控制线程的行为。
今天的文章将介绍同步机制、一些常用的并发工具类,并结合示例代码展示如何使用它们来解决实际问题。
1. 同步机制:保证线程安全
多个线程同时访问共享资源时,如果没有正确的同步机制,可能会导致数据不一致或竞争条件(race condition)。为了避免这些问题,Java 提供了以下几种同步机制:
1.1. 使用 synchronized
关键字
synchronized
是 Java 中最基础的同步机制,主要用于防止多个线程同时访问共享资源。可以使用 synchronized
来修饰方法或者代码块。
- 修饰实例方法:表示当前对象的所有实例方法都使用同一个锁。
- 修饰静态方法:表示当前类的所有静态方法使用同一个锁。
- 修饰代码块:可以指定特定的资源作为锁,从而实现更细粒度的控制。
示例代码:
class Counter {
private int count = 0;
// 使用 synchronized 修饰方法,保证线程安全
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class SynchronizedExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 创建多个线程来测试线程安全
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终计数:" + counter.getCount()); // 输出 2000
}
}
解析:
- synchronized:通过使用
synchronized
保证同一时间只有一个线程可以访问increment()
和getCount()
方法,从而避免了竞态条件。
1.2. 使用 ReentrantLock
ReentrantLock
是 java.util.concurrent.locks
包下的一个类,它比 synchronized
提供了更灵活的锁机制。ReentrantLock
允许线程在访问共享资源时显式地获取和释放锁,它支持可中断的锁请求、定时锁请求等特性。
示例代码:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private Lock lock = new ReentrantLock();
// 使用 ReentrantLock 显式获取和释放锁
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 确保释放锁
}
}
public int getCount() {
return count;
}
}
public class LockExample {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 创建多个线程来测试线程安全
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终计数:" + counter.getCount()); // 输出 2000
}
}
解析:
ReentrantLock
:与synchronized
不同,ReentrantLock
允许线程在获取锁后能做更多的控制,如可以通过lock.tryLock()
来尝试获取锁,或者设置超时等。
2. 并发工具类
Java 提供了许多并发工具类,这些类帮助我们在复杂的并发环境下,协调线程的执行顺序,简化并发编程的难度。
2.1. CountDownLatch
CountDownLatch
是一种同步工具类,它允许一个或多个线程等待直到在其他线程中执行的一组操作完成。CountDownLatch
维护一个计数器,每当一个线程完成某个任务时,计数器就会减 1,直到计数器减到 0,等待的线程才能继续执行。
示例代码:
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
int taskCount = 3;
CountDownLatch latch = new CountDownLatch(taskCount);
// 创建并启动 3 个线程
for (int i = 0; i < taskCount; i++) {
final int taskId = i + 1;
new Thread(() -> {
System.out.println("任务 " + taskId + " 正在执行...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown(); // 任务完成,计数器减 1
System.out.println("任务 " + taskId + " 完成");
}).start();
}
latch.await(); // 等待直到计数器为 0
System.out.println("所有任务完成,继续执行主线程");
}
}
解析:
- CountDownLatch:在上述示例中,主线程等待所有子线程完成工作后再继续执行。
latch.countDown()
将计数器减 1,latch.await()
会阻塞主线程直到计数器为 0。
2.2. CyclicBarrier
CyclicBarrier
类允许一组线程相互等待,直到所有线程都到达某个公共屏障点。CyclicBarrier
可以重复使用,因此适用于需要反复等待一组线程到达某个点的场景。
示例代码:
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) throws InterruptedException {
int parties = 3; // 线程数量
CyclicBarrier barrier = new CyclicBarrier(parties, () -> {
System.out.println("所有线程到达屏障,执行一些操作...");
});
// 启动多个线程
for (int i = 0; i < parties; i++) {
final int threadId = i + 1;
new Thread(() -> {
System.out.println("线程 " + threadId + " 到达屏障");
try {
barrier.await(); // 等待其他线程到达屏障
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("线程 " + threadId + " 继续执行");
}).start();
}
}
}
解析:
- CyclicBarrier:每个线程在
barrier.await()
处阻塞,直到所有线程都到达屏障,屏障才会被解除,并执行可选的Runnable
操作。
2.3. Semaphore
Semaphore
是一种计数信号量,它用来控制同时访问特定资源的线程数量。可以用来限制并发线程的数量,适用于资源共享的场景。
示例代码:
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(2); // 限制最多 2 个线程同时访问
// 启动多个线程
for (int i = 0; i < 5; i++) {
final int threadId = i + 1;
new Thread(() -> {
try {
semaphore.acquire(); // 获取许可
System.out.println("线程 " + threadId + " 正在执行...");
Thread.sleep(1000); // 模拟任务执行
System.out.println("线程 " + threadId + " 执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放许可
}
}).start();
}
}
}
解析:
- Semaphore:在这个例子中,
Semaphore
被初始化为 2,意味着最多允许 2 个线程同时访问受限资源。每个线程在执行任务前调用acquire()
方法来获取许可,执行完任务后调用release()
来释放许可。 - 当有多个线程尝试访问时,
acquire()
会使线程阻塞,直到有空余的许可,只有当许可被释放时,其他线程才可以获得许可并继续执行。
3. 其他并发工具类简介
除了 CountDownLatch
、CyclicBarrier
和 Semaphore
,Java 还提供了许多其他的并发工具类,能够帮助开发者解决不同的并发编程问题。
-
Exchanger
:Exchanger
是一个简单的同步点,两个线程通过exchange()
方法交换数据。它适用于需要两个线程互相交换数据的场景。示例代码:
import java.util.concurrent.Exchanger; public class ExchangerExample { public static void main(String[] args) { Exchanger<String> exchanger = new Exchanger<>(); // 线程 1 new Thread(() -> { try { String message = "来自线程1的消息"; System.out.println("线程1: 交换消息 -> " + message); message = exchanger.exchange(message); // 交换数据 System.out.println("线程1: 接收到消息 -> " + message); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); // 线程 2 new Thread(() -> { try { String message = "来自线程2的消息"; System.out.println("线程2: 交换消息 -> " + message); message = exchanger.exchange(message); // 交换数据 System.out.println("线程2: 接收到消息 -> " + message); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } }
解析:
- Exchanger:这个工具类允许两个线程交换数据。在这个例子中,线程 1 和线程 2 通过
exchange()
方法交换字符串信息,exchange()
会阻塞线程,直到两个线程都准备好交换数据。
- Exchanger:这个工具类允许两个线程交换数据。在这个例子中,线程 1 和线程 2 通过
-
Phaser
:Phaser
是一种更灵活的线程同步工具,类似于CountDownLatch
和CyclicBarrier
,但是支持更多的功能,例如可以动态调整参与的线程数。
4. 总结
在今天的学习中,我们深入探讨了 Java 中的几种同步与并发工具类:
- 同步机制:通过
synchronized
关键字和ReentrantLock
来保证线程安全。 - 并发工具类:
CountDownLatch
用于控制线程等待某些操作完成后再继续执行。CyclicBarrier
用于协调多个线程在同一时刻继续执行,适合处理一组线程的协作。Semaphore
用于限制并发线程数,适合处理有限资源的并发访问。Exchanger
用于两个线程之间交换数据,适合需要线程间通信的场景。
这些工具类使得我们在并发编程中能够更容易地控制线程之间的协调和数据同步,避免了常见的并发问题如死锁、竞态条件等。
明天,我们将继续学习 Java 中的其他并发编程相关工具和高级技巧。希望今天的内容能够帮助你更好地理解如何通过同步和并发工具类管理和控制并发线程。
这样,今天的文章已经完整地涵盖了 Java 中的同步机制与并发工具类。希望这些内容能帮助你更好地理解并发编程中的关键概念,并在实践中灵活应用。