乐趣区

并发编程系列之深入理解Synchronized

  synchronized 的底层是使用操作系统的 mutex lock 实现的
   内存可见性:同步块的可见性是由“如果对一个变量执行 lock 操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行 load 或 assign 操作初始化变量的值”、“对一个变量执行 unlock 操作之前,必须先把此变量同步回主内存中(执行 store 和 write 操作)”这两条规则获得的。
  操作原子性:持有同一个锁的两个同步块只能串行地进入

锁的内存语义

  • 当线程释放锁时,JMM 会把该线程对应的本地内存中的共享变量刷新到主内存中
  • 当线程获取锁时,JMM 会把线程对应的本地内存置为无效。从而使得监视器保护的临界区代码必须从主内存中读取共享变量

锁释放和锁获取的内存语义

  • 线程 A 释放一个锁,实质上是线程 A 向接下来将要获取这个锁的某个线程发出了(线程 A 对共享变量所做修改的)消息。
  • 线程 B 获取一个锁,实质上是线程 B 接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
  • 线程 A 释放锁,随后线程 B 获取这个锁,这个过程实质上是线程 A 通过主内存向线程 B 发送消息

synchronized 锁

  synchronized 锁的是对象的头。

  JVM 基于进入和退出 Monitor 对象来实现方法同步和代码块同步。代码块同步是使用 monitorenter 和 monitorexit 指令实现的,monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 是插入到方法结束处和异常处。任何对象都有一个 monitor 与之关联,当且一个 monitor 被持有后,它将处于锁定状态。

  根据虚拟机规范的要求,在执行 monitorenter 指令时,首先要去尝试获取对象的锁,如果这个对象没被锁定,或者当前线程已经有用了那个对象的锁,把锁的计数器加 1;相应地,在执行 monitorexit 指令时会将锁计数器减 1,当计数器被减到 0 时,锁就释放了。如果获取对象锁失败了,那当前线程就要阻塞等待,直到对象锁被另一个线程释放

Mutex Lock

  监视器锁(Monitor)本质是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的。每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

  互斥锁:用于保护临界区,确保同一时间只有一个线程访问数据。对共享资源的访问,先对互斥量进行加锁,如果互斥量已经上锁,调用线程会阻塞,直到互斥量被解锁。在完成了对共享资源的访问后,要对互斥量进行解锁。

synchronized 的使用场景

分类 具体分类 被锁的对象 伪代码
方法 实例方法 类的实例对象
退出移动版