Java关键字(部分)原理解析

(1)synchronized

同步代码块,当锁住一个object的时候,有个锁升级的过程。

1. 偏向锁:

对象头的偏向锁字段会记录占用该锁的线程ID,当下次相同的线程来的时候,就直接获锁。不用进行CAS的操作。

 

 升级过程

由于偏向锁不会主动释放锁。

a.当线程进入的时候,发现自己线程ID和字段记录的一样,直接获锁。

b.当线程进入的时候,发现自己线程ID和字段记录的不一样,查看字段记录的线程ID的线程已经死了,那就将其重置为无锁,然后线程一起争抢。

c. 当线程进入的时候,发现自己线程ID和字段记录的不一样,查看字段记录的线程ID的线程依旧存活,那么立刻查找该线程的栈帧信息,如果发现其还需要持有锁,那么暂停当前线程,撤销偏向锁,升级为轻量级锁。******

d. 当线程进入的时候,发现自己线程ID和字段记录的不一样,查看字段记录的线程ID的线程依旧存活,那么立刻查找该线程的栈帧信息,如果线程不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。 

 

2.轻量级锁

第一步:线程1获取轻量级锁时,会先把锁对象的对象头MarkWord复制一份到线程1栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录的地址

第二步:如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁

第三步:但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

 

3.重量级锁

在1.6之前,synchronized只存在重量级锁,作为一个JVM实现的锁,底层调用了Monitor Enter方法加锁,Monitor Exit方法释放锁,且实现了可重入。具体是每个锁对象会指向一个对应的monitor对象,通过monitor对象来记录重入次数以及占有锁的线程的信息。如果没有抢占到锁,底层还是调用park方法,来进行阻塞,且进入队列排队。

(2)volatile

 

1.防止指令重排序

例子1 -- 为什么需要单例双重检查机制:Test t=new Test();创建一个对象,这并不是一个原子性的操作,分为以下三步:

1). 开辟内存空间,为分配对象内存空间作准备。

2). 生成Test对象,并初始化对象。

3). 引用指针指向该对象。

==> 有些时候123的顺序,变成132,需要将引用变量用volatile修饰,以满足不会出现返回的单例对象是半成品的情况。

 

例子2 -- 为什么多线程i++,即使volatile int i修饰还是会线程不安全: i++分为以下三步:

1). 拷贝i当前值的副本

2). 副本+=1

3). 副本赋值回原值

==> volatile修饰i,能让123按照顺序执行,但是可以让两个线程先后执行完12,同时执行3,那么就导致数据不一致了。

 

2.CPU缓存一致性协议MESI

1.缓存当多个CPU持有的缓存都来自同一个主内存的拷贝,当有其他CPU偷偷改了这个主内存数据后,其他CPU并不知道,那拷贝的内存将会和主内存不一致,这就是缓存不一致。

==> 那我们如何来保证缓存一致呢?这里就需要操作系统来共同制定一个同步规则来保证,而这个规则就有MESI协议。

状态说明图:

状态

描述

M(Modified)

这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本cache中

E(Exclusive)

这行数据有效,数据和内存中的数据一致,数据只存在于本cache中。

S(Shared)

这行数据有效,数据和内存中的数据一致,数据存在于很多cache中

I(Invalid)

这行数据无效

 

状态转换图:

由于太过繁杂,简单介绍下几种场景,避免了缓存不一致的情况:

当Cache1单独获取到了数据,则是E独占,但是Cache2也读取了数据,大家状态都变成了S,但是Cache2很过分将数据修改了,则自己的状态变成了E,Cache1状态变成了I,失效了,只能再取数据。

当前状态

事件

行为

下一个状态

I(Invalid)

Local Read

如果其他Cache没有这份数据,本Cache从该内存中取数据,Cache line状态变成E;

如果其他Cache有这份数据,且状态为M,则将数据更新到内存,本Cache再从内存中取数据,两个Cache的Cache line状态都变成S;

如果其他Cache有这份数据,且状态为S或者E,本Cache从内存中取数据,这些Cache的Cache line状态都变成S。

E/S

Local Write

从内存中取数据,在Cache中修改,状态变成M;如果其他Cache有这份数据,切状态为M,则要先将数据更新到内存;

如果其他Cache有这份数据,则其他Cache的Cache line状态变成1

M

Remote Read

既然是invalid,别的核的操作与它无关

I

Remote Write

既然是invalid,别的核的操作与它无关

I

E(Exclusive)

Local Read

从Cache中取数据,状态不变

E

Local Write

修改Cache的数据,状态为M

M

Remote Read

数据和其他核共用,状态变成了S

S

Remote Write

数据被修改,本Cache line不能再使用,状态变成I

I

S(Shared)

Local Read

从Cache中取数据,状态不变

S

Local Write

修改Cache中的数据,状态变成M,其他核共享的Cache line状态变成I

M

Remote Read

状态不变

S

Remote Write

数据被修改,本Cache line不能再使用,状态变成I

I

M(Modified)

Local Read

从Cache中取数据,状态不变

M

Local Write

修改Cache中的数据,状态不变

M

Remote Read

这行数据被写到内存中,使其他核能使用到最新的数据,状态变成S

S

Remote Write

这行数据被写到内存中,使其他核能使用到最新的数据,由于其它核会修改这行数据,状态变成I

I

 

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值