【深入学习并发之三】synchronized关键字详解

若阅读过程中出现疑问,可先阅读并发学习总览

synchronized关键字

一、synchronized满足三种特性的方式

syschronized通过对共享对象加锁的方式实现并发下的同步。

原子性和有序性十分好理解:
加锁了之后其他线程自然不可访问,自然存在原子性;
线程访问共享对象时,一定是按顺序获取锁的,于是可以保证有序性。

可见性:获取锁时,会将工作线程内的共享变量清空,从主存重新读取;释放锁时,会将工作内存中的共享变量写入主存中;


二、synchronized怎么使用

通过对修饰对象的分类,synchronized大致可分为四种使用方法:

  • 修饰代码块
synchronized(this){
	//do something
}

如上代码块,this代表当前对象,实际使用的时候括号内只要是对象即可。这里的语义是:对当前对象加锁,该实例对象内的,被synchronized修饰的实例字段和方法在加锁期间只能被此代码块访问。

  • 修饰实例方法
public synchronized void method(){
	//do something
}

修饰实例方法和修饰代码块是相同的,都是对实例对象加锁。修饰实例方法时,加锁的对象是拥有此方法的实例对象。该实例对象内的,被synchronized修饰的实例字段和方法在加锁期间只能被此方法访问。

  • 修饰某类
synchronized(A.class){
	//do something
}

如上代码片,加锁对象是类A对应的Class对象。若对类对象加锁,其语义仅是将静态同步方法和字段进行加锁,而各个类A的实例对象内的同步方法则无法加锁

  • 修饰类方法
public synchronized static void method(){
	//do something
}

同修饰类对象时的语义,是对此类的Class对象加锁,也只对静态方法生效


三、synchronized加的是什么锁(锁优化)

我们已经知道了synchronized是通过对对象加锁,来满足线程安全所需求的三种性质的。
那么synchronized加的是什么锁呢?是通过阻塞的方式还是非阻塞的方式实现的同步呢?

synchronized字节码层语义

synchronized在字节码中的语义是,将修饰的代码块前后分别加上monitorenter、 monitorend字节码,其操作数是一个reference对象,即java中的对象。

java中每个对象都对应一个monitor锁,monitorenter是获取monitor的执行权,即获取锁;monitorend是释放monitor的执行权,是释放锁。

对象锁的分类

下图是对象头的Mark Word,它表示了对象当前的加锁状态。
对象可以被加三种锁:偏向锁,轻量级锁,重量级锁。

无锁状态:在对象没被加锁时,存放对象的hashcode、与GC有关的分代年龄信息;

偏向锁和轻量级锁是由JVM根据Mark Word(即下图)来完成同步的没有用到对象内部的monitor

重量级锁是通过对象内部的monitor锁,依赖于操作系统内部的mutex(互斥量)进行同步的,很消耗资源,这也是“重量级锁”名称的来源。

在这里插入图片描述

偏向锁

偏向锁是最轻量级的锁,单纯是为了在无竞争的情况下减少不必要的轻量级锁执行路径。所以其加锁和释放锁的流程很简单。需要注意的是,偏向锁一旦获取就不会被主动释放,需要其他线程去竞争

加锁:
1.先检查是否为可偏状态,此时有两种可能(无线程占有此对象/有线程在其上加了偏向锁),若可偏,则执行2,否则结束;
2.检查线程ID字段是否指向当前线程的ID,如果是,则直接执行线程逻辑;
3.若对象的线程ID字段未指向当前线程,则通过循环CAS进行竞争锁此时有可能占有共享对象的线程已经执行完了,只是没有释放锁;也有可能还在执行)。
4.若成功,则释放偏向锁(即置monitor为无锁状态),并由后面竞争的线程执行线程逻辑;若失败,证明当前存在多线程竞争情况,当到达全局安全点,获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后之前拥有偏向锁线程继续往下执行同步代码块;

释放锁:monitor恢复到无锁状态/轻量级锁状态
在这里插入图片描述

轻量级锁

轻量级锁用到了自旋锁的思想,即后来的竞争线程,若发现共享对象有轻量级锁,则会先自旋CAS获取锁(有某个时间限制);若自旋超时失败,则会让锁膨胀为重量级锁,并使竞争线程及其后面竞争线程,阻塞挂起。同时在当前线程释放锁时,唤醒其后的竞争线程。

加锁:
1.在当前线程中分配一个“锁空间”,用于存储共享对象本来的Mark Word内容。
2.将共享对象的Mark Word内容复制到锁空间中,并循环CAS尝试将共享对象的Mark Word替换为指向锁空间的指针;若成功,则执行线程逻辑;
3.若替换Mark Word失败,则自旋尝试获取锁,获取到则执行线程逻辑,但若超时,则将锁膨胀为重量级锁,并将当前线程阻塞(此时锁还是被其他线程占用着),等待被唤醒。

释放锁:
尝试用CAS,获取锁空间的内容(加锁前的Mark Word)替换共享对象内Mark Word的内容;若成功,则释放成功;否则,还需要先唤醒阻塞的线程

在这里插入图片描述

重量级锁

依赖于操作系统的mutex互斥量进行同步,需要进行操作系统间的线程切换,资源耗费很大。

三种锁的关系

偏向锁–>轻量级锁–>重量级锁 只可能从左到右膨胀,而不可能从右到左收缩。

早期的JVM只有重量级锁,但是很笨重,而JVM中的共享对象大多数情况下只由一个线程进行加锁,这就造成了浪费。于是使用了应用自旋思想的乐观轻量级锁,在共享对象只有一个线程加锁时,则轻量级锁会大大提高性能。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值