(二)synchronized的实现原理(从JVM中C++源码层面研究)

从JVM中C++源码层面研究synchronized的实现原理

(1)首先Synchronize的实现是基于Monitor对象来实现的,

  1. List item

(2)从JVM中的hotspoot的ObjectMonitor类的源码去研究Synchronize:

  1. 下面的_WaitSet(线程的等待队列)和_EntryList(线程的锁池)其实就是,每个对象锁的线程都会包装成一个ObjectWaiter来放到上面的两个集合中
  2. 其中的_owner这个就是指向持有ObjectMonitor对象的线程,当有多个线程同时获取同一个对象资源的时候,线程会先进入_EntryList(也就是锁池中等待),当其中一个线程A获取到Monitor对象后,会把owner指向获取到Monitor对象的线程A,然后monitor的计数器就会加1,然后线程A释放锁的时候会计数器减一,并且把_owner对象置空便于指向下一个线程,然后线程A被放入到_WaitSet等待队列中,等待下一次被唤醒,整个操作结束。

链接地址:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/da3a1f729b2b/src/share/vm/runtime/objectMonitor.hpp

  // JVM/DI GetMonitorInfo() needs this
  ObjectWaiter* first_waiter()                                         { return _WaitSet; }
  ObjectWaiter* next_waiter(ObjectWaiter* o)                           { return o->_next; }
  Thread* thread_of_waiter(ObjectWaiter* o)                            { return o->_thread; }

  // initialize the monitor, exception the semaphore, all other fields
  // are simple integers or pointers
  ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //monitor对象的计数器 
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; //这个是一个等待队列
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //这个是相当于一个锁池
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

浅谈synchronized的底层实现原理

首先synchronized是一种重量级的锁,跟volatile相比而言。但是两者又有各自的优缺点和不足之处,比如volatile锁的是关键的变量,而synchronized锁的主要是对象,通过monitor机制来实现如何能锁住对象的

下面可以锁住的对象和方法
(二)synchronized的实现原理(从JVM中C++源码层面研究)_第1张图片

对象锁(monitor)机制

一个简单的demo

public class SynchronizedDemo {
    public static void main(String[] args) {
        synchronized (SynchronizedDemo.class) {
        }
        method();
    }

    private static void method() {
    }
}

上面的代码中有一个同步代码块,锁住的是类对象,并且还有一个同步静态方法,锁住的依然是该类的类对象。编译之后,切换到SynchronizedDemo.class的同级目录之后,然后用javap -v SynchronizedDemo.class查看字节码文件:
(二)synchronized的实现原理(从JVM中C++源码层面研究)_第2张图片

如图,上面用黄色高亮的部分就是需要注意的部分了,这也是添Synchronized关键字之后独有的。执行同步代码块后首先要先执行monitorenter指令,退出的时候monitorexit指令。通过分析之后可以看出,使用Synchronized进行同步,其关键就是必须要对对象的监视器monitor进行获取,当线程获取monitor后才能继续往下执行,否则就只能等待。而这个获取的过程是互斥的,即同一时刻只有一个线程能够获取到monitor。上面的demo中在执行完同步代码块之后紧接着再会去执行一个静态同步方法,而这个方法锁的对象依然就这个类对象,那么这个正在执行的线程还需要获取该锁吗?答案是不必的,从上图中就可以看出来,执行静态同步方法的时候就只有一条monitorexit指令,并没有monitorenter获取锁的指令。这就是锁的重入性,即在同一锁程中,线程不需要再次获取同一把锁。Synchronized先天具有重入性。每个对象拥有一个计数器,当线程获取该对象锁后,计数器就会加一,释放锁后就会将计数器减一。

任意一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取该对象的监视器才能进入同步块和同步方法,如果没有获取到监视器的线程将会被阻塞在同步块和同步方法的入口处,进入到BLOCKED状态

这个是表现了对象,对象监视器,同步队列以及执行线程状态之间的关系:
(二)synchronized的实现原理(从JVM中C++源码层面研究)_第3张图片

轻量级锁的释放:

(二)synchronized的实现原理(从JVM中C++源码层面研究)_第4张图片

synchronized的执行过程:

  1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁
  2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1
  3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
  4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁
  5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
  6. 如果自旋成功则依然处于轻量级状态。
  7. 如果自旋失败,则升级为重量级锁。

上面几种锁都是JVM自己内部实现,当我们执行synchronized同步块的时候jvm会根据启用的锁和当前线程的争用情况,决定如何执行同步操作;

在所有的锁都启用的情况下线程进入临界区时会先去获取偏向锁,如果已经存在偏向锁了,则会尝试获取轻量级锁,启用自旋锁,如果自旋也没有获取到锁,则使用重量级锁,没有获取到锁的线程阻塞挂起,直到持有锁的线程执行完同步块唤醒他们;

偏向锁是在无锁争用的情况下使用的,也就是同步开在当前线程没有执行完之前,没有其它线程会执行该同步块,一旦有了第二个线程的争用,偏向锁就会升级为轻量级锁,如果轻量级锁自旋到达阈值后,没有获取到锁,就会升级为重量级锁;

你可能感兴趣的:(【并发专题】)