synchronized和volatile在java并发领域扮演者重要的角色。其中,synchronized一直给人的印象就是重量级锁,volatile是轻量级锁。随着Java SE 1.6之后对synchronized进行了一系列优化之后,synchronized的性能逐渐也变得不那么重了。
volatile的应用
在多线程中,volatile一直都是用来保证变量的可见性。可见性的意思是,当一个线程对volatile修饰的变量进行修改操作时,其他线程能立刻感知到这种变化,并重新加载这个变量。volatile的经典应用就是Doug lea大神在concurrent并发包中的cas+volatile变量的使用。concurrent包中大量使用cas+volatile来避免使用锁导致的频繁的上下文切换带来的消耗,以此提升高并发下的性能。
那么volatile是如何保证可见性的呢?
阅读了《Java并发编程的艺术》,作者在文中指出,volatile之所以能保证内存可见性,是因为volatile修饰的变量在进行写操作时,在汇编代码下会多出lock前缀。而lock前缀的指令在多核处理器下会引发两件事情:
- 将当前处理器缓存行的数据写回到内存中
- 写回内存的操作会使得其他CPU缓存行中的数据失效,重新从内存加载
这里解释一下,在现代处理器中,CPU的处理速度是最快的,内存次之,硬盘最慢。CPU的速度远远比内存速度要快,为了加快运行速度,通常在CPU与内存中间加缓存,通常是寄存器充当缓存,而现代处理器每个CPU通常都有一级缓存、二级缓存、三级缓存。
这里CPU在运行线程任务时,会将变量加载到CPU的高速缓存中,而多个CPU同时运行时也就会有多个缓存数据,volatile变量在其中一个执行中被写回到内存中时,由于COU实现了缓存一致性协议,就会导致其他CPU缓存行中的数据失效,从而重新加载内存中的变量数据。
synchronized的应用
synchronized在多线程环境中通常用在普通同步方法、静态同步方法以及同步代码块中。
synchronized的含义。
synchronized的不同用法有着不同的含义。
- synchronized用于普通方法:synchronized锁的是当前实例。
- synchronized用于静态方法:锁的是当前类的Class对象,Class对象存在于堆中的永久代,是共享区,因此也是全局锁。
- synchronized静态代码块:synchronized()括号中的是Class对象时,同2,括号中是对象实例时,同1。
从Java SE1.6开始,引入偏向锁、轻量级锁、重量级锁来对synchronized进行优化,提高并发下的性能。且锁只能升级,不能降级。
偏向锁
偏向锁意思是偏向第一个获得锁的线程,当获得锁的线程再次进入此同步方法时,只需要check对象头的Mark Word中是否有当前线程的线程ID即可,如果有,即可执行同步代码。
偏向锁的获得过程:
- 访问Mark Word中的偏向锁标识是否设置为1,锁标志位是否为01,确认我可偏向锁状态。
- 如果为可偏向锁状态,检查Mark Word中的线程ID是否是当前线程,是则直接执行5,不是则执行3。
- CAS替换Mark Word,成功则将Mark Word中的线程ID替换为当前线程ID,然后执行5。竞争失败,执行4。
- CAS替换失败,说明有其他线程在竞争,当到达全局安全点时,获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,被挂起的线程继续执行同步代码。
- 执行同步代码。
轻量级锁
当偏向锁竞争时,便升级为轻量级锁,其他未获得轻量级锁的线程自旋,等待锁的释放,当在自旋周期内仍未获得锁时,轻量级锁升级为重量级锁。
轻量级锁的获得过程:
- 在代码进入同步代码块时,如果同步对象锁状态为无锁状态(锁标志为01,偏向锁为0),虚拟机首先将在当前线程的栈桢中建立一个名为Lock Record的空间,用于存储锁对象目前的Mark Word的拷贝,称之为Displaced Mark Word。
- 拷贝对象头的Mark Word到锁记录。
- 拷贝成功,则CAS操作将对象的Mark Word设置为Lock Record的指针,并将Lock Reocrd的owner指针对象的Mark Word,成功则执行4,否则执行5。
- 如果这个操作成功了,那么这个线程就拥有了这个对象的锁,并且对象的锁标志为00,表示处于轻量级锁状态。
- 如果失败,虚拟机会检查这个对象的Mark Word中是否指向当前线程的栈桢,是则说明当前线程已经拥有了这个对象的锁,可以执行同步代码块。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程。
整体来说,synchronized的执行过程为:
- 检测Mark Word里面是否为当前线程ID,如果是,表示当前线程为偏向锁状态。
- 如果不是,则CAS将当前线程ID替换Mark Word,成功则表示当前线程获得偏向锁,置偏向标志1,执行同步代码。
- 如果失败,表示发生竞争,撤销偏向锁,升级为轻量级锁。
- 当前线程使用CAS将Mark Word指向当前线程的存储Mark Word栈桢的指针,成功,则获得锁。
- 失败,表示其他线程获得锁,此时自旋来获得锁。
- 自旋成功,则依然为轻量级锁。
- 自旋失败,则升级为重量级锁。