在Java 并发编程的世界里,锁就像一个守门员,决定着谁能进、谁得等。如果你还在被 synchronized
搞得头晕,或者对 ReentrantLock
、AQS 一知半解,今天就让我们用最通俗易懂的方式,把 Java 锁的前世今生讲个明白!
1. synchronized:并发编程的老伙计
synchronized
关键字是 Java 并发控制的“祖师爷”,它的特点是:
✅简单易用:直接加在方法或代码块上,开发者几乎不用操心底层实现。
✅自动释放:线程执行完 synchronized
代码块后,锁会自动释放,避免死锁。
✅可重入:支持同一个线程多次获取同一把锁,不会发生死锁。
用法示例
public class SynchronizedExample {
private int count=0;
public synchronized void increment() { // 修饰方法
count++;
}
public void syncBlock() {
synchronized (this) { // 修饰代码块
count++;
}
}
}
synchronized 的缺点
- • 性能较低:它是 JVM 层面的重量级锁,在 Java 早期版本中,线程进入 synchronized 会导致 CPU 上下文切换,影响效率。
- • 不能尝试获取:如果一个线程获取不到锁,只能傻等,不能像 Lock 那样超时退出。
优化升级:JDK 1.6 之后
JVM针对 synchronized
进行了优化,引入偏向锁、轻量级锁、自旋锁等技术,提升了性能。
2. ReentrantLock:synchronized 的升级版
synchronized
虽然简单,但在高并发场景下显得力不从心。这时,JDK 5 引入了 ReentrantLock
,它是 synchronized
的可替代方案,性能更强大。
核心特性
✅可中断:支持 lockInterruptibly()
,可以响应中断,避免线程永久阻塞。
✅支持超时:tryLock(timeout, unit)
允许线程等待一段时间,如果超时,则放弃获取锁。
✅公平锁 / 非公平锁:可以选择公平模式(FIFO)或非公平模式(默认,性能更好)。
使用示例
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock=new ReentrantLock();
public void safeMethod() {
lock.lock(); // 获取锁
try {
// 临界区代码
System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行");
} finally {
lock.unlock(); // 释放锁
}
}
}
⚠️注意:lock()
和 unlock()
必须配对使用,否则可能导致死锁!
3. AQS(AbstractQueuedSynchronizer):并发锁的终极基石
AQS是 JDK 并发包(java.util.concurrent
)的核心,ReentrantLock
、Semaphore
、CountDownLatch
都是基于 AQS 实现的。
AQS 的工作原理
1、 1.CLH队列:AQS采用CLH(Craig,Landin,andHagersten)锁队列来管理等待的线程;
2、 2.独占锁/共享锁:;
3、 •独占锁(ReentrantLock):同一时刻只有一个线程能获取锁;
• 共享锁(ReadWriteLock):允许多个线程同时读取,但写时必须独占。
自定义锁(基于 AQS)
如果你想实现自己的锁,可以继承 AQS 并重写 tryAcquire()
和 tryRelease()
方法:
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
public class CustomLock {
private static class Sync extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) { // CAS 修改 state
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
private final Sync sync = new Sync();
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
}
这样,你就实现了一个最简单的可重入锁!
4. 各种锁的比较
锁类型 | 特点 | 适用场景 |
synchronized | JVM 层面实现,自动释放锁,低效但简单 | 适用于简单同步 |
ReentrantLock | 支持超时、公平锁、可中断 | 适用于高并发场景 |
ReadWriteLock | 读写分离,多个线程可同时读取 | 适用于读多写少的情况 |
StampedLock | 乐观读锁,提高读性能 | 适用于写少读多场景 |
Semaphore | 控制并发线程数 | 限流、资源控制 |
CountDownLatch | 线程计数器,等待多个线程完成任务 | 任务批量执行 |
CyclicBarrier | 线程屏障,所有线程都到达才执行 | 任务协同 |
5. 实战:如何选择合适的锁?
- • 简单同步 👉 synchronized
- • 高并发,避免 CPU 切换 👉 ReentrantLock
- • 读多写少 👉 ReadWriteLock 或 StampedLock
- • 需要控制并发线程数 👉 Semaphore
- • 多个任务需要等待完成 👉 CountDownLatch 或 CyclicBarrier
总结
1、 1.synchronized
适合简单同步,但性能较低;
2、 2.ReentrantLock
提供更灵活的锁机制(可中断、可超时);
3、 3.AQS是ReentrantLock
、Semaphore
、CountDownLatch
等的基石;
4、 4.选择合适的锁,能让你的代码更高效!;
你是不是对 Java 并发锁彻底搞明白了?如果你觉得有收获,记得点赞+收藏,以后遇到锁问题,就来翻这篇文章!🚀🚀🚀