Syschronized的底层实现原理以及各种锁的理解

news/2024/5/17 17:47:20 标签: synchronized

      java中每个对象都可作为锁,锁有四种级别,按照量级从轻到重分为:无锁、偏向锁、轻量级锁、重量级锁。每个对象一开始都是无锁的,随着线程间争夺锁,越激烈,锁的级别越高,并且锁只能升级不能降级。

 

java对象头

 锁的实现机制与java对象头息息相关,锁的所有信息,都记录在java的对象头中。用2字(32位JVM中1字=32bit=4baye)存储对象头,如果是数组类型使用3字存储(还需存储数组长度)。对象头中记录了hash值、GC年龄、锁的状态、线程拥有者、类元数据的指针。


 

Syschronized作用:

         Syschronized可以保证方法或者代码块在运行时同一时刻只有一个线程能进入临界区,同时保证共享变量对其他线程的可见性。


 

JDK1.6 之前,  Synchronized是一个重量级锁  :           

      Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。JDK中对Synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用。

 


JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,增加了从偏向锁轻量级锁再到重量级锁的过度。

       锁的升级过程如下:锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)

 

重量级锁、轻量级锁和偏向锁之间转换:

 

 对synchronized锁做了如下锁优化: 自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁。

 

 1、自旋锁:为了减少线程状态改变带来的消耗 不停地执行当前线程 。

 自旋锁

       频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,所谓自旋锁,就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。怎么等待呢?执行一段无意义的循环即可(自旋),如果持有锁的线程很快就释放了锁,那么自旋的效率就非常好,反之,自旋的线程就会白白消耗掉处理的资源,它不会做任何有意义的工作,典型的占着茅坑不拉屎,这样反而会带来性能上的浪费。所以说,自旋等待的时间(自旋的次数)必须要有一个限度,如果自旋超过了定义的时间仍然没有获取到锁,则应该被挂起

适应性自旋锁

      JDK 1.6引入了更加聪明的自旋锁,即自适应自旋锁。所谓自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。它怎么做呢?线程如果自旋成功了,那么下次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。 

      有了自适应自旋锁,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测会越来越准确,虚拟机会变得越来越聪明。

 

2.锁消除: 锁消除是Java虚拟机在JIT编译是,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过锁消除,可以节省毫无意义的请求锁时间。

          锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁。

例如:为了保证数据的完整性,我们在进行操作时需要对这部分操作进行同步控制,但是在有些情况下,JVM检测到不可能存在共享数据竞争,这是JVM会对这些同步锁进行锁消除(StringBuffer、Vector、HashTable的加锁操作)

 
  1. public void vectorTest(){

  2. Vector<String> vector = new Vector<String>();

  3. for(int i = 0 ; i < 10 ; i++){

  4. vector.add(i + "");

  5. }

  6.  
  7. System.out.println(vector);

  8. }

 

3.锁粗化:

  将连续的加锁 精简到只加一次锁。

 
  1. package com.paddx.test.string;

  2.  
  3. public class StringBufferTest {

  4. StringBuffer stringBuffer = new StringBuffer();

  5.  
  6. public void append(){

  7. stringBuffer.append("a");

  8. stringBuffer.append("b");

  9. stringBuffer.append("c");

  10. }

  11. }

      这里每次调用stringBuffer.append方法都需要加锁和解锁,如果虚拟机检测到有一系列连串的对同一个对象加锁和解锁操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次append方法结束后进行解锁。 

 

4.轻量级锁:无竞争条件下 通过CAS消除同步互斥(多个线程交替执行同步块,没有竞争)

       轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

1、轻量级锁的加锁过程

  (1)在代码进入同步块的时候,如果同步对象锁状态为无锁状态(锁标志位为“01”状态,是否为偏向锁为“0”),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为 Displaced Mark Word。这时候线程堆栈与对象头的状态如图2.1所示。

  (2)拷贝对象头中的Mark Word复制到锁记录中。

  (3)拷贝成功后,虚拟机将使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针,并将Lock record里的owner指针指向object mark word。如果更新成功,则执行步骤(3),否则执行步骤(4)。

  (4)如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图2.2所示。

  (5)如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。

2、轻量级锁的解锁过程:

  (1)通过CAS操作尝试把线程中复制的Displaced Mark Word对象替换当前的Mark Word。

  (2)如果替换成功,整个同步过程就完成了。

  (3)如果替换失败,说明有其他线程尝试过获取该锁(此时锁已膨胀),那就要在释放锁的同时,唤醒被挂起的线程。

 

