文章
Java中的synchronized
目录
- 下面从基础概念、三种用法、工作机制、注意事项和典型场景五个方面,详解 Java 中 synchronized 的用法与原理。
- 1. 基础概念
- 2. 三种用法
- 2.1 修饰实例方法(对象锁)
- 2.2 修饰静态方法(类锁)
- 2.3 修饰代码块(灵活锁定)
- 3. 工作机制与 JVM 优化
- 4. 注意事项
- 5. 典型场景示例
- 下面列出一系列 synchronized** 实际用法的实例**,涵盖常见并发场景、设计模式和线程协作,帮助你理解 synchronized 在不同上下文中的使用。
- 🌟 1. 方法同步(实例锁)
- 🌟 2. 静态方法同步(类锁)
- 🌟 3. 同步代码块:细粒度控制
- 🌟 4. 多个锁保护不同资源(避免锁竞争)
- 🌟 5. 线程安全的单例模式(双重检查锁)
- 🌟 6. 线程协作:wait/notify 配合 synchronized
- 🌟 7. 死锁示例(注意避免)
- 🌟 8. 多线程售票系统
- 🌟 9. 保护共享集合
- 🌟 10. 嵌套 synchronized(重入锁)
- 📎 参考文章
下面从基础概念、三种用法、工作机制、注意事项和典型场景五个方面,详解 Java 中 synchronized 的用法与原理。#
1. 基础概念#
- 互斥锁(Mutex):
synchronized强制同一时刻只有一个线程可以执行被它修饰的代码块或方法,以保证共享资源的线程安全。 - 监视器(Monitor):每个 Java 对象都有一个与之关联的监视器锁(也称对象监视器)。线程要么持有该监视器锁才能进入对应的
synchronized区域,要么被阻塞等待。
2. 三种用法#
2.1 修饰实例方法(对象锁)#
public synchronized void foo() {
// 等同于 synchronized(this) { … }
// 对当前实例加锁
}
- 锁对象:
this - 作用范围:整个方法体
- 同一时刻,只有一个线程能执行该实例的任何一个
synchronized实例方法。
2.2 修饰静态方法(类锁)#
public static synchronized void bar() {
// 等同于 synchronized(MyClass.class) { … }
// 对 Class 对象加锁
}
- 锁对象:对应的
Class对象 - 作用范围:整个静态方法
- 不同实例间共享同一把类锁;静态方法与实例方法所用锁相互独立。
2.3 修饰代码块(灵活锁定)#
public void baz() {
synchronized(lockObject) {
// 只对 lockObject 加锁
// lockObject 可以是 this、Class 对象或任意非空引用
}
}
- 锁对象:由程序员指定
- 作用范围:仅限于代码块内部
- 推荐:当只需要保护局部临界区时,用代码块可以缩小锁粒度,提升并发性能。
3. 工作机制与 JVM 优化#
- 进入与退出
- 进入:线程尝试获取监视器锁,若锁空闲则获得并进入;否则进入阻塞队列等待。
- 退出:执行完方法/块或抛出异常时,自动释放监视器锁,并唤醒等待队列中下一个线程。
- 重入性(Reentrant)
- 同一线程可对同一把锁重复加锁(如在一个
synchronized方法里调用另一个同锁方法),JVM 会记录加锁计数,退出时需对应释放。
- 同一线程可对同一把锁重复加锁(如在一个
- 锁优化(HotSpot JVM)
- 偏向锁:默认倾向于第一个获得锁的线程,减少后续竞争时的 CAS 操作。
- 轻量级锁:在无或者低竞争场景下,用对象头中的栈锁记录持有者,避免操作系统的重量级阻塞。
- 重量级锁:高竞争时升级,会进入操作系统 Mutex,阻塞与唤醒成本较高。
4. 注意事项#
- 锁粒度:
- 过大:会严重影响并发性能。
- 过小:可能产生更多的锁开销并增加死锁风险。
- 建议:只在必要的临界区使用
synchronized代码块。
- 锁对象选择:
- 切勿使用可变对象或公共变量(如:
String常量、Integer装箱对象等)做锁,否则可能遭遇意外锁竞争。 - 最好使用私有的、专用的锁对象:
- 切勿使用可变对象或公共变量(如:
private final Object lock = new Object(); ```
- 死锁风险:
- 当多个线程互相持有对方需要的锁且都在等待时,就会死锁。
- 预防:
- 始终以相同顺序获取多把锁。
- 尽量减少嵌套锁。
- 设置超时或尝试锁(可考虑
ReentrantLock.tryLock())。
- 可见性与有序性:
synchronized不仅保证互斥,还具备 内存屏障 功能:- 释放锁前,线程会把自己工作内存中的变量刷新回主内存。
- 竞争锁成功前,会清空工作内存并从主内存重新读取最新值。
- 因此
synchronized块外的读写对锁内的操作可见。
5. 典型场景示例#
下面列出一系列 synchronized** 实际用法的实例**,涵盖常见并发场景、设计模式和线程协作,帮助你理解 synchronized 在不同上下文中的使用。#
🌟 1. 方法同步(实例锁)#
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
同步的是整个方法,相当于 synchronized(this)。适合方法体不复杂,锁粒度可以稍大一些的场景。
🌟 2. 静态方法同步(类锁)#
public class GlobalCounter {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
synchronized 修饰静态方法,锁住的是 GlobalCounter.class,所有线程共享该类锁。
🌟 3. 同步代码块:细粒度控制#
public class FineGrainedCounter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
只锁需要保护的代码,减小锁范围,有利于并发性能。
🌟 4. 多个锁保护不同资源(避免锁竞争)#
public class MultiLockExample {
private int a = 0;
private int b = 0;
private final Object lockA = new Object();
private final Object lockB = new Object();
public void modifyA() {
synchronized (lockA) {
a++;
}
}
public void modifyB() {
synchronized (lockB) {
b++;
}
}
}
分离资源,加快并发处理速度。
🌟 5. 线程安全的单例模式(双重检查锁)#
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
volatile 防止指令重排,synchronized 保证线程安全。
🌟 6. 线程协作:wait/notify 配合 synchronized#
public class WaitNotifyExample {
private final Object lock = new Object();
private boolean condition = false;
public void doWait() throws InterruptedException {
synchronized (lock) {
while (!condition) {
System.out.println("Thread waiting...");
lock.wait();
}
System.out.println("Condition met, proceeding.");
}
}
public void doNotify() {
synchronized (lock) {
condition = true;
lock.notifyAll();
System.out.println("Condition updated, notified.");
}
}
}
wait() 和 notify() 必须在 synchronized 内部使用,因为它们需要当前线程持有锁。
🌟 7. 死锁示例(注意避免)#
public class DeadlockExample {
private final Object resourceA = new Object();
private final Object resourceB = new Object();
public void method1() {
synchronized (resourceA) {
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (resourceB) {
System.out.println("method1 acquired both locks");
}
}
}
public void method2() {
synchronized (resourceB) {
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (resourceA) {
System.out.println("method2 acquired both locks");
}
}
}
}
两个线程互相等待对方释放锁会造成死锁。避免方法:统一加锁顺序 或使用 tryLock。
🌟 8. 多线程售票系统#
public class TicketSeller implements Runnable {
private int tickets = 10;
public void run() {
while (true) {
synchronized (this) {
if (tickets <= 0) break;
System.out.println(Thread.currentThread().getName() + " sold ticket: " + tickets--);
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
}
}
}
public static void main(String[] args) {
TicketSeller seller = new TicketSeller();
new Thread(seller, "Thread A").start();
new Thread(seller, "Thread B").start();
}
}
演示共享资源竞争和线程同步。
🌟 9. 保护共享集合#
import java.util.*;
public class SafeList<T> {
private final List<T> list = new ArrayList<>();
public void add(T element) {
synchronized (list) {
list.add(element);
}
}
public T get(int index) {
synchronized (list) {
return list.get(index);
}
}
}
使用 Collections.synchronizedList(...) 可以避免手动同步。
🌟 10. 嵌套 synchronized(重入锁)#
public class ReentrantExample {
public synchronized void outer() {
System.out.println("Outer");
inner();
}
public synchronized void inner() {
System.out.println("Inner");
}
}
Java 的内置锁是可重入的,即一个线程可以重复获取同一个锁而不会死锁。
通过以上内容,你应当能系统地掌握 synchronized 在 Java 中的各种用法、背后机制与注意事项,并能在实际并发场景中灵活运用。