深入浅出Synchronized

来源:互联网 发布:java xmn 编辑:程序博客网 时间:2024/06/08 06:11

synchronized在jdk早期版本的时候就已经出现了,那时作为重量级锁,由于效率低下而使用较少,

最近版本(1.6)对synchronized的功能和性能有了大幅的改进和提高。


synchronized锁形式:

1、对于普通同步方法-->锁是当前实例对象。

2、对于静态同步方法-->锁是当前类的Class对象。

3、对于同步代码块-->锁是代码块内的对象。


对于synchronized,要知道,它是在JVM层面上实现的。而JVM基于进入和退出Monitor对象来实现方法、代码块的同步。

方法、代码块的同步可以使用以下两个方法:①monitorenter ②monitorexit

根据方法名,可以猜测到:monitorenter是在编译后插入到同步代码块的开始位置,对应monitorexit插入到方法结束处。

它们必须一一对应。

一个对象对应一个monitor(监视器),线程执行到monitorenter指令时,将会尝试(注意是尝试)获取该对象对应monitor的所有权(即锁)。


Java对象头

synchronized用的锁存在java对象头里。

其中,数组类型-->3个字宽,非数组类型-->2个字宽来存储对象头。(注:32位虚拟机中,1字宽=4bytes=32bits)

而对象头组成为:

1、Mark Word(32bits)-->存储对象的hashCode(25bits)、分代年龄(4bits)、锁标记位(2bits)、是否是偏向锁(1bit)、锁状态(无锁状态、轻量级锁、重量级锁、偏向锁、GC标记);

2、类元数据地址的指针-->英文:Class Metadata Address;

3、数组长度(如果是数组的话)。

注:其中,Mark Word的存储结构会随着锁标志位的不同而不同。在64位虚拟机上,Mark Word是64bit的。


偏向锁:

JDK1.6引入,目的是消除数据在无竞争状态下的同步原语。换句话说,就是保证可重入时不重复加锁。

我们知道,当一个同步方法重复调用时,之前的策略是使用一个变量来记录重复锁的次数,进+出-,但这样做十分低效,

其实,在无竞争状态下,完全可以做到把同步去掉,按照这个思路,我们只要判断一个要执行该方法的线程是否已经获得该锁,

如果获得,则可以直接进入,如果未获得,则按照正常加锁操作。

“偏”字形象的说明锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将

不需再进行同步。


轻量级锁:

JDK1.6引入,目的是在没有多线程竞争的环境下,减少传统的重量级锁带来的性能消耗。轻量级锁并不能代替重量级锁。

它提升程序同步性能的依据是,“绝大部分的锁,在整个同步期间内都是不存在竞争的”,这是一个经验数据。

如果没有竞争,轻量级锁使用CAS操作来获得锁的过程避免了使用互斥量的开销。

但如果存在锁竞争,除了互斥量的开销,还额外产生了CAS的开销,因此在有竞争的情况下,轻量级锁比传统的重量级锁更慢。

如果有两个以上的线程竞争同一个锁,轻量级锁就会膨胀为重量级锁。


分析得出,每种锁不分优劣,都有它适合的场景:

在多线程竞争激烈的环境,轻量级锁反而不如直接加重量级锁;

在线程不会频繁重入锁时,偏向锁的性能也会有所降低。

因此,我们可以根据实际业务的具体情况,通过设置JVM参数来选择关闭、开启偏向锁和轻量级锁来达到性能的最优。

例如:

关闭偏向锁:-XX:-UseBiasedLocking=false.