作者:bravo1988
链接:https://www.zhihu.com/question/317687988/answer/1715863550
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
大致介绍完Java对象内存结构后,我们再来解决一个新疑问:
为什么需要标记锁的状态呢?是否意味着synchronized锁有多种状态呢?
在JDK早期版本中,synchronized关键字的实现是直接基于重量级锁的。只要我们在代码中使用了synchronized,JVM就会向操作系统申请锁资源(不论当前是否真的是多线程环境),而向操作系统申请锁是比较耗费资源的,其中涉及到用户态和内核态的切换等,总之就是比较费事,且性能不高。
JDK为了解决JVM锁性能低下的问题,引入了ReentrantLock,它基于CAS+AQS,类似自旋锁。自旋的意思就是,在发生锁竞争的时候,未争取到锁的线程会在门外采取自旋的方式等待锁的释放,谁抢到谁执行。
自旋锁的好处是,不需要兴师动众地切换到内核态申请操作系统的重量级锁,在JVM层面即可实现自旋等待。但世界上并没有百利而无一害的灵丹妙药,CAS自旋虽然避免了状态切换等复杂操作,却要耗费部分CPU资源,尤其当可预计上锁的时间较长且并发较高的情况下,会造成几百上千个线程同时自旋,极大增加CPU的负担。
synchronized毕竟JDK亲儿子,所以大概在JDK1.6或者更早期的版本,官方对synchronized做了优化,提出了“锁升级”的概念,把synchronized的锁划分为多个状态,也就是上图中提到的:
- 无锁
- 偏向锁
- 轻量级锁(自旋锁)
- 重量级锁
无锁就是一个Java对象刚new出来的状态。当这个对象第一次被一个线程访问时,该线程会把自己的线程id“贴到”它的头上(Mark Word中部分位数被修改),表示“你是我的”:
此时是不存在锁竞争的,所以并不会有什么阻塞或等待。
为什么要设计“偏向锁”这个状态呢?
大家回忆一下,项目中并发的场景真的这么多吗?并没有吧。大部分项目的大部分时候,某个变量都是单个线程在执行,此时直接向操作系统申请重量级锁显然没有必要,因为根本不会发生线程安全问题。
而一旦发生锁竞争时,synchronized便会在一定条件下升级为轻量级锁,可以理解为一种自旋锁,具体自旋多少次以及何时放弃自旋,JDK也有一套相关的控制机制,大家可以自行了解。
同样是自旋,所以synchronized也会遇到ReentrantLock的问题:如果上锁时间长且自旋线程多,又该如何?
此时就会再次升级,变成传统意义上的重量级锁,本质上操作系统会维护一个队列,用空间换时间,避免多个线程同时自旋等待耗费CPU性能,等到上一个线程结束时唤醒等待的线程参与新一轮的锁竞争即可。
synchronized案例
让我们一起来看几个案例,加深对synchronized的理解。
- 同一个类中的synchronized method m1和method m2互斥吗?
t1线程执行m1方法时要去读this对象锁,但是t2线程并不需要读锁,两者各管各的,没有交集(不共用一把锁)
- 同一个类中synchronized method m1中可以调用synchronized method m2吗?
synchronized是可重入锁,可以粗浅地理解为同一个线程在已经持有该锁的情况下,可以再次获取锁,并且会在某个状态量上做+1操作(ReentrantLock也支持重入)
- 子类同步方法synchronized method m可以调用父类的synchronized method m吗?
子类对象初始化前,会调用父类构造方法,在结构上相当于包裹了一个父类对象,用的都是this锁对象
- 静态同步方法和非静态同步方法互斥吗?
各玩各的,不是同一把锁,谈不上互斥
需要更多教程,微信扫码即可
👆👆👆
别忘了扫码领资料哦【高清Java学习路线图】
和【全套学习视频及配套资料】