synchronized介绍 -Java
sychronized
是 Java 中用于控制多线程访问共享资源的关键字。它能够确保在某一时刻只有一个线程可以访问某个资源,从而避免多线程的并发问题,如数据不一致、竞争条件等。synchronized
的实现依赖于 Java 内部的监视器锁(monitor lock)。
1. synchronized
的作用
synchronized
主要作用是实现线程间的互斥访问,通过锁机制保证:
- 互斥性:同一时间只有一个线程能够访问
synchronized
修饰的代码块或方法。 - 可见性:线程对共享变量的修改对其他线程是可见的,因为在释放锁之前,线程会将修改的值刷新到主内存中。
synchronized
可以用于以下几种场景:
- 修饰方法:对整个方法进行同步,锁对象是当前实例对象(
this
)。 - 修饰静态方法:对静态方法进行同步,锁对象是该类的
Class
对象。 - 修饰代码块:对代码块进行同步,可以指定锁对象。
2. 使用方法
(1) 修饰实例方法
synchronized
修饰实例方法时,锁是当前对象实例 (this
)。
public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } }
- 只有一个线程能同时执行
increment()
或getCount()
方法,因为它们都使用同一把锁(this
)。
(2) 修饰静态方法
synchronized
修饰静态方法时,锁是类的 Class
对象。
public class Counter { private static int count = 0; public static synchronized void increment() { count++; } public static synchronized int getCount() { return count; } }
- 静态方法的锁是
Class
对象,所有线程共享该锁。即便有多个实例对象,也只能有一个线程访问静态方法。
(3) 修饰代码块
synchronized
修饰代码块时,可以指定锁对象,通常是实例对象(this
)或自定义对象。
public class Counter { private int count = 0; private final Object lock = new Object(); public void increment() { synchronized (lock) { count++; } } public int getCount() { synchronized (lock) { return count; } } }
- 通过代码块锁定范围更小的代码,避免对整个方法加锁,从而提高效率。
3. synchronized
的实现原理
synchronized
的底层实现依赖于 JVM 的 Monitor(监视器) 锁机制,它是由操作系统的互斥锁(Mutex)实现的。
(1) Monitor 锁
- 每个 Java 对象都可以作为一个锁,因为每个对象都包含一个对象头(
Object Header
),其中存储了锁的信息。 - 当线程进入同步块或同步方法时,Monitor 锁会被锁定。其他线程需要等待该锁释放后才能继续执行。
(2) 锁的状态
synchronized
使用了不同级别的锁优化性能:
- 无锁状态:没有线程竞争时,锁处于无锁状态。
- 偏向锁:当只有一个线程访问同步代码时,JVM 会将该线程标记为偏向锁持有者,避免锁竞争的开销。
- 轻量级锁:当多个线程尝试获取锁时,JVM 会使用 CAS(Compare-And-Swap)操作进行加锁,避免线程挂起。
- 重量级锁:当线程竞争激烈时,锁会升级为重量级锁,线程进入阻塞状态。
(3) 锁的获取与释放
- 线程进入
synchronized
块时,尝试获取对象的 Monitor 锁。 - 锁被持有后,其他线程无法进入被锁定的代码块。
- 当线程退出同步块或方法时,锁会被释放,其他线程可以继续竞争锁。
(4) Java 对象头
每个 Java 对象的对象头包含两个重要部分:
- Mark Word:存储锁状态(无锁、偏向锁、轻量级锁、重量级锁)以及其他元数据(如哈希值)。
- Class Pointer:指向对象的类元数据。
4. synchronized
的性能优化
为了提升性能,Java 在 JDK 1.6 及以后引入了多种锁优化机制:
- 偏向锁:当锁的竞争很少时,锁会偏向第一个访问它的线程,避免无意义的同步操作。如果其他线程尝试获取锁,偏向锁会撤销。
- 轻量级锁:使用 CAS 操作避免线程的挂起和恢复。如果线程竞争锁失败,轻量级锁会膨胀为重量级锁。
- 锁粗化:如果一个线程频繁进入和退出同步块,JVM 会将多个同步块合并为一个较大的同步块,减少锁操作的开销。
- 锁消除:如果 JVM 通过逃逸分析发现某些同步块中的锁是多余的(不会被其他线程访问),它会消除这些锁。
5. 常见问题
(1) synchronized
和 ReentrantLock
的区别
synchronized
是 Java 的内置锁,而ReentrantLock
是 Java 并发包(java.util.concurrent.locks
)中的显式锁。ReentrantLock
提供了更多的功能,比如可中断的锁获取、超时机制和公平锁等。synchronized
更简洁,适合简单的同步场景。
(2) 性能问题
- 在高并发环境下,
synchronized
可能导致性能问题,特别是线程争抢激烈时会引发线程阻塞。 - JDK 1.6 之后,
synchronized
的性能已经大大优化,常见的锁竞争场景性能接近ReentrantLock
。
(3) 死锁问题
使用 synchronized
时,需要注意避免死锁。比如两个线程分别持有两把锁,并试图获取对方的锁时,会陷入死锁。
Java碎碎念 文章被收录于专栏
来一杯咖啡,聊聊Java的碎碎念呀