🔒 synchronized关键字
深入理解Java最基础的同步机制,掌握对象锁、类锁和锁升级原理
学习目标
- 深入理解synchronized的工作原理
- 掌握对象锁和类锁的区别
- 了解锁升级机制
- 学会识别和避免死锁
- 掌握synchronized的性能优化技巧
synchronized原理
synchronized是Java提供的最基础的同步机制,它基于监视器锁(Monitor Lock)实现。每个Java对象都有一个内置的监视器锁,当线程进入synchronized代码块或方法时,会自动获取这个锁。
synchronized关键字保证了同一时刻只有一个线程可以执行被同步的代码,从而确保线程安全。
监视器锁机制
字节码分析
从字节码层面看,synchronized的实现使用了monitorenter和monitorexit指令:
// Java代码
public void synchronizedMethod() {
synchronized(this) {
// 同步代码块
}
}
// 对应字节码
monitorenter // 获取锁
// 同步代码
monitorexit // 释放锁
monitorexit // 异常情况下的释放锁
对象锁和类锁
synchronized可以修饰实例方法、静态方法和代码块,根据修饰的目标不同,获取的锁也不同。
对象锁(实例锁)
public synchronized void instanceMethod() {
// 获取当前对象的锁
}
public void method() {
synchronized(this) {
// 获取当前对象的锁
}
}
类锁(静态锁)
public static synchronized void staticMethod() {
// 获取类的Class对象的锁
}
public void method() {
synchronized(MyClass.class) {
// 获取类的Class对象的锁
}
}
对象锁和类锁是完全独立的,一个线程获取了对象锁,不会阻止其他线程获取类锁。
锁升级机制
为了提高性能,JVM对synchronized进行了优化,引入了锁升级机制。锁会根据竞争情况从偏向锁升级到轻量级锁,再升级到重量级锁。
- 适用于只有一个线程访问的情况
- 几乎没有性能开销
- 在对象头中记录线程ID
- 当有其他线程竞争时升级
- 适用于线程交替执行的情况
- 使用CAS操作避免阻塞
- 在栈帧中创建锁记录
- 竞争激烈时升级为重量级锁
- 适用于高竞争的情况
- 使用操作系统的互斥量
- 线程会被阻塞和唤醒
- 性能开销最大
偏向锁 → 轻量级锁 → 重量级锁(单向升级,不可降级)
死锁问题
死锁是多线程编程中的经典问题,当两个或多个线程相互等待对方释放锁时,就会发生死锁。
死锁的四个必要条件
死锁示例
public class DeadlockExample {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: 获取lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread 1: 获取lock2");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: 获取lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread 2: 获取lock1");
}
}
});
t1.start();
t2.start();
}
}
死锁预防策略
- 锁排序:所有线程按相同顺序获取锁
- 锁超时:使用tryLock()设置获取锁的超时时间
- 死锁检测:定期检测死锁并采取恢复措施
- 避免嵌套锁:尽量避免在持有锁的情况下获取其他锁
性能优化
虽然synchronized使用简单,但在高并发场景下可能成为性能瓶颈。了解优化技巧有助于提升程序性能。
优化策略
将大锁拆分为多个小锁,减少锁竞争
// 粗粒度锁
synchronized(this) {
updateField1();
updateField2();
}
// 细粒度锁
synchronized(lock1) { updateField1(); }
synchronized(lock2) { updateField2(); }
读写分离,使用ReadWriteLock
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
JVM会自动消除不必要的锁
// 局部变量,JVM会消除同步
StringBuffer sb = new StringBuffer();
sb.append("hello");
sb.append("world");
- 尽量缩短同步代码块的执行时间
- 避免在循环中使用synchronized
- 考虑使用并发集合类替代同步集合
- 在高并发场景下考虑使用Lock接口
实战案例:银行转账
通过银行转账的例子,演示如何正确使用synchronized解决并发安全问题。
public class BankAccount {
private double balance;
private final Object lock = new Object();
public BankAccount(double initialBalance) {
this.balance = initialBalance;
}
public void deposit(double amount) {
synchronized (lock) {
balance += amount;
System.out.println("存入: " + amount + ", 余额: " + balance);
}
}
public boolean withdraw(double amount) {
synchronized (lock) {
if (balance >= amount) {
balance -= amount;
System.out.println("取出: " + amount + ", 余额: " + balance);
return true;
}
return false;
}
}
public double getBalance() {
synchronized (lock) {
return balance;
}
}
// 转账方法 - 避免死锁的实现
public static void transfer(BankAccount from, BankAccount to, double amount) {
// 按对象hashCode排序,避免死锁
BankAccount firstLock = from.hashCode() < to.hashCode() ? from : to;
BankAccount secondLock = from.hashCode() < to.hashCode() ? to : from;
synchronized (firstLock.lock) {
synchronized (secondLock.lock) {
if (from.balance >= amount) {
from.balance -= amount;
to.balance += amount;
System.out.println("转账成功: " + amount);
} else {
System.out.println("余额不足,转账失败");
}
}
}
}
}