Synchronized详解,同步互斥自旋锁分析。MonitorJVM底层实现原理
状态对象
如果一个对象有被修改的成员变量 被称为有状态的对象相反如果没有可被修改的成员变量 称为无状态的对象。
示例:
public class MyThreadTest { public static void main(String[] args) { Runnable r = new MyThread(); Thread t1 = new Thread(r); Thread t2 = new Thread(r); t1.start(); t2.start(); } } class MyThread implements Runnable { /** * 如果一个对象有被修改的成员变量 被称为有状态的对象 * 相反如果没有可被修改的成员变量 称为无状态的对象 * * 由于两个线程同时访问有状态的对象 当一个线程x++完 * 此时另外一个线程又将x变成0 此时就会输出两次0 */ int x; @SneakyThrows @Override public void run() { x = 0; while (true) { System.out.println("result: " + x++); Thread.sleep((long) Math.random() * 1000); if (x == 30) { break; } } } } /* result: 0 result: 0 result: 1 result: 2 result: 3 result: 4 result: 5 result: 6 result: 7 result: 8 result: 9 .... */
示例2:
/** * 类锁和对象锁互相不干扰 线程可以获取对象锁不影响其他的线程获取类锁 反之亦然 */ public class MyThreadTest2 { public static void main(String[] args) { MyClass myClass = new MyClass(); MyClass myClass2 = new MyClass(); Thread t1 = new Thread1(myClass); Thread t2 = new Thread2(myClass); t1.start(); try { System.out.println("name: "+Thread.currentThread().getName()); Thread.sleep(700);//睡眠main线程 } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); } } class MyClass { public synchronized void hello() { try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("hello"); } public synchronized void world() { System.out.println("world"); } } class Thread1 extends Thread { private MyClass myClass; public Thread1(MyClass myClass) { this.myClass = myClass; } @Override public void run() { myClass.hello(); } } class Thread2 extends Thread { private MyClass myClass; public Thread2(MyClass myClass) { this.myClass = myClass; } @Override public void run() { myClass.world(); } }
结论:每个实例对象都有一个唯一的Monitor(锁)。
synchronized修饰代码块
当我们用synchronized修饰代码块时字节码层面上是通过monitorenter和monitorexit指令来实现的锁的获取与释放动作。
/** * 当我们使用synchronized关键字来修饰代码块时, * 字节码层面上是通过monitorenter和monitorexit指令来实现的锁的获取与释放动作。 * monitorenter跟monitorexit 是一对多的关系 * * 当线程进入到monitorenter指令后,线程将会持有Monitor对象,执行monitorexit指令,线程将会释放Monitor对象 */ public class MyTest1 { private Object object = new Object(); public void method() { int i = 1; /* 此处的不是只能锁object 所有的都可以 此处synchronized 它会尝试去获取该对象的锁 有执行无则阻塞 */ synchronized (object) { System.out.println("hello world!"); //当应用主动抛出异常此时字节码 会直接执行并且直接执行monitorexit解锁 throw new RuntimeException(); } } public void method2() { synchronized (object) { System.out.println("welcome"); } } }
synchronized代码块修饰多个成员对象和this对象
结论:synchronized代码块锁定多个成员对象 和this对象 此时成员对象和this对象之间是互不影响的,只有当前代码块锁定的是同一个对象时才会等待。
注意: 成员属性对象加锁,若该类属于单例,那么该属性值全局并发修改始终以最新的值为主(volatile 关键字就是用来辅助线程读取最新的值 ),例如 A B C线程 线程修改(每次+1)某类的 sum =0 属性值 A最先修改为0+1 = 1 后续B接着修改就会是1+1 =2 以此类推 如果想让每个线程访问都是默认值0 需要使用Spring 的scope 的protype作用域 或者ThreadLocal 或者将其放置在方法中。
/** * synchronized代码块 锁定多个成员对象 和this对象 此时成员对象和this对象互不影响 */ public class Test { public static void main(String[] args) { MyClass myClass = new MyClass(); Thread t1 = new Thread1(myClass); Thread t2 = new Thread2(myClass); Thread t3 = new Thread3(myClass); t3.start();//5000 t1.start();//4000 try { System.out.println("name: " + Thread.currentThread().getName()); Thread.sleep(700);//睡眠main线程 } catch (InterruptedException e) { e.printStackTrace(); } t2.start(); //t1 t2 t3 } } class MyClass { final Object o1 = new Object(); final Object o2 = new Object(); public void hello() { //只锁o1的对象 由于o1和o2 是不同的对象两个方法互不影响 synchronized (o1) { try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("hello"); } public void world() { //只锁o2的对象 synchronized (o2) { System.out.println("world"); } } public synchronized void test() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("test"); } } class Thread1 extends Thread { private MyClass myClass; public Thread1(MyClass myClass) { this.myClass = myClass; } @Override public void run() { myClass.hello(); } } class Thread2 extends Thread { private MyClass myClass; public Thread2(MyClass myClass) { this.myClass = myClass; } @Override public void run() { myClass.world(); } } class Thread3 extends Thread { private MyClass myClass; public Thread3(MyClass myClass) { this.myClass = myClass; } @Override public void run() { myClass.test(); } }
输出:
name: main world hello test
/* 0 iconst_1 1 istore_1 2 aload_0 3 getfield #3 6 dup 7 astore_2 8 monitorenter 9 getstatic #4 12 ldc #5 14 invokevirtual #6 17 aload_2 18 monitorexit 19 goto 27 (+8) 22 astore_3 23 aload_2 24 monitorexit //22-26为了保证抛出异常也能释放锁 25 aload_3 26 athrow //IO流输出 存在这种异常情况发生 27 return */ /* 当加了throw new RuntimeException(); code 字节码生成的助记符 只生成一个monitorexit 0 iconst_1 1 istore_1 2 aload_0 3 getfield #3 6 dup 7 astore_2 8 monitorenter 9 getstatic #4 12 ldc #5 14 invokevirtual #6 17 new #7 20 dup 21 invokespecial #8 24 athrow 25 astore_3 26 aload_2 27 monitorexit//此时在方法体中抛出了异常 异常结束 直接释放了锁 28 aload_3 29 athrow //表示该方法一定会以异常结束 */
而其对应的标识符如下:
此时就没有通过monitorebter和moniterexit 来获取锁而是通过ACC_SYNCHRONIZED标识符来尝试获取锁synchronized修饰静态方法。
当synchronized修饰静态方法其实跟修饰成员方法一样 只不过方法标识符多了个ACC_STATIC,并且其锁的是类锁。
/** * 当synchronized修饰静态方法其实跟修饰成员方法一样 只不过方法标识符多了个ACC_STATIC * 其次锁的是 类锁 */ public class MyTest3 { /** * static静态方法不存在this局部变量 * 原因直接类名.就能调用 */ public static synchronized void method() { System.out.println("hello world!"); } } /* 0 getstatic #2 3 ldc #3 5 invokevirtual #4 8 return */
Monitor设计的概念
互斥与同步定义
关于“互斥”和“同步”的概念
- 答案很清楚了,互斥就是线程A访问了一组数据,线程BCD就不能同时访问这些数据,直到A停止访问了
- 同步就是ABCD这些线程要约定一个执行的协调顺序,比如D要执行,B和C必须都得做完,而B和C要开始,A必须先得做完。
synchronized底层原理
JVM中的同步是基于进入与退出监视器对象(管程对象)(Monitor)来实现的,每个对象实例都会有一个Monitor对象(每个Class生成时都会有且只有一个Monitor对象(锁) ), Monitor对象会和Java对象一同创建并销毁。Monitor对象是由C++来实现的。
当多个线程同时访问一段同步代码时,这些线程会被放到一个EntryList集合当中,处于阻塞状态(未获取对象锁 要区别WaitSet)的线程都会被放到该列表当中。 接下来,当线程获取到对象的Monito时,Monitor是依赖于底层操作系统的mutex lock(互斥锁)来实现互斥的,线程获取mutex成功。则会持有该mutex,这时其他线程就无法获取到该mutex.。
如果线程调用了wait方法(意思的调用wait方法才会进入WaitSet 竞争monitor时是和entryList 公平竞争),那么该线程就会释放掉所持有的mutex, 并且该线程会进入到WaitSet集合(等待集合)中,等待下一次被其他该对象锁线程调用notify/notifyAll唤醒(此处注意如果在WaitSet中被唤醒的线程没有竞争到锁该线程会进入entryList阻塞集合)。如果当前线程顺利执行完毕方法。那么它也会释放掉所持有的mutex。
用户态和内核态资源调度
总结一下:同步锁在这种实现方式当中,因为Monitor是依赖底层的操作系统实现,这样就存在用户态(如程序执行业务代码在用户端)与内核态(Monitor是依赖于底层操作系统 此时阻塞就是内核执行)之间的切换,所以会增加性能开销。 采用自旋作为回退机制当线程自旋时还是用户态占用的是CPU资源==(自旋太久也会造成CUP资源的浪费) 当自旋时间超过预期值还是会进入内核态。
通过对象互斥锁的概念来保证共享数据操作的完整性。每个对象都对应与一个可称为【互斥锁】的标记,这个标记用于保证在任何时刻,只能有一个线程访问该对象。
存在问题
那些处于EntryList与WaitSet中的线程均处于阻塞状态(两个集合都属于Monitor对象的成员变量),阻塞操作是由操作系统来完成的,在Linux下是通过pthread_mutex_lock函数实现的。 线程被阻塞后便会进入到内核调度状态,这会导致系统在用户态与内核态之间来回切换,严重影响锁的性能
解决方案
解决上述问题的办法便是自旋(Spin)。其原理是:当发生对Monitor的争用时,若Owner(拥有线程或BasicLock指针)能够在很短的时间内释放掉锁,则哪些正在争用的线程就可以稍微等待一下(即所谓的自旋),在Owner线程释放锁之后,争用线程可能会立刻获取到锁,从而避免了系统阻塞(内核态).不过,当Owner运行的时间超过了临界值后。争用线程自旋一段时间后依然无法获取到锁,这时争用线程则会停止自旋而进入到阻塞状态(内核态)。所有总体的思想:先自旋,不成功再进行阻塞,尽量降低阻塞的可能性,这对执行时间很短的代码块来时有极大的性能提升。显然自旋在多处理器(多核心)上才有意义。
互斥锁属性
PTHREAD_MUTEX_TIMED_NP: 这是省缺值,也就是普通锁,当一个线程加锁以后,其余请求锁的线程将会形成一个等待队列,并且在解锁后按照优先级获取到锁,这种策略可以确保资源分配的公平性。
PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁.允许一个线程对同一个锁成功获取多次,并通过unlock解锁。如果是不同线程请求,则在加锁线程解锁时重写进行竞争。
PTHREAD_MUTEX_ERRORCHECK_NP:检错锁。如果一个线程请求同一把锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMEDNP类型动作相同,这样就能保证了当不允许多次加锁时不会出现最简单的死锁。
PTHREAD_MUTEX_ADAPTIVE_NP:适应锁.动作最简单的锁类型,仅仅等待解锁后重新竞争。
Monitor
JVM中的同步是基于进入与退出监视器对象(管程对象)(Monitor)来实现的,每个对象实例都会有一个Monitor对象(每个Class生成对象时都会有且只有一个Monitor对象(锁)伴生 ), Monitor对象会和Java对象一同创建并销毁。Monitor对象是由C++来实现的。
Monitor对象是啥?
jdk8u/jdk8u-dev/hotspot: 3b255f489efa src/share/vm/runtime/objectMonitor.hpp
通过OpenJDK翻看JVM底层的一些C++代码。
点击进入hpp后缀文件找到如下的方法,ObjectWaiter对当前线程的封装 底层通过链表来记录。
//截取如下 class ObjectWaiter : public StackObj { public: enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ; enum Sorted { PREPEND, APPEND, SORTED } ; ObjectWaiter * volatile _next;//指向下一个ObjectWaiter ObjectWaiter * volatile _prev;//指向上游的ObjectWaiter Thread* _thread;
这么做的好处。
我们可以从一个ObjectWaiter 知道其他ObjectWaiter 的位置可以根据对应的策略选择性的唤醒对应的ObjectWaiter 如首位 中间指定等。
当waitset中唤醒的线程没有获取到monitor 就会将唤醒的线程放到entryList(也是链表格式)当中当entryList当中拿到锁就将对应线程从entryList中移除。
当没有遇到wait()方法时直接进入EntryList集合当中。
注意:WaitSet线程只是那些调用了wait()的线程,而EntryList是用来存储阻塞线程。
Wait JVM底层核心代码解析。
class ObjectMonitor { public: enum { OM_OK, // no error 没有错误 OM_SYSTEM_ERROR, // operating system error 操作系统错误 OM_ILLEGAL_MONITOR_STATE, // IllegalMonitorStateException 异常 OM_INTERRUPTED, // Thread.interrupt() OM_TIMED_OUT // Object.wait() timed out 超时 };
对应成员变量。
// initialize the monitor, exception the semaphore, all other fields // 初始化monitor, // are simple integers or pointers ObjectMonitor() { _header = NULL; _count = 0; _waiters = 0, _recursions = 0;//嵌套锁 递归嵌套 _object = NULL; _owner = NULL;//拥有线程或BasicLock指针 _WaitSet = NULL;//wait等待集合 _WaitSetLock = 0 ; //自旋锁标识字段 保护等待队列 _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; FreeNext = NULL ; _EntryList = NULL ; //阻塞集合 _SpinFreq = 0 ; _SpinClock = 0 ; OwnerIsThread = 0 ; _previous_owner_tid = 0; }
如下文档注释。
protected: ObjectWaiter * volatile _WaitSet; // LL of threads wait()ing on the monitor //Monitor上的所有线程等待()集合 protected: ObjectWaiter * volatile _EntryList ; // Threads blocked on entry or reentry. //线程在进入或返回时被阻塞。 protected: // protected for jvmtiRawMonitor void * volatile _owner; // pointer to owning thread OR BasicLock //指向拥有线程或BasicLock的指针
由JVM底层C++代码和文档注释我们可知_WaitSet和_EntryList 其实是其Monitor对应的的成员变量 初始值都为NULL。
在objectMonitor.cpp文件当中如wait方法实际对应与java Object基类当中的wait(0)所对应的方法。
// Wait/Notify/NotifyAll // // Note: a subset of changes to ObjectMonitor::wait() // will need to be replicated in complete_exit above void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { ..... ObjectWaiter node(Self);//被包装的线程节点 node.TState = ObjectWaiter::TS_WAIT ; Self->_ParkEvent->reset() ; OrderAccess::fence(); // ST into Event; membar ; LD interrupted-flag // Enter the waiting queue, which is a circular doubly linked list in this case //输入等待队列,在本例中是一个循环的双链接列表 // but it could be a priority queue or any data structure. //但它可以是优先级队列或任何数据结构。(链表优势) // _WaitSetLock protects the wait queue. Normally the wait queue is accessed only //_WaitSetLock保护等待队列。通常只访问等待队列 // by the the owner of the monitor *except* in the case where park() //由监视器的所有者*except*在park()的情况下 // returns because of a timeout of interrupt. Contention is exceptionally rare //由于中断超时而返回。争论异常罕见 // so we use a simple spin-lock instead of a heavier-weight blocking lock. //所以我们使用了一个简单的自旋锁,而不是一个更重的重量级锁。 Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;//自旋捕获 锁 AddWaiter (&node) ;//用来更换指针引用 ...... exit (true, Self) ; // exit the monitor 退出monitor
更换内容如下:
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) { assert(node != NULL, "should not dequeue NULL node"); assert(node->_prev == NULL, "node already in list"); assert(node->_next == NULL, "node already in list"); // put node at end of queue (circular doubly linked list) if (_WaitSet == NULL) { _WaitSet = node; node->_prev = node; node->_next = node; } else { ObjectWaiter* head = _WaitSet ; ObjectWaiter* tail = head->_prev; assert(tail->_next == head, "invariant check"); tail->_next = node; head->_prev = node; node->_next = head; node->_prev = tail; } }
在这我们看出当调用了waitSet方法时底层C++时,先进行SpinAcquire (自旋捕获)尝试获取锁,没获取到则将对应线程添加到waitSet当中以链表的形式,当完成上述操作时exit monitor。
notify底层核心代码解析
void ObjectMonitor::notify(TRAPS) { CHECK_OWNER(); if (_WaitSet == NULL) {//_WaitSet 为null 直接返回 TEVENT (Empty-Notify) ; return ; } .... Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ; //DequeueWaiter 根据不同的调度策略获取waitSet集合链表中目标线程 ObjectWaiter * iterator = DequeueWaiter() ; ..... if (Policy == 0) { // prepend to EntryList if (List == NULL) { iterator->_next = iterator->_prev = NULL ; _EntryList = iterator ; } else { List->_prev = iterator ; iterator->_next = List ; iterator->_prev = NULL ; _EntryList = iterator ; //此时如果目标线程未获取到monitor则放入ENtryList当中 }
总结:notify底层会先根据不同调度策略获取waitSet集合链表中目标线程,此时如果目标线程未获取到monitor则放入ENtryList当中。
notifyAll底层核心代码解析
void ObjectMonitor::notifyAll(TRAPS) { CHECK_OWNER(); ObjectWaiter* iterator; if (_WaitSet == NULL) { //WaitSet null 直接返回 TEVENT (Empty-NotifyAll) ; return ; } DTRACE_MONITOR_PROBE(notifyAll, this, object(), THREAD); int Policy = Knob_MoveNotifyee ; int Tally = 0 ; Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notifyall") ; for (;;) { //遍历所有 iterator = DequeueWaiter () ; //拿到对应的WaitSet 全部唤醒 if (iterator == NULL) break ; TEVENT (NotifyAll - Transfer1) ; ++Tally ; ....
总结:notifyAll底层通过死循环唤醒WaitSet 所有的ObjectWaiter 目标线程。