功能区别
-
在未优化前synchronized是属于重量级锁,系统切换用户态与核心态来实现阻塞与唤醒
-
Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,
为了避免忘记手工释放锁造成死锁,需要lock()和unlock()方法配合try/finally语句块来完成
JAVA使用synchronized实现互斥同步线程安全以及原理
- synchronized经过编译后会在同步代码块前后形成monitorenter和monitorexit,
任何对象都有一个 Monitor 与之相关联,当且一个 Monitor 被持有之后,他将处于锁定状态。
线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 Monitor 所有权,即尝试获取对象的锁,
如果当前线程拥有那个对象的锁,把锁的计数器+1,相应的但执行monitorexit指令时会把锁的计数
器-1即释放锁!
synchronized锁优化
-
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
-
ReentrantLock
-
ReentrantLock是java.util.concurrent包下提供的一套可重入互斥锁
-
相对于synchronized提供以下功能操作
-
等待可中断,
持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。
通过lock.lockInterruptibly()来实现这个机制。 -
多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁
-
ReenTrantLock提供了一个Condition类,用来实现分组唤醒需要唤醒的线程们。
-
-
ReenTrantLock实现的原理:
ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁,来避免了使线程进入内核态的阻塞状态。