synchronized关键字锁实现原理几锁优化剖析


为了唤醒大家的记忆,介绍synchronized原理之前,先给一个大家都很熟悉的使用场景: 单例模式(double check)。

public class Singleton {
	private static volatile Singleton singleton = null;

	private Singleton() {

	}

	/**
	 * 获取实例
	 * 
	 * @return
	 */
	public static Singleton getInstance() {
		if (singleton == null) {
			synchronized (Singleton.class) {
				if (singleton == null) {
					singleton = new Singleton();
				}
			}
		}
		return singleton;
	}
}
1. 锁机制的两种特性
  • 互斥性:即在任何时刻都只有一个线程持有某个对象锁,通过这种特性来实现多线程中对共享数据的协调访问机制(协议),即在同一时刻只有一个线程访问锁内的代码,互斥性我们也往往称为操作的原子性。
  • 可见性:必须保证在锁被释放之前对共享数据所做的修改,对于随后获得该锁的线程是可见的,即在获得锁时应获得最新共享数据,否则就会出现数据不一致进而引发很多严重的问题。
2. synchronized的三种应用方式
2.1 同步实例方法

源码

public synchronized void synMethod() {
		System.out.println("======同步实例方法======");
	}

字节码
在这里插入图片描述
当前方式是对实例方法加锁,即会直接锁当前 实 例 对 象 \color{red}实例对象 。可以通过过字节码看到,此时会在方法flags中添加 ACC_SYNCHRONIZED 标识。

2.2 同步静态方法

源码

public static synchronized void synStaticMethod() {
		System.out.println("======同步静态方法======");
	}

字节码
在这里插入图片描述
当前方式是对静态方法加锁,因此锁住的是 类 对 象 \color{red}类对象 。和同步实例方法一样,也会在方法flags中添加 ACC_SYNCHRONIZED 关键标识。

2.3 同步代码块

源码

	public void synCodeBlock() {
		synchronized (this) {
			System.out.println("======同步(实例对象)代码块======");
		}

	}

	public static void synCodeBlock2() {
		synchronized (SynchronizeDemo.class) {
			System.out.println("======同步(类对象)代码块======");
		}

字节码
在这里插入图片描述
synchronized(instance|class) {},代码块是通过对实例对象或者类对象加锁实现的,锁的范围即给定的锁对象。可以从字节码看到在同步块的入口和出口分别增加 monitorenter ,monitorexit指令来加锁及释放锁。
在 Java 中,每个对象都会有一个Monitor 对象即监视器。某一线程获取某个对象锁的时候,先判断monitor 的计数器是否为0,如果是0表示没有线程占有,则该线程占有这个对象锁,并将这个对象的monitor+1;如果不为0,表示这个锁已经被其他线程占有,则该线程判断是否是当前线程占有,若是则monitor+1,否则线程等待。当线程释放释放锁的时候,monitor-1; 同一线程可以对同一对象进行多次加锁,即重入性。每一次加锁必然对应一次释放锁,指导monitor的值为0,表示锁彻底释放,则可以进入新一轮所竞争。

3. JVM的锁优化
3.1 基本概念

在Hotspot虚拟机中,对象在内存中的布局分为三块区域:对象头(Mark Word、Class Metadata Address)、实例数据和对齐填充;Java对象头是实现synchronized的锁对象的基础。一般而言,synchronized使用的锁对象是存储在Java对象头里。它是轻量级锁和偏向锁的关键。
对象头的结构如下:
在这里插入图片描述
Mark World 结构:
在这里插入图片描述
Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。

3.2 锁优化

jdk1.6以后对synchronized的锁进行了优化,引入了偏向锁、轻量级锁,锁的级别从低到高逐步升级: 无锁->偏向锁->轻量级锁->重量级锁。

  • 无锁状态:没有加锁
  • 偏向锁:当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁。如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下Mark Word中偏向锁的标识是否设置成01(表示当前是偏向锁)。如果没有设置,则使用CAS竞争锁。如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。竞争不激烈的时候适用。
  • 轻量级锁:是用于线程有交替执行的情况且互斥性不是很强,CAS失败,转态00
  • 重量级锁:重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。强互斥的时候适用,状态为10,等待时间长。
  • 自旋锁:竞争失败的时候,不是马上转化级别,而是执行几次空循环,通常是5 10 。还有一种适应性自旋,是赋予了自旋一种学习能力,它并不固定自旋10次一下。他可以根据它前面线程的自旋情况,从而调整它的自旋,甚至是不经过自旋而直接挂起。
  • 锁消除:JIT在编译的时候吧不必要的锁去掉,比如循环中调用StringBuffer的append方法。
  • 如果jvm检测到有一串零碎的操作都对同一个对象加锁,将会把锁粗化到整个操作外部,如循环体。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值