synchronized锁升级

synchronized加锁,是对什么加锁

对象!

这个概念很重要,比如synchronized最常用的用法,用来修饰一个方法,那有些人会随口说出,对这个方法加锁,这个说法其实是错误的。

一个类中可能有很多方法,而我们去访问某些 非静态 方法时,往往是通过这个类的实例对象去操作的,比如:
Dog dog1 = new Dog();
Dog1.eat();

那么当我们对Dog中 eat 方法加锁时,我们锁的是什么?

this对象

此时的this是谁?其实锁住的是这个实例对象 dog1。

那么对于静态方法呢?

我们知道静态方法是随着类加载而加载的,那么静态方法总不能是锁住实例对象吧,静态方法的加载要早于实例对象。
对的。
当synchronized修饰静态方法时,我们锁住的其实是Dog类的Class对象。
当synchronized修饰代码块时候,是对括号内的对象加锁(这个很好理解了)

synchronized很重要的特性:互斥和可重入

互斥,这个很好理解,就是字面的意思,我拿到锁了,所有人都给我等着。

可重入,这个理解起来有点意思。

字面的意思有些拗口,我简单说一下:同一个线程获得锁以后可以继续获得锁。
好吧,还是有点拗口,下面再说的大白话一点。
同一线程!
这个很重要。
在这里插入图片描述

比如 Dog类中有两个synchronized同步方法 eat() 和 drink()
在eat()方法中又调用了 drink()方法(吃饭时候喝两口水,嗯,合理)
然后dog2对象在调用synchronized eat()时,获得了锁(this对象)
代码继续运行,运行到了drink()方法,这时因为是同一线程,无需等待锁的释放,可以继续运行drink方法。
这就是可重入。
那假设这里如果是不可重入锁会发生什么呢?
那这里我们假设 synchronized 不可重入。
下面的逻辑是建立在synchronized 不可重入的基础上,不要搞混。
线程在eat()方法拿到锁以后,继续运行,直到运行到drink()方法,线程要获得drink()方法的锁才可以继续运行。
而重要的一点,eat方法和drink方法是同一个对象加锁(this),线程在eat获取了this对象锁以后,还没有释放 > this对象的锁没有释放,无法获取,也就无法进入drink()方法,需要等待this对象锁释放 > eat方法没有执行完,不会释放锁,继续执行需要调用drink方法 > ……
这就像极了小品中的那段:开锁要拿出房产证 > 房产证在箱子里,开了锁给你看房产证 > 你得给我房产证我才能给你开锁 ……
如此循环,就是所谓的死锁。
所以从另一方面证明,synchronized 必须是可重入锁。

synchronized 锁升级

在刚学习编程的时候,许多老前辈曾说过:synchronized 太重了,轻易不要用。
但synchronized 真的就很重吗,重在哪里了呢?
其实不然,在JDK1.6以后,synchronized 加入了锁升级的概念。
升级过程为 无锁 > 偏向锁 > 轻量级锁 > 重量级锁(不可逆)
然后我们就要聊一下什么是偏向锁,但聊偏向锁之前,咱们必须要搞清楚一个概念,那就是java对象的对象头。
Java对象大致可以分为三个部分,分别是对象头、实例变量和填充字节。
对象头的是由MarkWord和Klass Point(类型指针)组成,其中Klass Point是是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,Mark Word用于存储对象自身的运行时数据。
实例变量存储的是对象的属性信息,包括父类的属性信息,按照4字节对齐
填充字符,因为虚拟机要求对象字节必须是8字节的整数倍,填充字符就是用于凑齐这个整数倍的

在这里插入图片描述

记住上面这张对象头MarkWord的图,我们继续往下说偏向锁。
偏向锁顾名思义,就是锁偏向于某个线程。
当线程A获得锁以后,MarkWord中便记录其线程ID,以及将偏向锁标志位改为1,示意此时为偏向锁。
当线程A再次获取锁的时候,便会比较MarkWord中记录的线程ID,比较发现一致,那么直接通过。
当需要锁的是线程B时,线程B的线程ID和MarkWord中记录的线程ID不一致(此时记录的还是线程A的ID),检查线程A是否还存活,如果不存在了,那么重置成无锁状态,线程B获得锁,重新变为偏向锁。
而当线程A还存在,那么查找线程A的栈帧信息,是否还需要持有此锁,如果不再需要那么依旧先把对象还原成无锁状态,然后线程B获得锁,变成偏向锁。
当线程A存在,并且不依旧需要持有此锁,那么就转为轻量级锁——CAS锁。

这一段打个比喻就是,小明想要上厕所,这个厕所只有一个隔间,此时就是无锁状态,小明进入隔间然后这个时候假设附近只有小明一个人,那么这个门小明可以随便进出,这就是偏向锁。然后这个时候,又来了个小刚,小刚也要上厕所,两个大男人怎么用同一个隔间呢?于是就要保证他的隐私。这时候就要进行锁的升级,转为轻量级锁——CAS

那么到底什么是CAS呢?
字面意思compare and swap(比较并替换)
CAS 操作包含三个操作数 —— 内存位置、预期原值和新值。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。
用大白话解释是什么呢 现在有一个地址XXX,存储了一个值V。
然后线程A想要将V改成A,线程B想要把V改成B。
这时候他们两个线程都想第一时间把V改成自己的值,于是他们就开始拼抢,每个人都把地址XXX和原有值V存了起来。
然后A稍微领先一步,先去做了更改,它直接找到地址XXX,然后用原有的V做了个判断,一看,哦,V没变,那我把你改成A,这就是compare and swap(比较并替换)。
但这个时候其实线程B也存了地址XXX和值V,只不过它稍微慢了一步,然后这个时候它再去改值V的时候,找到地址XXX,这时候它突然有点懵,诶?刚刚这个值不是V吗,怎么变成了A?于是修改失败,但这个时候,线程B开始自旋。自旋是什么意思,就是不断询问,XXX地址的值是不是已经又变成了V,如果自旋时通过,那么就可以继续之前的修改(默认自旋10次)
(CAS存在ABA问题,这个问题以后再讲)
再打个比喻,小明和小刚同时想上厕所,他们同时得到一条信息,在南京路的公厕那个隔间是空的,于是他们两个同时去抢那一个公厕,小明跑的快一点,他先到了公厕,发现这里只有一个隔间,然后看了看自己手机里的信息,写的是“没人”,隔间里果然没人,信息对上了,他进入隔间方便。但这个时候好巧不巧,小刚也来到了公厕,他手机里面拿到的信息也是“没人”,但是他到了一看,隔间被人占了,门上挂了牌子写着“有人”,信息不匹配,他无法进入隔间方便。但这样他就死心了吗?不,小刚开始在外面催促小明:哥们你好了吗?你好了吗?好了吗?了吗?吗?……这就是自旋。

自旋锁还是很耗费CPU的,所以到达一定次数以后,它就再次升级,由轻量级锁变成了重量级锁,线程B此时被挂起成为阻塞状态,不再占用任何的资源,由用户态转为内核态,再次想要唤醒这个线程,就要通过操作系统了,所以人们说的synchronized “重”其实说的是这个地方。

所以当有人一提synchronized 就说,“这太重了不要用”,这个真的是要就事论事的去看。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值