Java 多线程系列详解:深度解密线程同步与锁的奥秘
在多线程编程中,同步与锁机制是保障数据一致性与线程安全的关键技术。本文作为 Java 多线程系列的中篇,将深入探讨线程同步的重要性,并通过实例讲解 synchronized、显式锁(如 ReentrantLock)、读写锁(ReadWriteLock)、信号量(Semaphore)、栅栏(CyclicBarrier)等机制的使用,以及线程间通信的实现和对比。
一、线程同步的重要性
1. 什么是线程同步?
线程同步是一种协调多线程访问共享资源的技术,目的是避免线程间竞争导致的数据不一致问题。
2. 数据不一致问题的示例
以下代码模拟了多个线程对共享变量的访问,展示了数据不一致问题:
public class Counter {
private int count = 0;
public void increment() {
// 让线程在这里故意延时,制造更多线程争夺的可能性
int temp = count;
try {
Thread.sleep(1); // 模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
count = temp + 1;
}
public int getCount() {
return count;
}
public static void main(String[] args) {
Counter counter = new Counter();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终计数值: " + counter.getCount());
}
}
输出结果可能是: 小于 2000,因为多个线程同时对 count 进行读写,导致了竞争条件。
为了解决这些问题,Java 提供了多种线程同步技术。
二、synchronized 关键字
synchronized 是 Java 中最基本的同步机制,用于确保同一时间只有一个线程可以访问同步代码块。
1. 使用方式
1.1 修饰实例方法
锁住当前对象实例。
public synchronized void increment() {
count++;
}
1.2 修饰静态方法
锁住当前类的 Class 对象。
public static synchronized void staticIncrement() {
staticCount++;
}
1.3 修饰代码块
锁住指定对象,灵活性更高。
public void increment() {
synchronized (this) {
count++;
}
}
2. 示例:银行账户转账
以下代码模拟银行账户的多线程存取款操作,确保余额计算正确。
public class BankAccount {
private double balance;
public synchronized void deposit(double amount) {
balance += amount;
System.out.println(Thread.currentThread().getName() + " 存款 " + amount + ",当前余额: " + balance);
}
public synchronized void withdraw(double amount) {
if (balance >= amount) {
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取款 " + amount + ",当前余额: " + balance);
} else {
System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足!");
}
}
}
三、显式锁:ReentrantLock
ReentrantLock 是一个灵活的显式锁,提供了 synchronized 无法实现的高级功能。
1. 主要方法
lock():加锁。unlock():释放锁。tryLock():尝试获取锁,如果失败则立即返回。lockInterruptibly():可响应中断的加锁。
2. 使用场景:停车场管理
以下代码模拟停车场管理系统,确保线程安全地操作停车位数量。
import java.util.concurrent.locks.ReentrantLock;
public class ParkingLot {
private int availableSpaces = 3;
private final ReentrantLock lock = new ReentrantLock();
public void park() {
lock.lock();
try {
if (availableSpaces > 0) {
System.out.println(Thread.currentThread().getName() + " 停车成功,剩余车位: " + (--availableSpaces));
} else {
System.out.println(Thread.currentThread().getName() + " 停车失败,车位已满!");
}
} finally {
lock.unlock();
}
}
}
四、读写锁(ReadWriteLock)
读写锁将读写操作分离,允许多个线程同时读,而写操作是独占的。
1. 主要方法
readLock().lock():获取读锁。writeLock().lock():获取写锁。
2. 使用场景:共享资源的读写分离
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteExample {
private int data = 0;
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void write(int value) {
lock.writeLock().lock();
try {
data = value;
System.out.println(Thread.currentThread().getName() + " 写入数据: " + data);
} finally {
lock.writeLock().unlock();
}
}
public int read() {
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " 读取数据: " + data);
return data;
} finally {
lock.readLock().unlock();
}
}
}
五、信号量(Semaphore)
信号量用于控制同时访问特定资源的线程数量。
1. 主要方法
acquire():获取信号量。release():释放信号量。
2. 使用场景:限制同时访问的线程数
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private final Semaphore semaphore = new Semaphore(3);
public void accessResource() {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 访问资源");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " 释放资源");
semaphore.release();
}
}
public static void main(String[] args) {
SemaphoreExample example = new SemaphoreExample();
for (int i = 0; i < 10; i++) {
new Thread(example::accessResource).start();
}
}
}
六、栅栏(CyclicBarrier)
CyclicBarrier 用于让一组线程达到一个屏障点后再继续执行。
1. 主要方法
await():等待所有线程到达屏障点。
2. 使用场景:任务分阶段执行
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
private final CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有线程到达屏障点,继续执行");
});
public void performTask() {
try {
System.out.println(Thread.currentThread().getName() + " 到达屏障点");
barrier.await();
System.out.println(Thread.currentThread().getName() + " 开始执行任务");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
CyclicBarrierExample example = new CyclicBarrierExample();
for (int i = 0; i < 3; i++) {
new Thread(example::performTask).start();
}
}
}
七、倒计时闩(CountDownLatch)
CountDownLatch 用于让一个线程等待其他线程完成操作。
1. 主要方法
countDown():递减计数器。await():等待计数器归零。
2. 使用场景:多个子任务完成后汇总结果
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
private final CountDownLatch latch = new CountDownLatch(3);
public void performTask() {
System.out.println(Thread.currentThread().getName() + " 执行任务");
latch.countDown();
}
public void waitForTasks() {
try {
latch.await();
System.out.println("所有任务完成,主线程继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
CountDownLatchExample example = new CountDownLatchExample();
for (int i = 0; i < 3; i++) {
new Thread(example::performTask).start();
}
example.waitForTasks();
}
}
八、总结
| 工具 | 描述 | 使用场景 |
|---|---|---|
synchronized | 基础同步机制 | 保证共享资源的互斥访问 |
ReentrantLock | 显式锁,功能更灵活 | 高级锁机制,支持可中断和超时功能 |
ReadWriteLock | 读写分离,提高并发性能 | 多线程读取多,写入少的场景 |
Semaphore | 控制并发线程数量 | 限制同时访问资源的线程数 |
CyclicBarrier | 同步点,所有线程到达后继续执行 | 阶段性任务同步 |
CountDownLatch | 等待多个线程完成任务 | 子任务完成后主线程继续执行 |
这些工具各有适用场景,可以根据实际需求选择合适的机制来实现线程同步和协调。