锁的状态

 

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁。JDK 1.6中默认是开启偏向锁和轻量级锁的,我们也可以通过-XX:-UseBiasedLocking来禁用偏向锁。锁的状态保存在对象的头文件中,以32位的JDK为例。
每个对象一开始都是无锁的,随着线程间争夺锁,越激烈,锁的级别越高,并且锁只能升级不能降级。
锁的升级过程
  1. 对象在刚创建时为无锁状态
  2. 当有一个线程使用这个对象的时候,此时升级为偏向锁,在对象头中标记为偏向锁,当下次这个线程再次访问这个对象时,直接使用;
  3. 当有两个线程竞争这个对象的时候,此时锁从偏向锁升级为轻量级锁
  4. 当有更多的线程竞争这个对象的时候,此时轻量级锁升级为重量级锁
锁的实现机制与java对象头息息相关,锁的所有信息,都记录在java的对象头中。用2字(32位JVM中1字=32bit=4baye)存储对象头,如果是数组类型使用3字存储(还需存储数组长度)。对象头中记录了hash值、GC年龄、锁的状态、线程拥有者、类元数据的指针。
 
 
Synchronized下的三种锁:偏向锁 轻量锁 重量锁
偏向锁
Java偏向锁(Biased Locking)是Java6引入的一项多线程优化。 
偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,减少加锁/解锁的一些CAS操作(比如等待队列的一些CAS操作),这种情况下,就会给线程加一个偏向锁。 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁
 
引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。
 
 
轻量级锁 
轻量级锁是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁;
轻量级锁在没有竞争时(就自已这个线程),每次重入仍然需要执行CAS操作。Java6中引入了偏向锁来做进一步优化:只有每一次使用CAS将线程ID设置到对象的MarkWord头,之后发现这个线程ID是自已的就表示没有竞争,不用重新CAS
  • 撤销偏向需要将持锁线程升级为轻量级锁,这个过程中所有线程需要STW
  • 访问对象的hashCode也会撤销偏向锁
  • 如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程T1的对象仍有机会重新偏向T2,重偏向会重置对象的ThreadID
  • 撤销偏向和生偏向都是批量进行的,以类为单为
  • 如果撤销偏向到达某个阀值,整个类的所有对象都会变为不可偏向的
  • 可以主动使用 -XX:-UseBiasedLocking 禁用偏向锁
 
 
重量级锁
重量级锁在使用的过程中会调用操作系统函数Mutex(互斥),即用户态切换到内核态,之后又切换回用户态。
Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。JDK中对Synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和“偏向锁”
 

偏向锁、轻量级锁、重量级锁之间的对比:

 

posted @ 2020-06-16 08:54  cao_xiaobo  阅读(408)  评论(0编辑  收藏  举报