文章目录
- 一、线程同步的核心目标
- 二、线程安全的判定条件
- 三、同步方式一:synchronized 关键字
- 1. 同步代码块
- 2. 同步方法
- 四、锁的释放与不释放场景
- 1. 自动释放锁的场景
- 2. 不会释放锁的场景
- 五、同步方式二:ReentrantLock(显式锁)
- 1. 核心特性
- 2. 使用规范
- 六、synchronized 与 ReentrantLock 的对比
- 总结
以下是对线程同步相关知识点的系统整理,涵盖同步方式、锁机制、线程安全条件及释放规则等核心内容:
一、线程同步的核心目标
解决多线程并发操作共享资源时的线程安全问题(如数据不一致、重复修改等),通过控制线程对共享资源的访问顺序,保证操作的原子性、可见性和有序性。
二、线程安全的判定条件
出现线程安全问题必须同时满足两个条件:
- 多线程环境:程序运行在多个线程并发执行的场景中。
- 共享资源:多个线程操作的是同一份资源(如共享变量、对象属性等)。
解决方案:通过“锁机制”保证同一时间只有一个线程能操作共享资源,核心是所有线程必须使用同一把锁。
三、同步方式一:synchronized 关键字
synchronized
是 Java 内置的隐式锁机制,通过“监视器(Monitor)”实现,可修饰代码块或方法。
1. 同步代码块
-
语法:
synchronized (锁对象) {// 需要同步的代码(操作共享资源的逻辑) }
-
核心要素:
- 锁对象:可以是任意对象(通常使用共享资源本身或
this
),但必须保证所有线程使用同一把锁。 - 同步范围:仅包含操作共享资源的代码,范围越小,效率越高。
- 锁对象:可以是任意对象(通常使用共享资源本身或
-
示例:
// 共享资源 private int count = 0; private Object lock = new Object(); // 锁对象// 多线程执行的方法 public void increment() {synchronized (lock) { // 所有线程使用同一把lock锁count++; // 操作共享资源} }
2. 同步方法
-
语法:直接在方法声明处添加
synchronized
关键字,隐式指定锁对象。// 实例方法 public synchronized void method1() { ... }// 静态方法 public static synchronized void method2() { ... }
-
隐式锁对象规则:
- 实例方法:锁对象为
this
(当前对象实例)。 - 静态方法:锁对象为
类名.class
(当前类的字节码对象,全局唯一)。
- 实例方法:锁对象为
-
特点:
- 无需显式指定锁对象,简化代码,但同步范围是整个方法(可能降低效率)。
- 静态同步方法和实例同步方法使用的是不同锁(类对象 vs 实例对象),两者不会互斥。
四、锁的释放与不释放场景
synchronized
锁的释放由 JVM 自动管理,以下情况会触发释放或不释放:
1. 自动释放锁的场景
- 同步代码块或同步方法正常执行完毕(执行到代码块末尾)。
- 同步代码中出现未捕获的异常或错误,导致线程终止。
- 同步代码中调用
wait()
方法(线程进入等待状态,主动释放锁)。 - 同步代码中通过
break
、return
等语句退出代码块/方法。
2. 不会释放锁的场景
- 调用
Thread.sleep(long)
:线程休眠时仍持有锁(其他线程无法获取)。 - 调用
Thread.yield()
:线程主动让出 CPU,但锁未释放。 - 线程被
suspend()
暂停(已过时方法):暂停期间仍持有锁,可能导致死锁。
五、同步方式二:ReentrantLock(显式锁)
java.util.concurrent.locks.ReentrantLock
是 JDK 提供的显式锁实现,比 synchronized
更灵活。
1. 核心特性
- 可重入性:同一线程可多次获取同一把锁(与
synchronized
一致)。 - 公平性:通过构造函数指定是否为公平锁:
ReentrantLock fairLock = new ReentrantLock(true); // 公平锁(按请求顺序获取锁) ReentrantLock nonfairLock = new ReentrantLock(false); // 非公平锁(默认,可能插队)
- 公平锁:线程按请求锁的顺序获取锁,避免饥饿,但效率较低。
- 非公平锁:允许线程“插队”获取锁,效率更高,但可能导致部分线程长期等待。
2. 使用规范
必须手动获取锁和释放锁,且释放操作需放在 finally
中,确保锁一定被释放:
ReentrantLock lock = new ReentrantLock();public void operation() {lock.lock(); // 手动获取锁try {// 操作共享资源(可能出现线程安全的代码)} finally {lock.unlock(); // 手动释放锁,必须在finally中}
}
六、synchronized 与 ReentrantLock 的对比
特性 | synchronized | ReentrantLock |
---|---|---|
锁类型 | 隐式锁(JVM 自动管理) | 显式锁(手动获取和释放) |
公平性 | 仅支持非公平锁 | 可通过构造函数指定公平/非公平 |
可中断性 | 不可中断 | 支持中断(lockInterruptibly() ) |
条件变量 | 仅支持 wait() /notify() | 支持多个 Condition 对象,更灵活 |
性能 | JDK 1.6 后优化,与 ReentrantLock 接近 | 高并发下略优,功能更丰富 |
总结
- 线程同步的核心是通过锁机制控制共享资源的并发访问,需保证多线程使用同一把锁。
synchronized
适合简单场景,隐式管理锁的获取和释放,易用性高。ReentrantLock
适合复杂场景(如公平性控制、中断处理),需手动管理锁,灵活性更高。- 无论使用哪种锁,都需注意锁的释放时机,避免死锁(如
sleep()
不释放锁可能导致的问题)。
合理选择同步方式是保证多线程程序正确性和效率的关键。