JAVA使用synchronized实现互斥同步线程安全以及原理
- synchronized经过编译后会在同步代码块前后形成monitorenter和monitorexit,
任何对象都有一个 Monitor 与之相关联,当且一个 Monitor 被持有之后,他将处于锁定状态。
线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 Monitor 所有权,即尝试获取对象的锁,
如果当前线程拥有那个对象的锁,把锁的计数器+1,相应的但执行monitorexit指令时会把锁的计数
器-1即释放锁!
锁优化
-
jdk1.6之后引入一项锁优化,加入偏向锁,轻量级锁
-
轻量级锁:主要是为了减少传统的重量级锁使用操作系统互斥产生的性能消耗
(因为重量级锁如果要阻塞或者唤醒一个线程,都需要操作系统介入,
需要从用户态切换为核心态,在状态切换需要消耗处理器时间)。
如果有多线程竞争同一把锁,则会膨胀为重量级锁竞争,该锁的获取与释放需要CAS操作 -
偏向锁:主要是消除数据在无竞争的情况下同步,在线程执行过程中,该锁没有被其他线程获取,则持有偏向锁的线程
将永远不需要再进行同步,减少CAS操作
-
-
轻量级锁获取流程
Mark Word:每个对象的对象头信息,该对象头分为两部分信息的。
第一部分:存储对象自身运行时的数据,如哈希码,GC分代年龄,是实现轻量级锁与偏向锁关键
第二部分:存储指向方法区对象类型数据的指针-
当线程访问同步代码块,会先检查该同步对象是否被锁定,如果没有则 JVM 首先将在当前线程的栈帧中,
建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的 Mark Word的 拷贝
(官方把这份拷贝加了一个 Displaced 前缀,即 Displaced Mark Word),否则执行 3 -
JVM 利用 CAS 操作尝试将对象的 Mark Word 更新为指向 Lock Record 的指正。如果成功,
表示竞争到锁,则将锁标志位变成 00(表示此对象处于轻量级锁状态)。否则执行 3 -
判断当前对象的 Mark Word 是否指向当前线程的栈帧?如果是,则表示当前线程已经
持有当前对象的锁,则直接执行同步代码块;否则,只能说明该锁对象已经被其他线程
抢占了,此时轻量级锁膨胀为重量级锁,锁标志状态值变为 10,后面等待的线程也要进入
阻塞状态
-
-
释放锁
-
取出在获取轻量级锁保存在 Displaced Mark Word 中 数据
-
使用 CAS 操作将取出的数据替换当前对象的 Mark Word 中。
如果成功,则说明释放锁成功;否则说明有其他线程尝试获得
该锁,要在释放锁的同时唤醒被挂起的锁
-
-
偏向锁
-
在jdk1.6之后,当锁对象第一次被线程获取时,JVM会把对象头中标志位设为01,即偏向模式
-
CAS操作把获得该的锁的线程ID记录在Mark Word中,如果成功则执行同步代码块,如果失败
则通过CAS竞争锁,成功则执行同步代码块,如果失败则将锁升级为轻量级锁.
-
-
偏向锁撤销
-
偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,需要等待其他线程来竞争
-
当有另一个线程尝试获得该锁,根据所当前的被锁定状态,撤销偏向,恢复到无锁状态01,或轻量级锁状态00
-