JAVA锁机制
Java中的锁机制是并发编程的核心,用于解决多线程共享资源竞争问题,保证线程安全。
基本概念
在并发场景中,多个线程同时操作共享资源时,可能导致数据不一致(脏读、丢失更新)。锁的作用是通过控制线程对资源的访问权限,确保同一时间只有特定的线程能操作资源,从而保证线程安全。
锁的核心特性:
- 互斥性:同一时间只有一个线程能持有锁,其他线程需等待;
- 可见性:线程释放锁时,其修改的共享资源会刷新到主内存;线程获取锁时,会从主内存加载最新的资源;
- 可重入性:线程可重复获取已持有的锁(避免死锁);
锁的分类
按锁的状态分类
偏向锁:
- 适应于只有一个线程访问同步块的场景;
- 第一次获取锁时,将线程ID记录在对象头中;
- 后续该线程再次获取锁时无需进行同步操作;
轻量级锁:
- 多个线程交替执行同步块,不存在竞争的情况下使用;
- 通过CAS操作和自旋实现
重量级锁:
- 当存在多个线程竞争锁时使用;
- 线程阻塞和唤醒需要操作系统介入,开销大;
按锁的竞争机制分类
乐观锁:
- 认为数据一般不会发生冲突;
- 使用CAS操作实现;
- 如AtomicInteger等原子类;
- 适用于冲突较少的场景;
悲观锁:
- 认为数据会发生冲突;
- 整个数据处理过程中锁定数据;
- 适用于冲突较多的场景;
按公平性分类
公平锁:
- 线程获取锁的顺序与请求顺序一致,无插队现象;
- 公平性高,开销较大,性能较低;
非公平锁:
- 线程获取锁的顺序不保证与请求顺序一致,允许新线程插队;
- 性能较高,可能导致部分线程饥饿;
按锁的共享性分类
独占锁(排他锁):
- 同一时间只有一个线程持有锁,其他线程必须等待。
- 适应场景:写操作
共享锁:
- 允许多个线程同时持有锁,仅限制写操作。
- 适应场景:读操作。
锁的核心问题与解决
死锁:
多个线程互相等待对方释放而进入无限阻塞
- 固定锁获取顺序,使用tryLock设置超时,短期释放锁。
活锁:
线程不断尝试获取锁,但因某种条件始终失败
- 引入随机等待时间,打破重试顺序。
饥饿:
低优先级线程长期无法获取锁
- 使用公平锁,控制优先级
锁的实现
synchronized
synchronized是基于对象监视器实现的。每个java对象都有一个与之关联的监视器,当线程进入synchronized代码块时,它会尝试获取对象的监视器锁。如果获取成功,线程可以执行同步代码块;如果获取失败,线程会被阻塞,直到锁释放。
public class Demo {private int i = 0;//同步方法public synchronized void addI(){i++;}//同步代码块public void addJ(){synchronized(this){i++;}}public int getI() {return i;}public void reset() {i = 0;}
}
ReentrantLock
ReentrantLock。多个线程可以多次获取同意把锁,可以选择公平锁或非公平锁;可以设置获取锁的超时时间,支持多个条件变量。
核心方法:
- lock():获取锁(获取到则阻塞);
- trylock():尝试获取锁(立刻返回true或false,避免死锁);
- unlock():释放锁(必须在finally中使用,比避免死锁);
- newCondition():创建条件变量,用于线程间通信。
public class ReentrantLockExample {private int count = 0;private final ReentrantLock lock = new ReentrantLock();private final Condition condition = lock.newCondition();// 基本的锁操作public void increment() {lock.lock();try {count++;} finally {lock.unlock();}}// 可中断的锁操作public void interruptibleIncrement() throws InterruptedException {lock.lockInterruptibly();try {count++;} finally {lock.unlock();}}// 带超时的锁操作public boolean tryIncrement(int timeoutMs) throws InterruptedException {if (lock.tryLock(timeoutMs, java.util.concurrent.TimeUnit.MILLISECONDS)) {try {count++;return true;} finally {lock.unlock();}}return false;}// 非阻塞尝试获取锁public boolean tryIncrementOnce() {if (lock.tryLock()) {try {count++;return true;} finally {lock.unlock();}}return false;}// 等待条件public void waitForCondition() throws InterruptedException {lock.lock();try {condition.await();} finally {lock.unlock();}}// 发送信号public void signal() {lock.lock();try {condition.signal();} finally {lock.unlock();}}public int getCount() {return count;}public void reset() {count = 0;}public boolean isLocked() {return lock.isLocked();}public boolean hasQueuedThreads() {return lock.hasQueuedThreads();}
}
ReentrantReadWriteLock
一种读写分离锁,允许多个读线程同时访问,但写线程与读线程,写线程与写线程互斥。适合读多写少的场景,提升并发效率。
public class ReentrantReadWriteLockExample {private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();private final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();private String data;public String read() {readLock.lock();try {return data;} finally {readLock.unlock();}}public void write(String newData) {writeLock.lock();try {data = newData;} finally {writeLock.unlock();}}
}
StampedLock
StampedLock是java8新引入的一种锁,提供了比读写锁更好的性能。
三种模式:
- 写锁(独占):获取戳记,释放时许传入戳记。
- 读锁(共享):与ReentrantReadWriteLock的读锁类似。
- 乐观读:无锁状态读取,验证数据是否被修改。
public void move(double deltaX, double deltaY) {long stamp = lock.writeLock();try {x += deltaX;y += deltaY;} finally {lock.unlockWrite(stamp);}}// 乐观读public double distanceFromOrigin() {long stamp = lock.tryOptimisticRead();double currentX = x, currentY = y;if (!lock.validate(stamp)) {// 乐观读失败,升级为悲观读stamp = lock.readLock();try {currentX = x;currentY = y;} finally {lock.unlockRead(stamp);}}return Math.sqrt(currentX * currentX + currentY * currentY);}
}
锁的选择
类型 | 特点 | 适应场景 |
---|---|---|
synchronized | JVM内置,自动释放,可重入 | 简单同步场景 |
ReentrantLock | 可中断,可现时,公平锁 | 更精细的控制场景 |
ReadWriteLock | 读写分离,提高并发能力 | 读多写少的场景 |
StampedLock | 支持乐观读,高性能 | 高并发读多写少场景 |