【JavaEE多线程】synchronized原理篇

  • Post author:
  • Post category:java



目录


一、认识对象头


32位JVM的Mark Word的默认存储结构


一、synchronized的优化机制


1)无锁状态


2)偏向锁状态:非必要,不加锁


3)轻量级锁


4)重量级锁:挂起等待


二、锁消除


三、锁粗化


锁的粒度


锁粗化的好处


在这一篇文章当中,我们也提到了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方法的粒度细很多。


锁粗化的好处

如果对于一些应用场景,两次加锁之间,间隙非常小。

但是由于

加锁、解锁

的过程也是需要一定的开销的。那不如直接使用一把大锁搞定,就不再反复加锁、解锁了。

这种变

多把小锁



一把大锁

的现象,就被称为

锁的粗化




版权声明:本文为weixin_56738054原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。