浅谈java中的锁级别

来源:互联网 发布:微信 for mac 2.2 dmg 编辑:程序博客网 时间:2024/05/17 09:12

本人第一篇博客,和大家分享一点关于java中的锁级别的问题,(PS:博主原先的名字千林被占用了,所以暂用DeBug大哭),由于本人知识,能力有限,如果表述中出现错误以及其他问题,请各位看官多多指教。闲话少说,开始正题。

个人认为在JAVA开发中,锁是一个避不开的问题,小到练习性代码,大到企业级开发的,并发编程是程序猿必须走的一步,而在进行并发编程时,如何进行线程间的同步,则一直是各种大牛,菜鸟们想法设法解决的问题,而为了保证线程间的数据同步以及数据可见性,常用的解决方式,便是加锁。

其实在JAVA中,每个对象都可以当成锁来使用,这要从JAVA对象来谈起。首先JAVA对象中有一个叫做‘对象头’的部分,JAVA对象头由Mark Word,Class matedata Address以及Array length三部分组成,而对象头中的Mark Word 则主要负责存储锁信息,当然,Mark Word不仅仅存储锁信息,其中还包括了对象分代信息,对象HashCode等。在存储的锁信息中,主要有三个部分,分别是锁状态,是否是偏向锁(之后会谈到偏向锁),以及锁的标记位。下面和大家分享一下这三个部分的作用。

在JAVA中,锁是有分级的,资源消耗从低到高分别为偏向锁,轻量级锁以及重量级锁。HotSpot的开发者在研究中发现一个很因吹斯汀的现象,就是在大多数情况下,锁的使用并不存在竞争(博主到现在也没明白怎么回事),反而经常被同一个线程得到或者占用,所以为了减少锁释放以及获取的资源消耗,便有了偏向锁。我们刚才讲到在对象头中存储了是否是偏向锁的标记以及锁状态,在偏向锁的使用中,就会用到。首先当某个线程获取到偏向锁后,锁(其实就是对象)就会在对象头中存储获取到自己的线程的线程ID,标识,自己已经被该线程获取到了(大家可以这样理解,你娶了你貌美如花,沉鱼落雁的妻子,然后你妻子就存储了你的标记,是什么呢,就是戒指啊,然后由于你妻子太美了,其他帅哥就会过来勾搭你妻子啊,然后这个标记就起作用了,帅哥们看到你妻子的戒指以后就会知道,她是有老公的人了,当然,这个比喻并不恰当)。之后每次线程来获取锁的时候,就是进行检查,当获取到该锁的线程来重新获取锁的时候,因为锁里面本来就存储了这个线程的ID,所以就会允许使用,但是如果线程B也来获取锁(看吧,老婆太漂亮,有人来竞争了吧奋斗),首先竞争肯定是不成功的,但是线程B这哥们太强势,强制性的要求线程A释放锁,那么线程A在执行完同步体之后,就会释放偏向锁。但是偏向锁的释放需要等待全局安全点,也就是在这个安全点上没有任何程序在执行,那么锁就会将自己对象头中的偏向线程ID设置为空,(你老婆摘下了戒指),然后呢就表示锁已经是没有线程在使用了,然后线程恢复,线程B对释放偏向锁的请求就执行成功了,那么线程B是不是一定能获取锁呢,这是不一定的,只是对释放锁的请求执行成功了就是了(就是线程B要求线程A和他老婆分手,然后线程B就使用了一些阴暗的手段--CAS置换,然后线程A就和锁分手了,至于分手之后锁会被谁获取,是不一定的,说不定仍然会被线程A获取)。

偏向锁之后是轻量级锁。JVM在执行同步块之前,会在线程的栈帧中创建一块空间,专门用来存储所记录,那么存储的内容是什么呢,就是我们上面说到的Mark Word,那么对象原来用来存储Mark Word的空间用来存储什么呢,其实是用来存储指向线程中存储锁记录的指针。所以在轻量级锁获取当中,首先是在线程的栈帧中创建存储Mark Word的记录空间,然后就开始竞争锁,在竞争锁的过程中进行CAS,将对象中原本存储Mark Word的空间,置换为指向记录的指针,如果成功,标识获取成功,如果失败,则自旋(简单理解成死循环),不断的去尝试获取锁。那轻量级锁是怎么释放的呢,首先当线程在使用完锁之后,会尝试将栈帧中锁记录部分的数据置换会对象头中,如果成功,表示释放成功,如果失败。。。。下面是我们要说的锁升级。

首先当线程A获取到轻量锁的时候,线程B同样也来请求锁,但是由于锁已经被线程A获取到了,线程B会首先尝试自旋获取,但是由于自旋会消耗大量的资源,所以在某些情况下会判定自旋失败,那么就会将锁升级为重量级锁,当锁膨胀为重量级锁之后,线程B就会跳出自旋,然后阻塞自己。如果线程A尝试释放锁,就会释放失败,然后锁就知道有线程在竞争锁,就会唤醒阻塞的线程,然后就会开始重量级锁的竞争。

需要注意的是锁是可以升级的,但是并不能降级,这主要是为了提高获取锁和释放锁的效率。而且各种锁的适用条件并不相同。例如偏向锁,加锁和解锁并不需要太多的资源,但是如果存在大量的线程竞争,就会产生损耗,所以适用于只有一个线程执行同步块的情况(博主一直以为,这话说的很鸡肋,如果只有一个程序在执行同步块,为什么不把同步块单独写进线程,当然,这是不考虑线程封装的情况,如果所有的线程都执行一样的功能,那自然最好不要为了写一个同步块,单独写一个线程)。而轻量级锁在使用时由于竞争的线程不会阻塞,线程响应速度自然提高了,但是适应自旋来获取锁,就会造成CPU的额外损耗,所以适用于同步块执行时间比较短,并且追求响应速度的程序。

而重量级锁则不会消耗CPU资源,但是响应速度最慢,但是可以实现高吞吐量,(轻量级锁如果遇到高吞吐,大量的线程在自旋,机器估计要炸了),所以重量级锁,适用于追求高吞吐量,或者,同步块执行时间较长的程序。

上面就是博主的一些学习体会,希望大家多多指教。

原创粉丝点击