您的位置:首页 > 编程语言 > Java开发

synchronized的实现原理及JDK1.6之后的锁优化

2018-08-03 18:33 316 查看

概念:

synchronized可以保证方法或代码块在运行时,同一时刻只有一个方法进入到临界区,同时它还保证共享变量的内存可见性;

synchronized是一个重量级锁,相对Lock并不高效,显得更加笨重。

实现原理

同步代码块是使用monitorenter和monitorexit指令实现的,

同步方法依靠的是方法修饰符上的ACCSYNCHRONIZED实现。

Java对象头和monitor是实现synchronized的基础。

Java对象头:

synchronized用的锁就是存在Java头里的,

Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。其中Klass Pointer是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键 

Monitor:

我们可以把它理解为一个同步工具,也可以描述为一种同步机制,它通常被描述为一个对象。

Monitor是线程私有数据结构。

JDK1.6之后的锁优化:

JDK1.6对锁的实现引入了大量的优化,如自旋锁,适应性自旋锁,锁消除,锁粗化,偏向锁,轻量级锁等锁的技术来减少锁操作的开销。

锁主要存在四种状态:无锁状态、偏向锁状态,轻量级锁状态,重量级锁状态。他们会随着竞争激烈而不断升级;

注意锁可以升级不可以降级,这种策略是为了提高获得锁和释放锁的效率。

自旋锁:

线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来说是一件负担很重的工作,势必会给系统的并发性能带来很大的压力。同时我们发现在许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是非常不值得的,所以引入自旋锁。 

自旋锁就是让该线程等待一段时间,不会被立即挂起,看持有锁的线程是否会很快释放锁。

如何实现等待呢?

执行一段无意义的循环即可,自旋默认为10次。

自适应自旋锁:

JDK1.6之后引入了更聪明的自旋锁,自适应自旋锁。所谓自适应就意味着自旋的次数不是固定的它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态决定的。

实现原理:线程如果自旋成功,那么下次自旋的次数会更多;反正,如果对于某个锁,很少有自旋能够成功的,那么以后这个锁自旋的次数会减少甚至省略掉自旋的过程,以免浪费处理器资源。

锁消除:

为了保证数据的完整性,我们在进行操作时需要对这部分操作进行同步控制,但是在有些情况下,JVM检测到不可能存在共享数据竞争,这是JVM会对这些同步锁进行锁消除。锁消除的依据是逃逸分析的数据支持。 

有时候我们虽然没有显示使用锁,但是我们在使用一些JDK的内置API时,如StringBuffer、Vector、HashTable等,这个时候会存在隐形的加锁操作。JVM检测到变量没有逃逸方法之外的,就可以大胆的将内部加锁操作消除。

锁粗化:

锁粗化就是将多个连续的加锁、解锁操作连到一起,扩展成更多范围的锁。

JVM检测到同一个对象有连续的加锁、解锁操作,会合并成为一个更大范围的加锁、解锁操作,例如将加锁解锁操作移到for循环外面。

轻量级锁:

引入轻量级锁的作用主要是在没有多线程竞争的前提下,减少传统的重量级锁使用在操作系统中互斥产生的性能消耗。

当关闭偏向锁功能或者多个线程竞争偏向锁导致偏向锁升级为轻量级锁。

对于轻量级锁,其性能提升的依据是“对于绝大部分的锁,在整个生命周期内都是不会存在竞争的”,如果打破这个依据则除了互斥的开销外,还有额外的CAS操作,因此在有多线程竞争的情况下,轻量级锁比重量级锁更慢;

偏向锁:

引入偏向锁的主要目的是为了在无多线程竞争的情况下,尽量减少不要的轻量级锁执行路径。

偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动释放偏向锁,需要等待其他线程来竞争。

重量级锁:

重量级锁通过对象内部监听器来实现,操作系统线程之间切换需要从用户态到内核态的切换,切换成本非常高。

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: