synchronized的实现原理和应用

前言

下班回来无事,电视剧没更新,吃鸡没队友,无奈打开电脑,看见旁边落灰已久的《Java并发编程的艺术》随手拿起来翻了几页,作为程序员的我感觉发现了新大陆,瞬间一发不可收拾,越看越有意思。现在想想我为什么要买这本书呢,其实从工作以来,一直就在看这方面的内容,奈何网络上知识零碎,加上理解有差,所以买回来看看,看看完整的。增加自己知识的完整度。

对于这种工具书,我的观念是千万不能看PDF和各种电子的,工具书就是要做到及时,随看随翻,可以当成字典用,需要什么翻什么,也切记从头读到尾,我的做法是,对哪方面感兴趣就去看哪,毕竟工具书没有剧情的,上下文跳着看不影响的。还有一定要记得看了之后做笔记,也就是我现在做的事,不然看了之后用不上,时间久了等于白看。好了,不多说废话了,今天看了关于标题内容的一节,记录一下,文章不长。毕竟信书不如无书,肯定看了之后加上我的理解才有了这篇文章

好,开始!

说到synchronized,这个在java开发工程师眼中肯定不陌生,Hashtable和currentHashMap的源码都是有这个的,不信打开你们的idea 然后ctrl+H(我的是这个快捷键)看看你们项目中有多少用到这个方法了。

很多人都程序它为重量级锁,就是当你锁住了一个方法,或者锁住了一个方法块,则其他线程就进不来,如果当前锁的时间耗时比较长,则会影响程序性能。但是jdk 1.6对synchronized进行了优化,有些情况他就不那么重了,这里详细介绍jdk1.6为了减少获得锁和释放锁带来的性能小号而引入的偏向锁和轻量级锁以及所得存储结构和升级过程。

正文

锁的3种表现形式

如果锁住了普通方法,则synchronized锁住的是当前对象(方法)

如果锁住了静态的方法,则synchronized锁住的是当前类的Class对象

如果锁住了一个方法块,及synchronized{},则锁的是中括号里面的对象,来看看代码吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//锁住的是当前方法
public synchronized demo{
//dosomething
}

//此时锁会升级,锁住demo1这个类
class demo1{
public static synchronized demo{

}
}

//这个只会锁住当前{}里面的内容
synchronized{
//dosomthing
}

当一个线程试图访问同步的代码块是,他必须要先得到锁,退出或抛出异常时必须释放锁

JVM规范中可以看到synchronized是基于进入和退出Monitor对象来实现方法的同步和代码块的同步,但两者的现实细节不一样,代码块同步是用的monitorenter和monitorexit指令现实的,而方法同步是用的另外一种方法,但是都可以用这两个指令来实现,这里我曾经有幸看过字节码,确实是这样的,我就不翻出来看了。

monitorenter指令时在编译后插入到同步代码块的开始位置,而monitorexit则插入在方法结束和异常的地方,JVM要保证每个monitorenter都有一个monitorexit与之配对,任何对象都有一个monitor与之关联,当一个monitor被持有之后,他就会处于锁定状态,线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获得对象锁

锁的升级和对比

书看到这里其实已经不是很懂了,本来想上网搜一下,偏向锁和轻量级锁的原理的,发现网上那些都和我这本书上的一样,哈,我想说我书买对了。

继续吧!

上文说过了,为了性能提升,引入了偏向锁和轻量级锁,锁一共有4中状态,级别从低到高分别是,无锁状态 ->

偏向锁状态 -> 轻量级锁状态 -> 重量级锁状态,这个状态会随着锁的竞争情况去升级,而且只能升级,不能降级,就是只能从偏向锁到轻量级锁,不能从轻量级锁到偏向锁。

偏向锁

偏向锁适用于只有一个线程访问同步块的场景

HotSpot的作者研究发现大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得,所以为了减少线程获得锁的代价,则引入了偏向锁。当一个线程访问同步块的代码的时候,会在对象头和栈帧中存入这个线程的ID,就相当于是做一个标记,如果下次该线程在来获取这个锁的时候,就不需要进行CAS操作来加锁和解锁了,只需要简单的判断一下对象头的Mark Word里面的ID是不是和当前访问的线程ID一致,如果一致,则直接获取到锁,如果不一致,则先去判断一下Mark Word中的偏向锁的标识是否设置成了1(表示当前为偏向锁),如果没有的话,则用CAS去竞争锁,如果竞争成功了,则将对象头里面的ID改成当前线程的ID,如果失败了,就去判断线程是否处于活动状态,如果不处于活动状态,也就是表示已经执行完了同步块里面的内容。则将对象头设置成无锁状态,如果线程还活着,则要么偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,然后唤醒暂停的线程,下面看书里面的一张图应该就好理解的,我倒现在应该看懂了。

轻量级锁

看了一下书,也在网上找了资料,我的理解是偏向锁和轻量级锁的区别是偏向锁省略了CAS的操作,轻量级锁在竞争锁的时候,会尝试使用自旋来获取锁,当自旋获取锁失败之后,则轻量级锁会膨胀成重量级锁。因为自旋会消耗CPU,为了避免无用的自旋(阻塞时),一旦锁升级到了重量级锁,则就不会在恢复到轻量级锁了,上文也说过了,只会升级,不会降级,当升级到重量级锁时,其他线程只能等待当前线程执行完,释放锁之后才会唤醒其他线程。还是有个图,看看吧

锁的优缺点对比
优点 缺点 使用场景
偏向锁 加锁和解锁不需要CAS操作,没有额外的性能消耗,和执行非同步方法相比仅存在纳秒级的差距 如果线程间存在锁竞争,会带来额外的锁撤销的消耗 适用于只有一个线程访问同步快的场景
轻量级锁 竞争的线程不会阻塞,提高了响应速度 如线程成始终得不到锁竞争的线程,使用自旋会消耗CPU性能 追求响应时间,同步快执行速度非常快
重量级锁 线程竞争不适用自旋,不会消耗CPU 线程阻塞,响应时间缓慢,在多线程下,频繁的获取释放锁,会带来巨大的性能消耗 追求吞吐量,同步快执行速度较长

结尾

这本书里面的一小部分看完了,也弄懂了,只能说大概的弄懂了,明天去翻翻《深入理解Java虚拟机》这本书里面关于这点是怎么讲的,今天就先到这了,大致了解了一下,有时间再补充。

-------------本文结束感谢您的阅读-------------
0%