并发编程基础之Synchronized

synchronized基本使用

synchronized可以作用在方法、静态方法、类、代码块上
一、修饰方法,修饰方法时锁的范围是整个方法,锁的对象是调用该方法的实例对象(不同实例对象互不影响),需要注意的是synchronized不能被子类重写的方法继承

public synchronized void method() {
	//TODO
}

二、修饰一个代码块,如果代码块包住了整个方法的代码则等价于修饰了该方法,修饰代码块时必须指定一个对象,这个对象可以是this也可以new一个对象,获取锁时是获取了这个对象的锁

public void method() {
	synchronized(this) {
		//TODO
	}
}
Object obj = new Object();

public void method() {
	synchronized(obj) {
		//TODO
	}
}

三、修饰一个静态方法,修饰在静态方法上时,锁作用的对象是这个类的对象

public synchronized static void method() {
	//TODO
}

四、修饰一个类,这种写法和修饰静态方法的效果是一样的,都是锁住了这个类的对象

class Demo {
	public void method() {
		synchronized(Demo.class) {
			//TODO
		}
	}
}

synchronized底层原理

Monitor

synchronized基于对象的Monitor锁(监视器锁)实现,每个对象都持有Monitor,线程通过Monitorenter指令进入Monitor持有锁,通过Monitroexit指令退出Monitor对象释放锁,Monitor锁底层是操作系统通过Mutex lock实现的,所以如果直接操作Monitor就会涉及到用户态和内核态的转换,非常消耗性能,因此synchronized被称为重量级锁,当然在Java1.5版本之后Oracle公司已经对synchronized做了大量优化,性能和ReentrantLock持平,反编译字节码文件后我们会发现当synchronized修饰在方法上时是使用一个标识符ACC_SYNCHRONIZED来标识当前方法是一个同步方法,而当synchronoized修饰代码块时使用了一次monitorenter指令和两次monitorexit指令,第一次是同步正常退出释放锁,第二次是发生异步退出释放锁
在这里插入图片描述

Mark Word

每个对象都持有一个Monitor,那么对象是如何记录Monitro信息的呢,答案就是Mark Word(对象头),内存中的对象由三部分组成:对象头、实例数据、对齐填充

  • 对象头:包含对象的hashcode、对象所属的年代、锁标记位、偏向锁(线程)ID、偏向时间、数组长度等,对象头一般占用2个机器码(子宽),32位虚拟机中一个机器码是4个字节(32bit),64位虚拟机中一个机器码是8个字节(64bit),如果对象是个数组则会占用3个机器码,其中一个机器码用来记录数组长度
  • 实例数据:存放类的属性数据信息,包括父类的属性信息
  • 对齐填充:虚拟机要求对象起始地址必须是8字节的整数倍,填充数据只是为了字节对齐

为了存储更多的数据,Mark Word被设计成一个非固定的结构,它会根据对象的状态复用自己的存储空间,也就是说Mark Word是可变的,变化的结构如图:
在这里插入图片描述

synchronized锁优化

synchronized锁优化后有四种状态,级别从低至高分别是:无锁状态、偏向锁、轻量级锁、重量级锁,随着锁竞争的激烈,锁的级别是逐渐上升的,叫做锁的升级,锁的升级是单向的只能升级不能降级

偏向锁

偏向锁是JDK1.6后新引入的,研究发现在大多数情况下,锁不仅不存在多线程竞争,而且总是由一个线程多次获取,因此为了减少线程每次获取锁的耗时而引入了偏向锁,偏向锁的核心思想是当线程获取锁之后,锁进入偏向模式,当线程再次获取锁时无需CAS操作,从而减少消耗提高性能,偏向锁适合单线程执行代码块的场合,在锁竞争激烈的场合则会升级为轻量级锁或重量级锁,偏向锁默认是开启的,可以通过参数-XX:-UseBiasedLocking禁止偏向锁

轻量级锁

偏向失败后,JVM先尝试升降到轻量级锁,使用轻量级锁是为了减少直接使用重量级锁引起的开销,轻量级锁提升性能的依据是"对于绝大部分的锁,在整个生命周期内是不会产生竞争的",轻量级锁适用于多个线程交替执行代码块的场合,如果线程之间的竞争更加激烈,则从轻量级锁膨胀为重量级锁

自旋锁

由于在大多数情况下线程持有锁的时间不会太长,为了避免在短时间内阻塞和唤起线程,引入了自旋的概念,当一个线程尝试获取锁时,如果该锁已经被其他线程占据,则进入空循环等待锁释放,而非进入休眠,这个过程叫做自旋,当然自旋会持续占用CPU,如果锁长时间不释放,就会白白浪费性能,因此自旋的时机或者次数需要有个上限,如果长时间获取不到锁,则升级到重量级锁

适应性自旋锁

如果某次自旋成功了,那么下次自旋的次数将会增加,反之如果自旋很少成功,则自旋次数会减少,也就是说JVM会根据上次自旋的情况调整自旋的次数

锁消除

锁消除指的是虚拟机在编译时会去除不可能存在共享资源竞争的锁,节省无意义的加锁时间,有点时候并非我们主动写的同步代码而是使用的api中具有同步方法比如stringBuff的append方法,锁消除的依据是逃逸分析的数据支持,使用JVM参数可控制是否开启逃逸分析,默认是开启的,-XX:+DoEscapeAnalysis : 开启逃逸分析 -XX:-DoEscapeAnalysis : 关闭逃逸分析

锁粗化

锁粗化也很好理解,实际在使用synchronized时,我们可能会隔一段代码使用一个synchronized同步块,这种情况下连续加锁会造成额外的损耗,JVM会将一段一段的锁合并成一个锁

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值