`
snowm
  • 浏览: 1187 次
  • 来自: ...
最近访客 更多访客>>
社区版块
存档分类
最新评论

synchronized

    博客分类:
  • JVM
 
阅读更多
  • ​首先说下其使用特点:
  1. 排他:一个对象的锁只在一个时间段内,只能被一个线程持有,其它企图持有锁的线程都处于blocked状态(或是自旋状态running)
  2. 非公平:不是先申请就能先得到锁(jdk1.5后,ReentrantLock可以代替synchronized,可选择公平还是非公平,不过公平锁的吞吐量要低很多,数倍,ReentrantLock的效率不一定更高,但是代码会更易读)

 

  • 内部:

先介绍下对象头信息,JVM里面每个对象都有一个对象头,普通对象两个字,数组3个字(字长为虚拟机位数,32、64)

长度 内容 说明
字(32/64) Mark Word 存储对象的hashcode,分代年龄,锁信息
字(32/64) Class Metadata Address 对象的类对象的指针
字(32/64) Array Length 数组长度(如果是数组的话)

 

 

 

 

 

在本主题只介绍Mark Word内容,32位jvm如下:

 

64位如下:


  

 

 

 

  1. 偏向锁(Biased):

    场景,某线程获得锁之后,为了消除此线程锁重入的开销(CAS),就像此线程得到了偏袒。

    对象A,线程T,T竞争A上的锁,动作如下:

    先检查,发现对象A处于初始状态(Tag为01,偏向信息为0),可以加偏向锁;

    如果A上已经有偏向信息(注意,不是锁,这意味着现在就有或是曾经加过偏向锁,Tag为01,偏向信息为1,Thread Id为T),T也就是当前线程嘛,那就是重入,啥也不用做,继续操作;如果不是当前线程,那就是有不同线程竞争锁,那就要将偏向锁撤销(revode)为轻量锁,再升级为自旋-->重量锁。这个撤销操作,稍后介绍。

     

    这就是加偏向锁的条件及过程,若是其它情况偏向锁都不会启用。
    重入:发现有偏向锁,检查threadid是否就是当前线程,是,那么重入成功。

    解锁:要等到非本线程竞争者出现,才会撤销。(意思是,就算同步块已经完成,mark value也不会恢复未加锁状态)

    总结:在没有不同线程加锁的情况下,重入开销小(cas操作都不需要),解锁是被动的(开销也小),但是一旦换个线程来竞争锁,就得-->轻量锁-->自旋-->重量。

     

  2. 轻量锁(Light-weight):

    场景:线程T1曾经获取了对象A上的锁(最初就是偏向锁,当然也可能是轻量锁),然后释放了,线程T2尝试获取A上的锁,此时轻量锁就派上用场了。

    先判断A的Mark Value的内容,无锁,或是曾经有偏向锁;
    T2的栈里面会新生成一个lock record对象,其内容就是A的Mark Value的copy;
    通过CAS操作,将A的MarkValue设为lock record地址,将tag设为00,如果成功,那么就获取锁成功;失败,那说明已经有别的线程已经先一步获A上的锁了,那么就要升级到自旋或是重量锁。

    重入:也就是判断A的Mark Value时,发现有轻量锁,其地址指向的lock record就在T2的栈内,每一次重入,都会在T2的栈上新生成一个lock record,过程和新上锁一样,实际上就形成了一个版本队列,栈上第一个lock record保存的内容是第一次获取A的锁的时候的内容(无锁,或是曾经有偏向锁),第二个lock record保存的就是第一次获取锁之后A上Mark Value的内容。。。
    解锁:通过CAS操作将T2栈上的lock record还原到A的Mark Value中,如果操作成功,那么解锁成功,失败则说明有别的线程在竞争A上的锁,已经升级为自旋或是重量锁了。

    总结:在没有多线程竞争的情况下,首次加锁或重入加锁的开销都差不多,一个CAS操作,但是一旦出现多线程竞争,就往自旋或是重量锁转变。

  3. 自适应自旋锁

    任务本身要做的事如果很短,比如就是个计数器,这时如果使用阻塞锁,那么切换线程的开销可能比任务本身还大。

    自旋就是为了优化这个问题,让线程执行一个无意义循环来竞争锁,如果竞争不到,继续循环(而不是线程阻塞),直到获取锁,或是达到某一个基于统计的次数(基于JVM运行时的统计,这个次数称为上界,这就是自适应)而仍然没有拿到锁,那就转换为阻塞锁。此过程中线程一直处于RUNNABLE状态,直到任务完成或是转换成阻塞锁,转换为阻塞锁,线程就处于BLOCKED状态。

    可以抽象的解释为:线程会先不断尝试竞争锁(有次数限制,上界),超过次数,那么说明资源很紧俏,别老嚷嚷了,认命吧,进入阻塞状态安心等待。

    好处:不用第一次失败就进入阻塞队列(可能没有队列,空间优化),失败一定次数了,那么说明资源很紧俏,别老嚷嚷了,嚷嚷也要口水的,不如等资源管理者给你分配更有效(时间优化)。

  4. 阻塞锁(重量锁,Monitor):

    线程竞争锁的时候先尝试一把,如果失败或是锁已经被拿走,那么线程进入BLOCKED状态,并被塞入阻塞队列。等到锁释放,队列中的锁又来打一次群架,成功的做事,失败的老实呆着。
    具体的可以参考另外一篇:http://blog.csdn.net/chen77716/article/details/6618779

    线程的阻塞和唤醒需要CPU从用户态转为核心态,频繁切换的开销还是挺大的。

     

  5. 待续

  • 大小: 9.3 KB
  • 大小: 6.8 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics