目录
在这一篇文章当中,我们也提到了synchronized的作用。
Java对于synchronized的初步认识_革凡成圣211的博客-CSDN博客
synchronized,死锁,
https://blog.csdn.net/weixin_56738054/article/details/128062475?spm=1001.2014.3001.5501
回顾一下,在synchronized当中,两个线程如果针对同一个对象加锁,如果一个线程可以获取到锁,另外一个线程就会进入阻塞等待的状态。
关于synchronized锁的一些特性,在这一篇文章当中,已经提到了。
【JavaEE进阶】锁的特性_革凡成圣211的博客-CSDN博客
Java锁的特性
https://blog.csdn.net/weixin_56738054/article/details/128574608?spm=1001.2014.3001.5501
下面,再回顾一下synchronized的几个特性:
特性一、synchronized是一个
非公平锁
;特性二、synchronized是一个
可重入锁
;特性三、synchronized是一个
悲观锁
;特性四、synchronized是一个
互斥锁
,两个线程
不可以
同时占有一把锁;特性五、synchronized可以由一个
轻量级锁
转化为
重量级锁
。
一、认识对象头
synchronized用的锁是存在Java对象头当中的。如果对象是数组类型,则虚拟机用3个字宽存储对象头。如果对象是非数组类型,则用2字节宽存储对象头。
对象类型 | 存储大小 |
数组类型 | 3字宽(1字宽等于4字节) |
非数组类型 | 2字宽 |
32位JVM的Mark Word的默认存储结构
锁状态 | 25bit | 4bit | 1bit是否偏向锁 | 2bit锁标志位 |
无锁 | 对象的hashCode | 对象分代年龄 | 0 | 01 |
在运行期间,Mark Word里存储的数据会随着锁标志位的变化而变化。
一、synchronized的优化机制
以下四个过程:就是在线程进入
synchronized代码块
当中之后,加锁的过程,就可能会经历下面这几个阶段。
synchronized(locker){
//在这个内部,进行加锁的过程
}
synchronized内部其实还有一些优化机制,存在的目的就是为了让这个加锁的过程更加高效。
下面,将重点分析一下加锁的几个过程:
1)无锁状态
不加锁,这种状态一般不会存在。如果真的存在,那么很有可能是被编译器把锁给优化掉了。
2)偏向锁状态:非必要,不加锁
当进入synchronized代码块当中之后,首先会进入到偏向锁的状态;
其实偏向锁,就是一个线程对对象尝试加锁的一种状态,并没有真正施加锁,而是先对于这个对象的
对象头当中做一个标记
。做标记这个过程,其实相比于真正加锁,还是轻量了不少的。
如果整个使用锁的过程当中,都没有出现锁竞争,那么这个标记,就会在线程离开synchronized之后释放;
如果另外一个线程同时也尝试对于
同一个对象
加锁,那么就会造成
锁升级
,转变为
轻量级锁。
3)轻量级锁
对于synchronized,它的轻量级锁,是通过
自旋锁
的方式来实现的。
对于
自旋锁
的解释,也已经在这一篇文章当中提到了:
【JavaEE进阶】锁的特性_革凡成圣211的博客-CSDN博客
Java锁的特性
https://blog.csdn.net/weixin_56738054/article/details/128574608?spm=1001.2014.3001.5501
自旋锁虽然
不会
造成线程的阻塞等待,但是如果无法通过CAS获取到锁,就会一直在
循环
当中尝试获取锁。
如果获取到锁的线程
很快
就释放锁了,那么也就意味着自旋是划算的。如果获取到锁的线程一直没有释放锁,那么这个自旋的过程是
很消耗cpu资源的
。
因此,当synchronized处于自旋锁的状态的时候,它的内部会有一个计数器,当计算的数量达到一定的数目之后,就停止自旋,升级为
重量级锁
。
4)重量级锁:挂起等待
重量级锁,会造成线程阻塞等待。这个过程,则是基于操作系统原生的API来实现的。
这个时候,如果线程进行了重量级锁的加锁过程,那么获取不到锁的线程就会被操作系统调度离开CPU内核,被放入阻塞队列当,暂时不参与CPU的运算调度。
重量级锁,因为涉及线程调度离开CPU,调度回到CPU的过程,相比起轻量级锁,会更
加消耗CPU的资源。
目前JVM,只支持锁升级的操作,不支持锁降级的操作。
二、锁消除
锁消除是发生在编译阶段的事件
编译器的智能判定,看当前代码是否真的需要加锁。
如果不需要加锁 ,代码当中也加锁了,那么这个锁就会被编译器消除。
经典现象
:对于StringBuffer,如果单线程环境使用,那么编译器就会把这个锁消除掉。
三、锁粗化
锁的粒度
对于synchronized:它所包含的代码越多,粒度就越粗。包含的代码越少,粒度就越细。
在保证线程安全的情况下面,锁的粒度越细越好
对于ConcurrentHashMap来说,它的put方法的粒度就比Hashtable的put方法的粒度细很多。
锁粗化的好处
如果对于一些应用场景,两次加锁之间,间隙非常小。
但是由于
加锁、解锁
的过程也是需要一定的开销的。那不如直接使用一把大锁搞定,就不再反复加锁、解锁了。
这种变
多把小锁
为
一把大锁
的现象,就被称为
锁的粗化