5.偏向锁:无竞争条件下 消除整个同步互斥,连CAS都不操作。(只有一个线程执行同步)

    引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

1、偏向锁获取过程:

  (1)访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01——确认为可偏向状态。

  (2)如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)。

  (3)如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)。

  (4)如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。

  (5)执行同步代码。

2、偏向锁的释放:

  偏向锁的撤销在上述第四步骤中有提到偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

 

重量级锁

 重量级锁的加锁、解锁过程和轻量级锁差不多,区别是:竞争失败后,线程阻塞,释放锁后,唤醒阻塞的线程,不使用自旋锁,不会那么消耗CPU,所以重量级锁适合用在同步块执行时间长的情况下。

 

总结 

  本文重点介绍了JDk中采用轻量级锁和偏向锁等对Synchronized的优化,但是这两种锁也不是完全没缺点的,比如竞争比较激烈的时候,不但无法提升效率,反而会降低效率,因为多了一个锁升级的过程,这个时候就需要通过-XX:-UseBiasedLocking来禁用偏向锁。下面是这几种锁的对比:

优点

缺点

适用场景

偏向锁

加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。

如果线程间存在锁竞争,会带来额外的锁撤销的消耗。

适用于只有一个线程访问同步块场景。

轻量级锁

竞争的线程不会阻塞,提高了程序的响应速度。

如果始终得不到锁竞争的线程使用自旋会消耗CPU。

追求响应时间。

同步块执行速度非常快。

重量级锁

线程竞争不使用自旋,不会消耗CPU。

线程阻塞,响应时间缓慢。

追求吞吐量。

同步块执行速度较长。

 

 

转自:http://www.pianshen.com/article/13827882/


http://www.niftyadmin.cn/n/1239348.html

相关文章

springCloud统一配置(springcloud bus + webhooks)

前言 微服务要实现集中管理微服务配置、不同环境不同配置、运行期间也可动态调整、配置修改后可以自动更新的需求&#xff0c;Spring Cloud Config同时满足了以上要求。Spring Cloud Config 分为Config Server和Config Client两部分&#xff0c;是一个可以横向扩展&#xff0c…

IO多路复用和线程池哪个效率更高,更有优势?

多路复用的优势在于&#xff0c;当处理的消耗对比IO几乎可以忽略不计时&#xff0c;可以处理大量的并发IO&#xff0c;而不用消耗太多CPU/内存。这就像是一个工作很高效的人&#xff0c;手上一个todo list&#xff0c;他高效的依次处理每个任务。这比每个任务单独安排一个人要节…

线程的优先级与yield()、sleep()方法之间的关系

线程优先级从0-10&#xff0c;一般常用三个值表示0&#xff08;最低&#xff09; ,5&#xff08;默认&#xff09;,10&#xff08;最高&#xff09;。 线程的优先级并不是表示优先级高的线程会先执行&#xff0c;低的后执行&#xff0c;而是表示CPU切换到该线程的几率会增加。…

二重积分转换成极坐标_二重积分转换极坐标r的范围如何确定?

展开全部在直角坐标系中过原点作此区域函数图e5a48de588b662616964757a686964616f31333431353861像的两条切线&#xff0c;则两条切线的角度则为极坐标系中θ的范围。然后&#xff0c;在直角坐标系下不是已经已知一个关于x&#xff0c;y的函数关系来表示范围。将其中的xy换成r&…

dubbo笔记(实习期间做做学习笔记)

注&#xff1a;本篇笔记比较杂乱&#xff0c;学到哪写到哪&#xff0c;没有顺序可言哦 Dubbo里面有哪几种节点角色&#xff1f; 1、provide&#xff1a;暴露服务的服务提供方 2、consumer&#xff1a;调用远程服务的服务消费方 3、registry:服务注册于发现的注册中心 4、moni…

python中倒背如流_八字基础知识--倒背如流篇

地支(有十二位)&#xff1a;子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥。地支阴阳&#xff1a;地支阴阳区分有两种&#xff1a;第一种&#xff1a;阳支&#xff1a;子、寅、辰、午、申、戌、阴支&#xff1a;丑、卯、巳、未、酉、亥第二种&#xff1a;(本门采纳)阳支&…

传入json对象_Go语言进阶之路(六):内置JSON库和开源库gjson

Go语言内置了部分JSON函数&#xff0c;可以方便地在Go语言结构体实例和JSON字符串之间互相转换。这可比Java强多了。不过Go语言内置的json库功能比较鸡肋&#xff0c;只能在结构体和JSON之间相互转换&#xff0c;没办法满足在JSON字符串中进行条件匹配和搜索的功能。本文先介绍…