(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 |