Synchronized keyword, the underlying principle

Synchronized keyword, the underlying principle

synchronized keyword main used in three ways

  • Examples of modified method, the current applied to the lock object instance, the synchronization code to be obtained before entering the current object lock instance

  • Modification of static methods, the role of the current non-empty lock class object, before entering the lock synchronization code to obtain the current class object. Accessing static synchronized method takes the current class lock is locked, and access to non-static synchronized method is currently occupied by the lock examples of object lock. I.e., to lock the current class, the class would be applied to all object instances

  • Modifying the code block, designated lock object, to lock a given object, before entering the synchronization code library to obtain a given lock of the object

  • Additional information can be found in detail

synchronize core components

Here Insert Picture Description

  • waitQueue: Which method calls wait thread is placed here
  • ContentionList: competition in the queue, all requests lock threads are placed in this queue competition
  • Entry List: Contention List are eligible to become a candidate thread resources
  • OnDeck: any time, at most one thread is competing resource lock, the thread becomes onDeck
  • Owner: has received the acquired resources thread Owner
  • ! Owner: The current thread releases the lock

the underlying principle of synchronized keyword summary

  • Synchronization block of statements

    • Achieve synchronized sync block of statements using the monitorenter and monitorexit instructions, wherein monitorenter instruction start position of the synchronization code points to blocks, monitorexit instruction indicating the end position of the synchronization code block.
    • When performing monitorenter instruction thread tries to acquire a lock that is acquired monitor (monitor object exists in the subject header of each Java object, synchronized lock is acquired in this way locks, it is why any object in Java can be used as a lock reason) to their holding.

    Examples of variables: storing attribute information such as data, attribute information including the parent class, instance if it is part of the array further comprises a length of the array, this memory 4-byte aligned.
    Padding data: Since the virtual machine start-request object address must be an integer multiple of 8 bytes. Padding data need not be present, just for byte alignment, you can understand this point
    and for the top, it is the first Java object that implements the basic synchronized lock object, which we focus on analyzing it, in general, synchronized the lock object is used to store a 2 word stored in the object header in advance of the Java objects, the JVM

    Here Insert Picture Description

  • Hotspot virtual machine object header includes two pieces of data: Mark Word (marker fields), Class Pointer (pointer type)

  • Mark Word key used to store runtime data object itself, it is the realization of lightweight lock and lock bias

  • Mark Word for storing runtime data object itself, such as: hash code (HashCode), GC generational age lock state flag thread holds the lock, the thread ID bias, bias timestamp. The figure is part of the memory structure Mark Word under the locked state no Java object header (32-bit virtual machine):
    Here Insert Picture Description

  • Mark Word is designed as a non-fixed data structures as much as possible so that a minimum of space in the data storage memory, it will reuse their own storage space according to the state of the object, that is, the operation of program Mark Word will occur changes that may change as the store the following four kinds of data:

    1. When the object is not as lock, which is a common objects, hashcode, lock flag MarkWord record object is 01, whether or not biased locking 0

    2. When the object is treated as synchronization lock and there is a thread A grab the lock, the lock flag is still 01, but whether it is biased locking flag is 1, the first 23bit record to grab the thread id lock, showing a state biased into the lock

    3. When a thread A again attempts to acquire the lock, JVM found that flag synchronization lock objects is 01, whether or not biased locking flag is 1, which is part of biased locking state, the thread id MarkWord recorded in a thread A own id, represents a thread has acquired the lock bias, the executable code synchronization lock

    4. 当线程B试图获取锁时,JVM发现同步锁处于偏向状态,但是MarkWord中线程id记录的不是B线程id,则B线程会先用CAS操作试图获取锁。如果抢锁成功,就把MarkWord中的线程id改为B的id,代表B线程获得了这个偏向锁,可以执行同步代码,如果抢锁失败,执行下一步

    5. 偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁升级为轻量级锁,JVM会在当前线程栈中开辟一块单独空间,里面保存的是指向对象锁的MarkWord的指针,同时对象锁的MarkWord中保存指向这片空间的指针,上述两个操作均为CAS操作。如果保存成功,代表线程抢到了同步锁,就把MarkWord中的锁标志位改为00,可以执行同步锁代码。如果失败,执行下一步

    6. 轻量级锁抢锁失败,JVM自旋,自旋锁不是一种锁状态,代表不断重试

    7. 自旋重试之后依然失败,同步锁升级至重量级锁,锁标志位改为10

  • 当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外一个线程释放为止。
    Here Insert Picture Description
  • 修饰方法的的情况

    • synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法,JVM 通过该 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。
      *Here Insert Picture Description

    JDK1.6 之后的底层优化

    DK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等 技术来减少锁操作的开销

    锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率

    • 偏向锁
      • 引入偏向锁的目的和引入轻量级锁的目的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。但是不同是:轻量级锁在无竞争的情况下使用 CAS 操作去代替使用互斥量。而偏向锁在无竞争的情况下会把整个同步都消除掉
    • 轻量级锁
      • 倘若偏向锁失败,虚拟机并不会立即升级为重量级锁,它还会尝试使用一种称为轻量级锁的优化手段(1.6之后加入的)。轻量级锁不是为了代替重量级锁,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗,因为使用轻量级锁时,不需要申请互斥量。另外,轻量级锁的加锁和解锁都用到了CAS操作

      • 轻量级锁能够提升程序同步性能的依据是“对于绝大部分锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。如果没有竞争,轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争,除了互斥量开销外,还会额外发生CAS操作,因此在有锁竞争的情况下,轻量级锁比传统的重量级锁更慢!如果锁竞争激烈,那么轻量级将很快膨胀为重量级锁

    • 自旋锁和自适应自旋锁
      • 轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层面挂起,还会进行一项称为自旋锁的优化手段。

      • 互斥同步对性能最大的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转入内核态中完成(用户态转换到内核态会耗费时间)。

      • 一般线程持有锁的时间都不是太长,所以仅仅为了这一点时间去挂起线程/恢复线程是得不偿失的。 所以,虚拟机的开发团队就这样去考虑:“我们能不能让后面来的请求获取锁的线程等待一会而不被挂起呢?看看持有锁的线程是否很快就会释放锁”。为了让一个线程等待,我们只需要让线程执行一个忙循环(自旋),这项技术就叫做自旋

      • 自旋锁在 JDK1.6 之前其实就已经引入了,不过是默认关闭的,需要通过–XX:+UseSpinning参数来开启。JDK1.6及1.6之后,就改为默认开启的了。需要注意的是:自旋等待不能完全替代阻塞,因为它还是要占用处理器时间。自旋次数的默认值是10次,用户可以修改**–XX:PreBlockSpin**来更改。

      • 另外,在 JDK1.6 中引入了自适应的自旋锁。自适应的自旋锁带来的改进就是:自旋的时间不在固定了,而是和前一次同一个锁上的自旋时间以及锁的拥有者的状态来决定,虚拟机变得越来越“聪明”了。

    • 锁消除
      • 锁消除理解起来很简单,它指的就是虚拟机即使编译器在运行时,如果检测到那些共享数据不可能存在竞争,那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。
    • 锁粗化
      • 原则上,我们再编写代码的时候,总是推荐将同步快的作用范围限制得尽量小——只在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。

      • 大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。

发布了20 篇原创文章 · 获赞 0 · 访问量 259

Guess you like

Origin blog.csdn.net/white_zzZ/article/details/103367910