JAVA并发编程的艺术(2)synchronized和volatile底层实现原理

in java并发 with 0 comment

在最初学习多线程的时候,只要遇到同步问题我就会使用synchronized关键字,在解决问题时感觉非常好用。后来到了web阶段,当用户访问过多的时候,也会出现多线程问题,这个时候加上一个synchronized,简直酸爽,再后来不断学习的过程中,慢慢也了解到synchronized是一个重量级锁,但是一直不懂,最近学习《java并发编程艺术》就好好了解下synchronized和volatile的底层实现。

Java对象头和monitor

先看Java对象头和monitor把,因为Java对象头和monitor是实现synchronized的基础

java对象头

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

Mark Word

Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。Java对象头一般占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit),但是如果对象是数组类型,则需要三个机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度。 image.png 对象头信息是与对象自身定义的数据无关的额外存储成本,但是考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据,它会根据对象的状态复用自己的存储空间,也就是说,Mark Word会随着程序的运行发生变化 image.png 以上都是32位虚拟机上的

Monitor

什么是Monitor?我们可以把它理解为一个同步工具,也可以描述为一种同步机制,它通常被描述为一个对象。 与一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。 Monitor 是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。 image.png

synchronized

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

java中每一个对象都可以作为锁,这也是synchronized实现同步的基础,具体表现为以下三种形式

在JVM规范中可以看到synchronized在JVM里的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步 同步代码块是使用monitorenter和monitorexit指令实现的; 同步方法(在这看不出来需要看JVM底层实现)依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。

synchronized优化

synchronized是重量级锁,效率不高,不过在jdk 1.6中对synchronize的实现进行了各种优化,使得它显得不是那么重了

自旋锁

自旋锁在JDK 1.4.2中引入,默认关闭,但是可以使用-XX:+UseSpinning开开启,在JDK1.6中默认开启。同时自旋的默认次数为10次,可以通过参数-XX:PreBlockSpin来调整

适应自旋锁

如果通过参数-XX:preBlockSpin来调整自旋锁的自旋次数,会带来诸多不便。假如我将参数调整为10,但是系统很多线程都是等你刚刚退出的时候就释放了锁(假如你多自旋一两次就可以获取锁),JDK 1.6引入了更加聪明的自旋锁,即自适应自旋锁。所谓自适应就意味着自旋的次数不再是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。线程如果自旋成功了,那么下次自旋的次数会更加多,因为虚拟机认为既然上次成功了,那么此次自旋也很有可能会再次成功,那么它就会允许自旋等待持续的次数更多。反之,如果对于某个锁,很少有自旋能够成功的,那么在以后要或者这个锁的时候自旋的次数会减少甚至省略掉自旋过程,以免浪费处理器资源。

volatile

volatile是轻量级的synchronized,如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文切换和调度;它在多处理器开发中保证了共享变量的“可见性”,通俗点讲就是说一个变量如果用volatile修饰了,则Java可以确保所有线程看到这个变量的值是一致的,如果某个线程对volatile修饰的共享变量进行更新,那么其他线程可以立马看到这个更新,这就是所谓的线程可见性。

valatile原理

valatile的原理它与Java的内存模型有关。这里暂时先不讲,volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。所以,valatile原理在JMM的时候在解释。