java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java synchronized

Java关键字synchronized原理与锁的状态详解

作者:凌波漫步&

在Java当中synchronized关键字通常是用来标记一个方法或者代码块。本文将通过示例为大家详细介绍一下Synchronized的各种使用方法,需要的可以参考一下

一、Java中锁的概念

二、同步关键字synchronized特性

特性:可重入、独享、悲观锁。

锁相关的优化:

1、锁消除示例

/**
 * 锁消除示例,JIT即时编译,进行了锁消除
 * @author 刘亚楼
 * @date 2020/1/16
 */
public class LockEliminationExample {
	/**
	 * StringBuilder线程不安全,StringBuffer用了synchronized关键字,是线程安全的
	 * 针对下面这种单线程加锁、解锁操作,JIT会进行优化,进行锁消除
	 */
	public static void eliminateLock() {
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append("a");
		stringBuffer.append("b");
		stringBuffer.append("c");
		stringBuffer.append("a");
		stringBuffer.append("b");
		stringBuffer.append("c");
		stringBuffer.append("a");
		stringBuffer.append("b");
		stringBuffer.append("c");
	}
}

2、锁粗化示例

/**
 * 锁粗化示例
 * @author 刘亚楼
 * @date 2020/1/16
 */
public class LockCoarseningExample {
	/**
	 * 针对下面这种无意义的加锁操作,JIT会进行优化,对变量i的所有操作放到一个同步代码块里
	 */
	public static void lockCoarsening() {
		int i = 0;
		synchronized (LockCoarseningExample.class) {
			i++;
		}
		synchronized (LockCoarseningExample.class) {
			i--;
		}
		synchronized (LockCoarseningExample.class) {
			i++;
		}
		synchronized (LockCoarseningExample.class) {
			i++;
			i--;
			i++;
		}
	}
}

备注:锁消除和锁粗化的区别在于锁消除是针对单个线程重复加解锁做的优化,最终没有锁的存在。而锁粗化不只是针对单线程,且最终还是有锁的存在。

三、synchronized关键字原理

1、关于Mark Word

首先,对象在堆中由对象头、实例数据和对齐填充组成。

对象头包含两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向锁id等,这部分数据官方称为"Mark Word"。

对象头的另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

synchronized实现的锁是通过改变对象头的"Mark Word"来实现的。

"Mard Word"在32位和64位的虚拟机(未开启压缩指针)中分别为32位和64位。32位虚拟机"Mark Word"如下:

2、锁的状态变化

(1) 无锁 → 轻量级锁

无锁变成轻量级锁时,多个线程会读取对象的对象头的无锁状态mark word内容,然后进行cas操作进行修改,预期值是无锁状态mark word内容,新值是轻量级锁状态mark word内容,若修改成功,Lock record address指向成功获取锁的线程的Lock Record

演示流程如下:

(2) 轻量级锁 → 重量级锁

由于未成功获取锁的线程会自旋,长时间自旋会消耗CPU资源,因此自旋到一定次数会进行锁升级,由轻量级锁转变为重量级锁。

重量级锁是通过object monitor(对象监视器)实现的,对象监视器包括entryList(锁池)、owner(持锁者)、waitSet(等待集合)等。

升级为重量级锁时对象头mark word的内容是monitor address(对象监视器地址),指向对象监视器。

演示流程如下:

备注:抢锁失败线程会进入entryList(锁池),在调用wait方法后,线程会进入waitSet(等待集合),waitSet中的线程被唤醒后会重新进入entryList。

(3) 关于偏向锁

加锁之后不解锁,针对单线程

所谓偏向就是偏心,单线程加锁之后就不再解锁,减少了加锁→业务处理→释放锁→加锁操作流程。

在JDK6以后,默认已经开启了偏向锁这个优化,通过JVM参数-XX:-UseBiasedLocking来禁用偏向锁,若偏向锁开启,只有一个线程抢锁,可获取到偏向锁。

关于偏向锁Mark Word内容如下:

偏向标记第一次有用,出现过争用后就没用了。

偏向锁本质就是无锁,如果没有发生过任何多线程争抢锁的情况,JVM认为就是单线程,无需做同步。

备注:JVM为了少干活,同步在JVM底层是有很多操作来实现的,如果没有争用,就不需要去做同步操作。

(4) 完整的锁升级过程

如果未开启偏向锁,无锁状态会先升级为轻量级锁,轻量级锁自选到一定程度升级为重量级锁。

如果开启了偏向锁,有两种情况:

到此这篇关于Java关键字synchronized原理与锁的状态详解的文章就介绍到这了,更多相关Java synchronized内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文