1、synchronized 的实现原理以及锁优化?

synchronized

1:锁作用在不同的位置,锁的对象不同

       a) 对于同步方法,锁是当前实例对象

        b) 对于静态同步方法,锁是当前对象的Class对象

        c) 对于同步方法块,锁是synchonized括号里配置的对象

如下图:

解析成字节码指令:



结论:同步方法和静态同步方法:依靠的是方法修饰符上的ACC_SYNCHRONIZED实现

                a)方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置;

                b)如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor;

                c)在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

           同步代码块:使用monitorenter和monitorexit指令实现的,

               a)  会在同步块的区域通过监听器对象去获取锁和释放锁,从而在字节码层面来控制同步scope. 

                b)  monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处(类似

                      try...finally...), JVM要保证每个 monitorenter必须有对应的monitorexit与之配对。

               c ) 任何对象都有一个 monitor 与之关联,当且一个monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指 令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获得对象的锁。


2:同步原理

2.1:锁的信息存储在对象头中,对象头主要分为两个部分:

1:"Klass Pointer":对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例

(数组,对象头中还须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小。)

2:"Mark Word":

       a)用于存储对象自身的运行时数据, 如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏 向线程ID、偏向时间戳等等。

        b)64位JVM下Mark Word大小的64位的,32位JVM下Mark Word大小的32位的。

        c)在运行期间随着锁标志位的变化存储的数据也会变化,具体有如下几种运行期间(32位为例),锁标志位 和 是否偏向锁 确定唯一的锁状态:

2.2:Monitor

        Monitor是 synchronized 重量级锁的实现关键。锁的标识位为 10 

        Monitor是线程私有的数据结构,每一个对象都有一个monitor与之关联。每一个线程都有一个可用monitor record列表(当前线   程对象monitor),同时还有一个全局可用列表(全局对象monitor)。每一个被锁住的对象,都会和一个monitor关联。

        当一个monitor被某个线程持有后,它便处于锁定状态。此时,对象头中 MarkWord的指向互斥量的指针,就是指向锁对象的monitor起始地址

monitor是由 ObjectMonitor 实现的,其主要数据结构如下:



》》EntryList 和 _WaitSet :用来保存ObjectWaiter对象列表(每个等待锁的线程都会被封装成ObjectWaiter对象)

》》_owner :指向持有 objectMonitor的线程。

根据虚拟机规范的要求,在执行monitorenter指令时,会尝试获取对象的锁。如果对象没有被锁定(获取锁),获取对象已经被该线程锁定(锁重入)。则把计数器加1(_count 加1)。相应的,在执行monitorexit指令时,会讲计数器减1。当计数器为0时,_owner指向Null,锁就被释放。(摘自《深入理解JAVA虚拟机》) 


当多个线程同时访问一个同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor后,会进入_owner 区域,然后把monitor中的 _owner 变量修改为当前线程,同时monitor中的计数器_count 会加1。

如果线程调用wait()方法,将释放当前持有的monitor,_owner变量恢复为null,_count变量减1,同时该线程进入_WaitSet等待被唤醒。(wait()和sleep()的区别,wait是Object的,并且wait会释放锁。)

由此看来 monitor对象存在于每个Java对象的对象头中,synchronized锁便是通过这种方式获取锁的。

这就解释了为什么Java所有对象都可以作为锁,同时也解释了 wait()  notify()  notifyAll() 为什么存在于顶级对象Object中。


3: JVM中锁的优化

3.1:锁机制升级流程

偏向锁--》轻量级锁--》重量级锁

3.2 偏向锁

原因:

大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。

过程:

1)当一个线程访问同步块并获取锁时,简单地测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁

2)如果成功,表示线程已经获得了锁。

3)如果测试失败,判断目前锁的状态是否是偏向锁(Mark Word中偏向锁的标识是否设置成1)

4)若当前是偏向锁,则尝试使用CAS将对象头的偏向锁指向当前线程

5)若不是偏向锁,则使用CAS竞争锁。

注意:当锁有竞争关系的时候,需要解除偏向锁,进入轻量级锁。

涉及参数:

偏向锁在Java 6和Java 7里是默认启用的,但是它在应用程序启动几秒钟之后才激活,

如有必要可以使用JVM参数来关闭延迟:XX:BiasedLockingStartupDelay=0

如果确定应用的锁通常情况下处于竞争状态,可以通过JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。

3.3 轻量级锁

1>加锁

线程在执行同步块之前,JVM会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Word复制到锁记录中,官方称为Displaced Mark Word。

然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。

2>解锁

轻量级解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。

下图展示两个线程同时争夺锁,导致锁膨胀的流程图:



自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于轻量级锁的状态,如果自旋失败进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己。因为自旋会消耗CPU,为了避免无用的自旋(比如获得锁的线程被阻塞住了),一旦锁升级成重量级锁,就不会再恢复到轻量级锁状态。当锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程,被唤醒的线程就会进行新一轮的夺锁之争


3.4 锁的优缺点对比


4:几个锁概念

4.1 锁粗化

就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成一个范围更大的锁。举个例子:

publicclassLockCoarsening{privateStringBuffer stringBuffer =newStringBuffer(20);publicvoidappend(){        stringBuffer.append("w");        stringBuffer.append("h");        stringBuffer.append("y");    }}

这里每次调用stringBuffer.append方法都需要加锁和解锁,如果虚拟机检测到有一系列连串的对同一个对象加锁和解锁操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次append方法结束后进行解锁。

4.2 锁消除

锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁。逃逸分析和锁消除分别可以使用参数-XX:+DoEscapeAnalysis和-XX:+EliminateLocks(锁消除必须在-server模式下)开启

4.3 适应性自旋

当前锁处于膨胀,会进行自旋。自旋是需要消耗CPU的,如果一直获取不到锁的话,那线程一直处在自旋状态,消耗CPU资源。为了解决这个问题JDK采用—适应性自旋,线程如果自旋成功了,则下次自旋的次数会更多,如果自旋失败了,则自旋的次数就会减少。另外自旋虽然会占用CPU资源,但不会一直占用CPU资源,每隔一段时间会通过os::NakedYield方法放弃CPU资源,或通过park方法挂起;如果其他线程完成锁的膨胀操作,则退出自旋并返回.







参考文献:

https://www.jianshu.com/p/1ea87c152413

http://www.cnblogs.com/xdyixia/p/9364247.html

https://www.jianshu.com/p/73b9a8466b9c

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,723评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,003评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,512评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,825评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,874评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,841评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,812评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,582评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,033评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,309评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,450评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,158评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,789评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,409评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,609评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,440评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,357评论 2 352