Java并发学习 4 : Synchronized实现原理

Synchronized实现原理

Java对象头

对象是存放在堆内存之中的,大致可以分为对象图,实例变量,填充字节。

其中对象头中有一个叫做MarkWord的区域是用来存储包含锁相关的信息:是否有锁,锁的类型,偏向锁偏向的线程的ID,锁的状态.

Java并发学习 4 : Synchronized实现原理_第1张图片

这是64位虚拟机中的模型(32位主要结构与其相同,位数有所不同)

5.2 几种锁的区别

​ JDK1.6之前,synchronized只有一种锁,就是当时的重量级锁,而JDK1.6之后给synchronized增加了几个新的锁,比如:偏向锁,轻量级锁,之后才是–>重量级锁。有效减少了申请锁和释放锁嗲来的性能消耗。

引入原因:JVM认为:大部分时候是不存在锁竞争的,常常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。

Tip : 比如一个for循环内调用方法,大多数情况是不需要竞争的。

偏向锁就好像一把宝刀,任何人想使用它,需要先问他:宝刀呀宝刀,你有主人吗?

宝刀说没有的话,则开始认主。

如果宝刀说有,那么就需要查询他主人是否还活着?死了的话就可以重新认主。

还活着的话,那么其他人就不能再要求使用它,并且宝刀进入自卫模式(轻量级锁),防止争夺。

偏向锁:当线程1 访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为 偏向锁不会主动释放锁,因此之后线程1再次获取锁的时候,需要 比对当前线程的threadID和Java对象头中的threadID是否一致,如果相同,说明还是线程1获取锁对象,则直接放行。

​ 如果不一致,说明是非线程1想要得到资源,那么需要询问对象头中的线程1是否还活着,如果依然活着,那么立刻查找线程1的栈帧信息,如果线程1说:”我还要这个资源,“那么暂停当前线程1,将偏向锁升级位轻量级锁(先撤销偏向锁,增加轻量级锁).

​ 如果线程1已经死了,活着线程1不再需要这个资源的话,将对象锁状态设为无锁状态,重新偏向新的线程。

[外链图片转存失败(img-lRe0mi6V-1564649471748)(D:\我的坚果云\Pictures\偏向锁升级过程.jpg)]

轻量级锁

​ 引入原因:轻量级锁考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。

还是刚才那把宝刀,宝刀认为可能刚才那个人是无意的,所以暂时还不准备砍伤他。

如果宝刀过一会发现自己主人死了/把自己抛弃了,那么换一个耐心等待它的主人。

但是如果争夺的人过多,宝刀就会觉得:“好烦呀!我又不是那么随便的刀,我主人活得好好的,你们来找事”

“把你们全部送进医院里!!!”,然后进入重量级锁状态,谁碰刀,谁就回家疗养。

​ 实现:通过CPU空转实现自旋,比如让当前进程进入一个while循环,跳出条件是没有线程争夺这个资源。

一个类似CPU空转的例子:

public final int incrementAndGet() {
    	// 不管怎样,该线程一定要进入到一个无限循环中
        for (;;) {	// 不断的尝试。
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))	// 直到目标资源是期待的样子
                return next;		// 结束这该死的循环
        } // 如果不符合,空转CPU。 
}

​ CAS的原理如上,如果资源是期待的模样,那么for循环是隐藏的,对性能没有任何影响。少数线程竞争资源时,空转CPU造成的资源浪费远小于从用户态转到内核态的代价。

就像堵车,你不愿意看到红灯就熄火,只有堵死的时候,才会熄火。

​ 那么什么时候将红灯变成堵车呢? 轻量级锁变为重量级锁呢?

A : 当等待线程自旋时间过后依然没有得到锁,并且又来了一个线程过来竞争这个锁对象,那么此时这个轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

注意:为了避免无用的自旋,轻量级锁一旦膨胀为重量级锁就不会再降级为轻量级锁了;偏向锁升级为轻量级锁也不能再降级为偏向锁。一句话就是锁可以升级不可以降级,但是偏向锁状态可以被重置为无锁状态。

重量级锁:

​ 没什么好说的,就是标准的,谁想抢资源谁就睡,睡到直到轮到它为止。

锁状态 优点 缺点 适用场景
偏向锁 加锁解锁无需额外的消耗, 和非同步方法时间相差纳秒级别 如果竞争线程多,那么会带来额外的锁撤销的消耗 基本没有线程竞争锁的同步场景
轻量级锁 竞争的线程不会阻塞,使用自旋,提高程序响应速度。 如果始终不能获取锁,长时间的自旋会造成CPU消耗 适用于少量线程竞争锁对象,且线程持有锁的时间不长,追求相应速度的场景
重量级锁 线程竞争不适用CPU自旋,不会导致CPU空转消耗CPU资源 线程阻塞,相应时间长 很多线程竞争锁,且锁持有的时间长,追求吞吐量的场景。

你可能感兴趣的:(并发编程学习)