synchronized简介
- 描述
synchronized 被称为重量级锁, 1.6 版本后进行了优化,它可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块。
- 主要操作对象是方法或者代码块
- 核心原理为 Java 对象头 以及 Monitor:JVM基于进入和退出Monitor对象来实现方法同步和代码块同步
- Java 对象头 和 Monitor
对象头
- Mark Word(标记字段)、Klass Pointer(类型指针)
- Klass Pointer : 类元数据指针,决定是何数据
- Mark Word : 自身运行时数据 (hashcode,锁状态,偏向,标志位等)
Monitor
- 互斥 :一个 Monitor 锁在同一时刻只能被一个线程占用
两者关系
- Monitor 是一种对象类型 , 任何Java 对象都可以是 Monitor 对象
- 当Java 对象被 synchronized 修饰时 , 就可以当成 Monitor 对象进行处理
数据结构
- ObjectMonitor中有两个队列以及一个区域
- _WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象)
- _owner (指向持有ObjectMonitor对象的线程) 区域
- 过程
- 1 当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合, 此时开始尝试获取monitor
- 2 当线程获取到对象的monitor 后进入 _Owner 区域 ,并把 monitor中的owner变量 设置为当前线程同时monitor中的计数器count加1
- 3 若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒。
- 4 若当前线程执行完毕 也将 释放monitor(锁) 并 复位变量的值,以便 其他线程进入获取monitor(锁)
- ObjectMonitor中有两个队列以及一个区域

- Monitor 指令
- monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之匹配
```
// Step 1 : 准备简单的Demo
public class SynchronizedService {
public void method() {
synchronized (this) {
} } }System.out.println("synchronized 代码块");
- monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之匹配
```
// Step 1 : 准备简单的Demo
public class SynchronizedService {
public void method() {
synchronized (this) {
// Step 2 : 查看汇编码 javap -c -s -v -l SynchronizedService.class
// Step 3 : 注意其中 3 和 13 以及 19 public void method(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=1 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #3 // String synchronized 代码块 9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 12: aload_1 13: monitorexit 14: goto 22 17: astore_2 18: aload_1 19: monitorexit 20: aload_2 21: athrow 22: return ```
- synchronized 方法底层逻辑 (ACC_SYNCHRONIZED标识)
- 方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。
- JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。
- 异常处理 : 如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放
- synchronized 用法
- synchronized关键字最主要的三种使用方式:
- 修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
- 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。
- 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。
- 所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
- 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。
- synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。
- synchronized 相关问题
- 谈谈 synchronized和ReenTrantLock 的区别
- 两者都是可重入锁
- synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API
- ReenTrantLock 比 synchronized 增加了一些高级功能
- 等待可中断;可实现公平锁;可实现选择性通知(锁可以绑定多个条件)
- synchronized 与等待唤醒机制 (notify/notifyAll和wait)
- 等待唤醒机制需要处于synchronized代码块或者synchronized方法中 , 调用这几个方法前必须拿到当前对象的监视器monitor对象
- synchronized 与 线程中断
- 线程的中断操作对于正在等待获取的锁对象的synchronized方法或者代码块并不起作用
- 多线程中的锁概念
锁按照等级分类
- 偏向锁
- 减少同一线程获取锁的代价 (在大多数情 况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得)
- 如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程
- 偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁
- 轻量级锁
- 对绝大部分的锁,在整个同步周期内都不存在竞争
- 轻量级锁所适应的场景是线程交替执行同步块的场合
- 如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁
- 自旋锁
- 假设等待后当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环
- 在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起
- 可以减少了线程上下文切换,但是增加了CPU消耗
- 重量级锁
锁的操作
- 锁清除
Java虚拟机在 JIT编译时 (可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,
通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间
- Java 常见的锁
- synchronized 关键字 , 重量锁
- ReentrantLock 重入锁
- ReadWriteLock 读写锁
其他锁概念
- 内部锁
- synchronized : 锁对象的引用 , 锁保护的代码块
- 每个Java 对象都可以隐式地扮演一个用于同步的锁的角色 ,这些内置的锁被称为 内部锁 或 监视器锁
- 公平锁/非公平锁
- 公平与非公平指的是竞争获取锁的顺序
- ReentrantLock默认非公平锁,可通过构造函数选择公平锁,Synchronized是非公平锁。
- 可重入锁
- 可重入锁指在一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
- ReentrantLock与Synchronized都是可重入的
- 独享锁/共享锁
- 独享锁是指一个锁只能一个线程独有,共享锁指一个锁可被多个线程共享
- 对于ReadWriteLock,读锁是共享锁,写锁是独享所
- 互斥锁/读写锁
- 独享锁/共享锁是一种广义的说法,互斥锁/读写锁是其具体实现
- 乐观锁/悲观锁
- 乐观锁与悲观锁是看待同步的角度不同,乐观锁认为对于同一个数据的修改操作,是不会有竞争的,会尝试更新,如果失败,不断重试。
- 悲观锁与此相反,直接获取锁,之后再操作,最后释放锁。
- 分段锁
- 分段锁是一种设计思想,通过将一个整体分割成小块,在每个小块上加锁,提高并发。
- 锁膨胀过程


刚开始 Monitor 中 Owner 为 null
当 Thread-2 执行 synchronized(obj) 就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一个 Owner
在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),就会进入EntryList BLOCKED
Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争的时是非公平的
图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足进入 WAITING 状态的线程
注意:不加 synchronized 的对象不会关联监视器
