synchronized实现方式及锁优化

    简述:关于synchronized的具体使用方式,网上已经给出了很多文章,各种不同,主要还是对于锁的对象不同导致,说白了,就是锁一个对象的实例,还是锁一个类,这两个是synchronized在使用时,尤其要关注的。这篇日志主要是记录synchronized其底层实现,以及锁的优化。

    synchronized在修饰代码块时,我们编译出来看到就是monitorenter和monitorexist, 这两个字段我们可以通俗理解下,其实就是:

    当执行monitorenter时:

    1、如果monitor的进入数为0,当前线程进入monitor,并且将线程数设置成1;

    2、如果当前线程已经获取到了锁,只是重新进入,会把当前线程数加1;

    3、如果其他线程已经占用了monitor,当前线程只能进入阻塞,直到monitor的线程数为0,尝试再次获取锁。

    当执行monitorexist时:

    1、判断当前线程是否是对象中拥有锁的线程,如果时,将当前对象的线程数减 1, 知道线程数降低为 0,释放该锁

    2、其他被改monitor阻塞的线程,尝试去获取当前monitor;

    对于同步方法:

    编译后,只是在方法签名处多了一个ACC_SYNCHRONIZED 标识符,jvm是根据这个标识符,进行方法的同步:当方法调用时,调用指令会判断ACC_SYNCHRONIZED访问标识是否被设置了,如果设置了,执行线程会先去获取monitor,获取成功之后执行方法体,执行结束之后释放monitor。在方法执行期间,任何线程都不能再获取monitor对象。

    

任意线程对object的访问都需要获取到object的监视器,如果获取失败,线程进入同步队列,变为blocked状态,当object的监视器被其他线程所释放之后,阻塞队列中的线程会重新尝试获取监视器,

    synchronized所提供的同步方法,还有同步代码块,限制了某个时间内只有一个线程可以进行访问,那么我们可以推到出,在synchronized块和synchronized修饰的方式中,对变量的修改是可以写会到主内存的,happens-before在synchronized中也是失效的,代码会按照正常的顺序执行。synchronized所用到的锁是悲观锁,并且是重量级锁。在实际开发中这个锁的性能并不高。

java在1.6之后对锁进行了优化,我简单记录下常见的几种锁。

准备知识:java对象头

java对象头有两部分组成Mark Word和类型指针

mark word:用来存储对象运行时的数据,比如hashcode,gc 分代年龄,锁状态标志,线程持有的锁,偏向线程id等。

类型指针:指向对象的类元数据,简单理解就是该对象是哪个类的实例。

这篇是关于对象头的详细内容 JAVA对象头信息

偏向锁

    引入目的:在没有多线程的竞争的情况下,减少不避免的轻量锁执行路径,轻量锁的获取释放依赖于CAS操作,而偏向锁只需要在置换threadID时,依赖一次CAS原子指令,一旦出现多线程竞争时必须撤销偏向锁,所以撤销偏向锁的性能必须小于之前节省下来的CAS原子操作的性能消耗,不然就得不偿失了,所以偏向锁是在只有一个线程执行同步块时提升性能。

    偏向锁的获取过程:

    1、获取到对象中的mark word ,判断是否是可偏向状态,(是否偏向锁设置成1,锁标志位设置成01)

    2、如果是偏向锁,判断javaThread中线程是否为空,如果为空,执行步骤3,如果指向当前线程,执行同步代码块,如果不为你当前线程执行步骤4

    3、通过CAS指令将当前对象的javaThread设置成当前线程id,如果执行成功,执行同步代码块;否则进入步骤4

    4、如果执行CAS指令失败,表示当前多个线程在竞争锁,到达全局安全点,获得偏向锁的线程被挂起,撤销偏向锁,并升级成轻量级锁,升级完成后,被阻塞在安全点的线程继续执行同步代码块。

    偏向锁的释放过程:

    1、只有当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。线程不会主动去释放偏向锁、

    2、首先会暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态。

    3、撤销偏向锁之后,恢复到未锁定或者是轻量级锁状态。

轻量级锁

    引入轻量级锁的目的:在多线程交替执行的条件下,避免重量级锁引起的性能消耗,但是如果在某个时刻,多个线程进入临界区,会导致轻量级锁膨胀成重量级锁。

    轻量级锁加锁过程:

    1、代码进入同步块时,如果当前对象锁的状态是无锁,虚拟机会在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头当中的mark word复制到锁记录(Lock record)中,即(displaced mark word)

    2、拷贝当前对象的mark word到锁记录(lock record)中

    3、线程尝试使用CAS将对象头中的mark word替换为指向锁记录中的指针,如果执行成功进入步骤4

    4、如果这个更新动作成功了,那么线程就拥有了该对象的锁,将对象锁标志设置成00,表示对象处于轻量级锁状态

    5、如果这个更新动作失败了,会首先检查对象的mark word是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了锁,可以直接进入同步块执行,否则说明有多个线程在竞争锁,轻量级锁就会膨胀成重量级锁,锁状态变成10,mark word中就是指向重量级锁的指针,后面等待锁的线程就会阻塞,当前线程采用自选的方式来获取锁。

轻量级锁的释放过程:

1、使用CAS操作将dispalcaed mark word替换回到对象头

2、如果替换成功,表示竞争没有发生

3、如果替换失败,表示当前锁存在竞争,锁就会膨胀成重量锁。就要在释放锁的同时,唤醒被挂起的线程。

重量级锁:

    1、从轻量级锁到重量级锁的过程中,是通过自旋的方式来获取;

    2、判断当前对象monitor是否为重量级锁,如果是重量级锁,执行步骤3,否则执行步骤4

    3、获取对象monitor指针,并返回,结束膨胀过程

    4、如果当前锁处在膨胀过程中,说明其他线程也在执行膨胀操作,当前线程就自旋的方式,等待锁膨胀完成,如果其他线程膨胀完成,退出自旋操作。

关于各个锁的优缺点对比:

优点缺点适应场景
偏向锁加锁和不加锁不需要额外的开销,和执行非同步方法比仅存在纳秒级的差距如果线程间存在锁竞争,会带来额外的锁撤销的消耗适用于只有一个线程访问同步块的场景
轻量级锁竞争的线程不会阻塞,提高了程序的响应速度如果始终得不到竞争的线程,使用自旋会消耗CPU追求响应时间,同步块执行速度非常快
重量级锁线程竞争不使用自旋,不会消耗CPU线程阻塞,响应时间慢追求吞吐量,同步块执行速度较长

 

    如果错误,敬请指正~

参考:Java并发编程:Synchronized及其实现原理

         JVM源码分析之java对象头实现

         JVM源码分析之synchronized实现

         《java并发编程艺术》方腾飞

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值