锁机制

1锁基础和优化。

  (1)对象头mark 

    *对象头的标记,描述对象的hash,锁信息,垃圾回收标记,年龄等。包括指向锁记录的指针,指向monitor的指针,gc标记,偏向锁线程id。这部分数据的长度在32位和64位的虚拟机中分别为32bit和64bit,简称“Mark Word”

    *对象头信息是与对象自身定义的数据无关的额外存储成本。它会根据对象的状态复用自己的存储空间。例如:在32位的HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32bit空间中的25bit用于存储对象哈希(HashCode),4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0。

    *如果对象是数组类型,则虚拟机用3个Word(字宽)存储对象头,如果对象是非数组类型,则用2Word存储对象头。下图是64位mark

    

    

  (2)偏向锁  jdk1.6引入,锁会偏向于当前已经占有锁的线程。因为普遍认为大部分情况没有竞争,通过偏向可以提高性能。偏向锁将对象头mark的标记设置为偏向,并将线程id写入对象头mark。如果没有竞争,获得偏向锁的线程在将来进入同步块时,不需要做同步。当其他线程请求相同的锁时,偏向模式结束。注意如果在竞争激烈的场合,偏向锁会增加系统负担。

  意义:锁偏向于第一个获得它的线程。消除数据在无竞争情况下的同步原语,提高性能。如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。

   -XX:+UseBiasedLocking=true/false  启用/禁用偏向锁,默认偏向锁是开启的。

   -XX:BiasedLockingStartupDelay=0 偏向锁启动延时。一般启动时jdk认为竞争不激烈,会延迟几秒再启动偏向锁。

  (3)轻量级锁  

    BasicObjectLock 嵌入在线程栈中的对象,是一种快速的锁定方法。

    如果对象没有被锁定,那么轻量级锁使用时会将对象头的mark指针保存到锁对象中,将对象头的轻量锁指针设置为指向锁的指针。线程栈里有指针指向对象头,对象头又指向线程栈的轻量级锁,形成一个互相引用的关系。所以只要判断对象头的指针是否指向某个线程栈,就知道对象是否加上轻量级锁。

    *如果轻量级锁失败,表示存在竞争,则升级为重量级锁,即常规锁。竞争较少的情况下,轻量级锁可以减少使用操作系统互斥量产生的性能消耗;在竞争激烈时,轻量级锁的工作是白费的,相当于额外操作导致性能下降。

  (4)自旋锁 

    当竞争存在时,如果线程可以很快获得锁,那么可以不在os层挂起线程,而是让线程自己做些空动作。

    jdk1.6中,-XX:+UseSpinning开启自旋,1.7以后默认自旋。

    如果同步块较长,则自旋失败,降低系统性能;同步较短则自旋成功,节省线程挂起时间,提升系统性能。

  *偏向锁,轻量级锁,自旋锁 是jvm层面的锁优化方法,系统会先尝试偏向锁,若不可用则用轻量级锁,再不可用则自旋,如果再失败则升级到一般锁调用os资源。

  *代码层面,应该尽量减少锁的时间,比如只锁部分代码;减少锁粒度,比如concurrentHashMap只对部分加锁(segment);

    (5)锁分离

   根据功能进行锁分离,是减少锁粒度的一种,典型例子是读写锁(readWriteLock),读多写少的情况可以提高性能。读锁可重入,写锁读写都独占。

   * linkedBlockingQueue 是个链表队列,同时有take和put两种操作,可以进行锁分离,take和push有单独的锁,可以提高性能。

  (6)锁粗化

    正常情况下,系统要求每个线程持有锁的时间尽量短,尽快释放资源,其他线程尽早得资源;但是如果对锁请求太频繁,请求同步释放等过程会消耗过多系统资源。例如,两个同步块之间的代码时间较短,jdk会把两个同步块连同中间代码合成一个同步块,避免反复请求资源;或者for循环内部加锁,会优化成直接锁for循环。

  (7)锁消除 

    jvm在编译时,如果发现不可能被共享的对象,则可以擦除不起作用的锁。

原文地址:https://www.cnblogs.com/lkdirk/p/6437954.html