博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
synchronized实现方式及锁优化
阅读量:4149 次
发布时间:2019-05-25

本文共 2773 字,大约阅读时间需要 9 分钟。

    简述:关于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等。

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

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

偏向锁

    引入目的:在没有多线程的竞争的情况下,减少不避免的轻量锁执行路径,轻量锁的获取释放依赖于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并发编程艺术》方腾飞

你可能感兴趣的文章
使用指针有哪些好处? C++
查看>>
引用还是指针?
查看>>
checkio-non unique elements
查看>>
checkio-medium
查看>>
checkio-house password
查看>>
checkio-moore neighbourhood
查看>>
checkio-the most wanted letter
查看>>
Redis可视化工具
查看>>
大牛手把手带你!2021新一波程序员跳槽季,全套教学资料
查看>>
Android高级工程师进阶学习,分享PDF高清版
查看>>
Context都没弄明白凭什么拿高薪?年薪50W
查看>>
看完吊打面试官!大厂经典高频面试题体系化集合,最强技术实现
查看>>
看完直接怼项目经理!应聘高级Android工程师历程感言,薪资翻倍
查看>>
看完老板就给加薪了!30岁以后搞Android已经没有前途?工作感悟
查看>>
绝了!这么香的技术还不快点学起来,大牛最佳总结
查看>>
网络优化软件apk,金九银十怎么从中小企业挤进一线大厂?我先收藏为敬
查看>>
美团安卓面试,这些年我所经历的所有面试,完整版开放下载
查看>>
美团安卓面试,阿里巴巴Android面试都问些什么?含小米、腾讯、阿里
查看>>
腾讯T2亲自讲解!阿里面试100%会问到的JVM,源码+原理+手写框架
查看>>
腾讯T3亲自讲解!字节大牛耗时八个月又一力作,大牛最佳总结
查看>>