Synchronize 是什么
synchronized,中文意思为同步,用于多线程资源共享与维护的最常用手段。它通过线程互斥的手段,保存证了资源的原子性。
使用如下:
<code>synchronized
(o
) { } /<code>
实现的原理
本文要讲的主要是1.6以后的版本,1.6版本针对synchronize做了使用优化,根据使用的情使用不同的锁。
包括:
偏向锁:
1:当线程取得锁时, warkword偏向锁位标记为1,并记录使用该对象的线程指针。
2:当线程尝试向 偏向锁对象 取锁时, 锁将升级为轻量级锁。
轻量级锁:
1.当线程拿不到锁对象时,线程不会释放,而是继续while循环空转,直到获取到锁为止。
2.轻量级锁的Markword会记录lock Record的指针,lock Record会记录对象自旋的次数,当它达到一定自旋次数之后,jvm会将它升级为重量级锁。
<code>优点: 无需从用户态转向内核态,在锁竞争比较低的情况,线程只需消化几个时钟周期就能获得锁,所以性能很快。 缺点: 线程自旋是需要消耗cpu性能的,在锁竞争激烈的情况,空转的线程数量和自旋的次数会变高,此时会白白浪费cpu时钟周期。/<code>
重量级锁:
jvm对重量级锁的实现,是需要依赖操作系统底层的,操作系统底层维护了一个锁的队列,当jvm所有重量级锁的申请,都需要在这个锁队列里面进行排队,线程需要从用户态转向内核态,排队过程线程被挂起,无需消耗cpu时钟频率,直到轮到这个线程获取锁时,系统才会唤醒该争用的线程。
<code>优点: 不消耗cpu,特别时对于大量锁的争用时。 缺点: 等待锁的时间长。/<code>
synchronize 锁升级过程
如下图所示:
- 初始化,无锁。
- 有且只有一线程取得锁时,为偏向锁。
- 对象已被锁,并有其它线程尝试取锁时,锁升级为轻量级锁。
- 锁状态为轻量级锁,并有更多(达到临界值时)的线程尝试去取锁,轻量级锁将升级为重量级锁。
线程获取锁过程
如下图所示:
- 对象无锁时。直接取得锁
- 有锁并且是轻量级锁,线程自旋取得锁。
- 有锁并且是重量级锁,线程阻塞,等待锁释放再取得锁。
- 取得锁后,用完释放。
锁状态与markword
对象锁的状态是存在markword记录的,如下图所示:
无锁时,锁标志为01,另外存储了其它各种信息(包括偏向锁状态,分代年龄,hashcode)。
偏向锁时,锁标志为01,另外存储了取得锁的线程。
轻量锁时,锁标志为00,存储了线程栈Lock Record的指针。
重量级锁时,锁标志为10,存储了,重量级锁的指针。
实例
下面将通过一个实例,结合markword一步一步地演示synchronize 锁的升级过程。
测试过程
- 引入JOL打印出对象的结构(关键是markword)。
- 模拟无锁,单线程锁,2个线程,100个级线。
- 输出对应的markword。
pom.xml 配置
<code><
project
xmlns
="http://maven.apache.org/POM/4.0.0"
xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation
="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
><
modelVersion
>4.0.0modelVersion
><
groupId
>SynchronizeTestgroupId
><
artifactId
>SynchronizeTestartifactId
><
version
>1.0-SNAPSHOTversion
><
dependencies
><
dependency
><
groupId
>org.openjdk.jolgroupId
><
artifactId
>jol-coreartifactId
><
version
>0.9version
>dependency
>dependencies
>project
>/<code>
代码:
<code>import
org.openjdk.jol.info.ClassLayout;public
class
ApplicationTest
{static
volatile
String strMsg =""
;static
volatile
String str2Msg =""
;public
static
void
main
(String[] args)
throws
InterruptedException { Thread.sleep(5000
); Object o =new
Object();new
Thread() {public
void
run
()
{while
(true
) {if
(str2Msg.equals(strMsg) ==false
) { str2Msg = strMsg; System.out.print(str2Msg); } } } }.start(); strMsg = ClassLayout.parseInstance(o).toPrintable(); }public
static
void
SynsTest
(
int
num,final
Object o)throws
InterruptedException {for
(int
i =0
; i < num; i++) { System.out.println("启开第"
+ (i +1
) +"线程"
);new
Thread() {public
void
run
()
{synchronized
(o) {for
(; ; ) { strMsg = ClassLayout.parseInstance(o).toPrintable();try
{ Thread.sleep(100
); } }catch
(InterruptedException e) { e.printStackTrace(); } } } }.start(); } } }/<code>
测试步骤:
第一步:
1、o对象初始化后,直接打印出o对象。解注以下代码:
<code> strMsg = ClassLayout.parseInstance(o).toPrintable(); /<code>
2、运行结果如下图所示: 状态位为01,断定为无锁。
第二步:
1、开启1个线程取锁。解注以下代码:
<code>SynsTest
(1
, o); /<code>
2、结果如下图所示:状态位为01,后面还记录着 对应的线程指针,断定为 偏向锁。
第三步:
1、开启2个线程最锁。解注以下代码:
<code>SynsTest
(2
,o); /<code>
2、如下图所示:状态位为00,断定为轻量级锁。
第四步:
1、开始100个线程取锁。解注以下代码:
<code>SynsTest
(100
,o);/<code>
2、结果如下图所示:状态位为10,断定为重量级锁。
PS:
因为偏向锁默认在jvm启动4秒后才启动。所以在这里设置等待5秒。具体的结果与配置有关。本机测试用的环境如下,所有配置都是默认的。
可以通过以下命令打印出配置的参数:
小编整理来自:https://blog.csdn.net/richyliu44/article/details/105327885,CSDN博主「Richy Liu」
今天小编整理的这篇干货您觉得怎么样呢?是否对自己有一定的提升呢,如果您觉得还可以的话,不妨点个关注收藏转发一下呐!与小编一起进步!您的支持就是小编最大的动力!谢谢啦!关注我!每天更新哦!绝对技术干货!