Java Synchronized 重量级锁原理深入剖析下(同步篇)

前言

线程并发系列文章:

Java 线程基础
Java “优雅”地中断线程
Java 线程状态
真正理解Java Volatile的妙用
Java ThreadLocal你之前了解的可能有误
Java Unsafe/CAS/LockSupport 应用与原理
Java 并发"锁"的本质(一步步实现锁)
Java Synchronized实现互斥之应用与源码初探
Java 对象头分析与使用(Synchronized相关)
Java Synchronized 偏向锁/轻量级锁/重量级锁的演变过程
Java Synchronized 重量级锁原理深入剖析上(互斥篇)
Java Synchronized 重量级锁原理深入剖析下(同步篇)
Java并发之 AQS 深入解析(上)
Java并发之 AQS 深入解析(下)
Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 详解
Java 并发之 ReentrantLock 深入分析(与Synchronized区别)
Java 并发之 ReentrantReadWriteLock 深入分析
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(原理篇)
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(应用篇)
最详细的图文解析Java各种锁(终极篇)

上篇分析了重量级锁在线程互斥场景下加锁、释放锁的过程,本篇将分析重量级锁在线程同步下的等待、通知机制。
通过本篇文章,你将了解到:

1、为什么 wait/notify/notifyAll 需要上锁
2、wait/notify/notifyAll 源码入口
3、Object.wait(xx) 流程解析
4、Object.notify()/Object.notifyAll() 流程解析
5、wait/notify/notifyAll 流程图
6、线程互斥同步下 锁的流程图
7、wait/notify/notifyAll 疑难点解析

1、为什么 wait/notify/notifyAll 需要上锁

一个Demo

线程同步需要在某种条件下调用wait/notify进行同步,先来看简单的例子:

public class TestThread {
    static Object object = new Object();
    static int count = 1;
    public static void main(String args[]) {
        //线程 A 消费者
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    count--;
                    if (count == 0) {
                        //count == 0 才会等待
                        object.wait();
                    }
                } catch (Exception e) {

                }
            }
        }).start();

        //线程 B 生产者
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //生产好了就通知线程A
                    count++;
                    object.notify();

                } catch (Exception e) {

                }
            }
        }).start();
    }
}

上述功能很简单:线程B生产东西(增加count值),线程A消费东西(减少count值),线程A发现没东西可用了就调用wait挂起等待相应的条件满足后再次运行。

此处的条件即是:count的值。

正常情况下是:线程A等待count值,线程B通知线程A count值已经准备好了,这就是线程之间的同步。

wait 之前为什么需要获取锁

现在从多线程并发的角度来看这Demo,可能的运行顺序如下:

1、count初始值为1。
2、线程A先执行到count==0,准备调用object.wait()。
3、此时线程B已经修改好了count值,并且调用了Object.notify()。
4、线程A此时调用Object.wait()后,因为错过了Object.notify(),所以就永远阻塞于此处。

导致上面问题的原因是:count是线程间共享的,对它的修改存在并发问题,因此需要加锁来实现互斥访问count。

notify 之前为什么需要获取锁

你也许会说:既然锁是为了保护count,那么只保护对应的共享变量即可,notify可以不上锁啊。如下代码:

    //线程A
    synchronized (object) {
        count--;
        if (count == 0) {
            //count == 0 才会等待
            object.wait();
        }
    }

    //线程B
    synchronized (object) {
        //生产好了就通知线程A
        count++;
    }
    object.notify();

notify 的目的是将等待队列里的线程插入到同步队列里,假设是notify没有在同步代码块里,那么线程B修改count值后释放锁,因为还没有notify,因此A没有移动到同步队列里,最终无法唤醒线程A,A就会一直阻塞等待。
notifyAll也是一样的道理。
notify具体原理接下来会详细分析。

JVM如何避免不正常地调用

wait/notify/notifyAll 需要在同步块里调用,而用户不一定这么操作,因此JVM会在调用wait/notify/notifyAll 时检测当前线程是否已经获取了锁,没有锁则会抛出异常。

#ObjectMonitor.cpp
void ObjectMonitor::notify(TRAPS) {
  CHECK_OWNER();
  ...
}
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
   ...
   CHECK_OWNER();
   ...
}
void ObjectMonitor::notifyAll(TRAPS) {
  CHECK_OWNER();
  ...
}

#define CHECK_OWNER()                                                             \
  do {                                                                            \
    if (THREAD != _owner) {      
      //当前线程不是重量级锁的获得者                                                    \
      if (THREAD->is_lock_owned((address) _owner)) {                              \
         //当前线程是之前轻量级锁的获得者
        _owner = THREAD ;  /* Convert from basiclock addr to Thread addr */       \
        _recursions = 0;                                                          \
        OwnerIsThread = 1 ;                                                       \
      } else {             
        //没有获取抛出异常                                                            \
        TEVENT (Throw IMSX) ;                                                     \
        THROW(vmSymbols::java_lang_IllegalMonitorStateException());               \
      }                                                                           \
    }                                                                             \
  } while (false)

可以看出,调用wait/notify/notifyAll的时候会调用宏CHECK_OWNER()去检测当前线程是否获取了锁,没有则抛出IllegalMonitorStateException 异常。

小结:

wait/notify/notifyAll 需要包在synchronized 同步块里的原因是保护同步的条件在并发场景下能够被正确访问。

2、wait/notify/notifyAll 源码入口

wait/notify/notifyAll 方法是声明在Java顶级类Object.java里的,通过寻找发现是native层实现的。

#Object.c
static JNINativeMethod methods[] = {
    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},
    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},
    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},
    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},
    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},
};

可以看到是动态注册的JNI函数。
以wait为例,继续寻找:

#jvm.cpp
JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
  ...
  ObjectSynchronizer::wait(obj, ms, CHECK);
JVM_END

又到了熟悉的ObjectSynchronizer类里了。

3、Object.wait(xx) 流程解析

找到了底层源码的入口,接下来就比较简单了。

#synchronizer.cpp
void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
  if (UseBiasedLocking) {
    //如果是偏向锁,则撤销
    BiasedLocking::revoke_and_rebias(obj, false, THREAD);
    assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
  }
  ...
  //膨胀为重量级锁
  ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
  DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
  //调用ObjectMonitor的wait函数
  monitor->wait(millis, true, THREAD);
  ...
}

此处可以看出,调用了wait函数后synchronized 膨胀为重量级锁了。
此时调用已经流转到ObjectMonitor里了。

#ObjectMonitor.cpp
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
   ...

   if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
     ...
     //调用wait 的时候线程可以被中断
     THROW(vmSymbols::java_lang_InterruptedException());
     return ;
   }

   //构造节点,封装了当前线程
   ObjectWaiter node(Self);
   //节点状态为TS_WAIT
   node.TState = ObjectWaiter::TS_WAIT ;
   Self->_ParkEvent->reset() ;
   OrderAccess::fence();          // ST into Event; membar ; LD interrupted-flag

   //操作等待队列需要获取锁
   Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;
   //将当前节点加入等待队列里------->(1)
   AddWaiter (&node) ;
   //释放等待队列的锁
   Thread::SpinRelease (&_WaitSetLock) ;
   ...
   //释放锁+唤醒线程------->(2)
   exit (true, Self) ;                    // exit the monitor
   ...
   { // State transition wrappers
     OSThread* osthread = Self->osthread();
     OSThreadWaitState osts(osthread, true);
     {
       ...
       if (interruptible && (Thread::is_interrupted(THREAD, false) || HAS_PENDING_EXCEPTION)) {
           // Intentionally empty
       } else
       if (node._notified == 0) {
         //挂起自己------->(3)
         if (millis <= 0) {
            Self->_ParkEvent->park () ;
         } else {
            ret = Self->_ParkEvent->park (millis) ;
         }
       }
       ...
     } // Exit thread safepoint: transition _thread_blocked -> _thread_in_vm
     //这之后是线程被唤醒后的操作
     ...
     ObjectWaiter::TStates v = node.TState ;
     if (v == ObjectWaiter::TS_RUN) {
         //正常获取锁的流程
         enter (Self) ;
     } else {
         //此时v的状态是在同步队列里--------------->(4)
         ReenterI (Self, &node) ;
         node.wait_reenter_end(this);
     }
     ...
   } // OSThreadWaitState()
}

列出了4个重点:
(1)
在上篇线程互斥加锁、释放锁的流程中可知:引入了同步队列(_cxq、_EntryList)。
竞争锁失败时最终会加入到同步队列里,当线程释放锁后会从同步队列里取出节点唤醒。
而在线程同步过程中,当调用wait函数后,节点会被加入到等待队列_WaitSet里。
来看看源码是如何实现的:

#ObjectMonitor.cpp
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
  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;
  }
}

_pre指的是前驱节点,_next指的是后驱节点。
_WaitSet 是双向循环链表。


Java Synchronized 重量级锁原理深入剖析下(同步篇)_第1张图片
image.png

如上图所示,线程A先调用wait(xx)方法,接着是线程B,最后是线程C。线程A在队列头,线程C在队列尾。
(2)
exit(xx)在上篇文章已经分析过了,其主要功能:

释放锁,然后唤醒同步队列里的节点。

(3)
还是调用ParkEvent挂起自己。
(4)
当线程被唤醒后(此时线程是在同步队列里),调用ReenterI(xx)竞争锁。

void ATTR ObjectMonitor::ReenterI (Thread * Self, ObjectWaiter * SelfNode) {
   ...
    for (;;) {
         //尝试一次加锁
        if (TryLock (Self) > 0) break ;
        //自旋加锁
        if (TrySpin (Self) > 0) break ;

        {
           //加锁失败,挂起
           jt->set_suspend_equivalent();
           if (SyncFlags & 1) {
              Self->_ParkEvent->park ((jlong)1000) ;
           } else {
              Self->_ParkEvent->park () ;
           }
        }

        //被唤醒后继续加锁
        if (TryLock(Self) > 0) break ;
        ...
    }
    //走到这,表示获取锁成功
    //从同步队列里移出节点
    UnlinkAfterAcquire (Self, SelfNode) ;
}

值得注意的是:因为已经在同步队列里,所以即使抢占锁失败后也不会加入到同步队列里了。

总结来说,调用Object.wait(xx) 方法底层主要做了四件事:

1、封装节点并加入到等待队列里。
2、释放锁并唤醒同步队列里的线程。
3、挂起自己。
4、被唤醒后继续竞争锁。

4、Object.notify()/Object.notifyAll() 流程解析

既然调用了wait(xx)后线程被挂起了,那么它什么时候被移出等待队列并且被唤醒呢?接着来看看Object.notify()。

notify 解析

与Object.wait(xx)方法入口类似:

#synchronizer.cpp
void ObjectSynchronizer::notify(Handle obj, TRAPS) {
 if (UseBiasedLocking) {
    //偏向锁,先撤销
    BiasedLocking::revoke_and_rebias(obj, false, THREAD);
  }
  markOop mark = obj->mark();
  if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
    //是轻量级锁并且锁被当前线程持有的话,直接退出。
    return;
  }
  //膨胀为重量级锁,并调用ObjectMonitor.notify()函数
  ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
}

最终还是调用了ObjectMonitor.notify()函数:

void ObjectMonitor::notify(TRAPS) {
   ...
  if (_WaitSet == NULL) {
   //等待队列为空,直接返回
     return ;
  }
  //notify 策略,默认是2
  int Policy = Knob_MoveNotifyee ;

  //获取操作等待队列的锁
  Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ;
  //将队头节点移出队列-------->(1)
  ObjectWaiter * iterator = DequeueWaiter() ;
  if (iterator != NULL) {
     ...
     ObjectWaiter * List = _EntryList ;
     if (List != NULL) {
      //修改节点状态为TS_ENTER
      ...
     }
     if (Policy == 0) {       // prepend to EntryList
      //插入_EntryList 头部
      ...
     } else
     if (Policy == 1) {      // append to EntryList
      //插入到_EntryList 尾部
      ...
     } else
     if (Policy == 2) {      // prepend to cxq
         // 默认策略-------------->(2)
         if (List == NULL) {
            //_EntryList 为空,则将节点插入到_EntryList 头
             iterator->_next = iterator->_prev = NULL ;
             _EntryList = iterator ;
         } else {
            //修改状态为TS_CXQ
            iterator->TState = ObjectWaiter::TS_CXQ ;
            for (;;) {
                ObjectWaiter * Front = _cxq ;
                iterator->_next = Front ;
                //插入到_cxq头部
                if (Atomic::cmpxchg_ptr (iterator, &_cxq, Front) == Front) {
                    break ;
                }
            }
         }
     } else
     if (Policy == 3) {      // append to cxq
      //插入到_cxq尾部
      ...
     } else {
        //都不满足,直接唤醒线程
        ParkEvent * ev = iterator->_event ;
        iterator->TState = ObjectWaiter::TS_RUN ;
        OrderAccess::fence() ;
        ev->unpark() ;
     }
     ...
  }
  //释放等待队列的锁
  Thread::SpinRelease (&_WaitSetLock) ;
  ...
}

依然列出了两个重点:
(1)
将节点从等待队列里移出:

#ObjectMonitor.cpp
inline ObjectWaiter* ObjectMonitor::DequeueWaiter() {
  //取队头节点
  ObjectWaiter* waiter = _WaitSet;
  if (waiter) {
    DequeueSpecificWaiter(waiter);
  }
  return waiter;
}
inline void ObjectMonitor::DequeueSpecificWaiter(ObjectWaiter* node) {
  ...
  ObjectWaiter* next = node->_next;
  if (next == node) {
   //后驱节点与当前节点一致,说明当前等待队列里只有一个节点
   //队列置空
    _WaitSet = NULL;
  } else {
   //从队列里移除当前节点,当前节点是队头节点
    ObjectWaiter* prev = node->_prev;
    next->_prev = prev;
    prev->_next = next;
    if (_WaitSet == node) {
      //将_WaitSet往后移动,指向下一个节点
      _WaitSet = next;
    }
  }
  node->_next = NULL;
  node->_prev = NULL;
}

(2)
将队头节点从等待队列里取出后,该怎么移动到同步队列里(_cxq、_EntryList)不同策略有不同的操作,以默认策略为例:

1、如果_EntryList为空,则将节点加入到_EntryList队列头部。
2、否则将节点加入到_cxq队列头部。

还是以线程A、B、C为例,当三者调用wait(xx)方法阻塞自己后,等待队列节点顺序为:A-->B-->C(从头到尾),现在另一个线程D调用notify()方法后,等待队列如下:


Java Synchronized 重量级锁原理深入剖析下(同步篇)_第2张图片
image.png

总结来说,调用Object.notify() 方法底层主要就做了一件事:

将节点从等待队列里移出并加入到同步队列里。

同时通过源码也解释了两个问题:

1、notify 操作没有释放锁。
2、notify 操作正常流程下没有唤醒线程。

notifyAll 解析

顾名思义,就是通知所有的等待节点。
notify是将单个节点从等待队列挪到同步队列,而notifyAll是将等待队列的节点逐个全部挪到同步队列,具体的代码就不贴了,主要看看默认策略下的处理:

#ObjectMonitor.cpp
     if (Policy == 2) {      // prepend to cxq
         // prepend to cxq
         iterator->TState = ObjectWaiter::TS_CXQ ;
         for (;;) {
             ObjectWaiter * Front = _cxq ;
             iterator->_next = Front ;
             if (Atomic::cmpxchg_ptr (iterator, &_cxq, Front) == Front) {
                 break ;
             }
         }
     }

与notify不同的是,此处是直接插入到_cxq头部了,也就是说原本在等待队列末尾的节点反而排在了同步队列的前面。

5、wait/notify/notifyAll 流程图

至此,三者原理已经剖析完毕,将三者关系用图串联起来:


Java Synchronized 重量级锁原理深入剖析下(同步篇)_第3张图片
image.png

6、线程互斥同步下 锁的流程图

结合上篇、本篇文章,已经将互斥与同步下锁的实现流程分析过了,接下来将这两者结合起来看,希望我们能从全局中把控整个流程。
先看一段伪代码:

//线程A调用
synchronized(object) {
   object.wait();
   //doSomething
}

//线程B调用
synchronized(object) {
   object.notify();
   //doSomething
}

之前你兴许会有以下疑惑:

1、线程A成功获取锁后,线程B再次获取锁会失败,线程B该如何自处?
2、线程A成功获取锁后,调用wait()方法,此时线程A处在什么状态?
3、线程A退出临界区释放锁后,又做了什么?
4、线程B调用notify()方法,发生了什么?
...

1、线程A成功获取锁后,线程B再次获取锁会失败,线程B该如何自处?
答:

线程B获取锁失败将自己插入到同步队列里,同步队列包括两个队列:_cxq和_EntryList。
此时插入到_cxq的队列的头部。
线程B将自己挂起。

2、线程A成功获取锁后,调用wait()方法,此时线程A处在什么状态?
答:

线程A将自己加入到等待队列了(_WaitSet),方式是插入到等待队列的头部。
释放占用的锁。
线程A将自己挂起。

3、线程A退出临界区释放锁后,又做了什么?
答:

线程A退出临界区后,先释放锁。
然后从同步队列里唤醒等待锁的线程。
根据不同的策略有不同的处理方式,以默认方式为例:
若是_EntryList队列不为空,则取出_EntryList队头节点并唤醒。
若是_EntryList为空,将_EntryList指向_cxq,并取出队头节点唤醒。

4、线程B调用notify()方法,发生了什么?
答:

线程B将等待队列里的头节点取出,并插入到同步队列里。
根据不同的策略有不同的处理方式,以默认方式为例:
如果_EntryList为空,则将节点加入到_EntryList队列头部。
否则将节点加入到_cxq队列头部。

最后用图表示如下:


Java Synchronized 重量级锁原理深入剖析下(同步篇)_第4张图片
image.png

总结以下,重量级锁的理解核心:

1、锁住的是ObjectMonitor里的_owner字段。
2、操作的是同步队列(_cxq/_EntryList)和等待队列(_WaitSet)。

7、wait/notify/notifyAll 疑难点解析

网上有很多解释重量级锁相关知识的文章,有些解释可能比较牵强。通过本篇的源码分析,相信你已经能够辨别,下面举例几个常出现的疑难点:
问:notify 唤醒线程是随机的吗?
答:

notify在正常的流程下并不会唤醒线程,而只是将等待队列里的节点根据一定的策略挪动到同步队列里。挪动的策略是:选取等待队列里的第一个线程挪到同步队列。也就是说同步队列是满足FIFO,notify的调用不是随机的。
而线程从等待队列取出后放到同步队列的哪个位置要看具体的模式,当某个线程释放锁后,会根据某个模式唤醒同步队列里的线程(具体模式/策略请查看第六点分析)。
也就是说线程虽然从等待队列里出来了,但是不一定就排在同步队列的第一个,也就是说下一个获取锁的线程不一定是它。这也即是官方注释说notify是随机唤醒的意思

问:notify/notifyAll 唤醒的例子
答:

public class TestThread {
    static Object object = new Object();
    static Thread a, b, c;
    public static void main(String args[]) {
        a = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (object) {
                        System.err.println("A before wait " + System.nanoTime());
                        b.start();
                        Thread.sleep(1000);
                        object.wait();
                        System.err.println("A after wait " + System.nanoTime());
                    }
                } catch (Exception e) {
                }
            }
        });
        a.start();
        b = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (object) {
                        System.err.println("B before wait " + System.nanoTime());
                        c.start();
                        Thread.sleep(1000);
                        object.wait();
                        System.err.println("B after wait " + System.nanoTime());
                    }
                } catch (Exception e) {

                }
            }
        });

        c = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (object) {
                        System.err.println("C before wait "  + System.nanoTime());
                        object.notify();
                        object.notify();
                        System.err.println("C after wait " +  + System.nanoTime());
                    }
                } catch (Exception e) {

                }
            }
        });
    }
}

如上,有线程A、B、C,A启动B、B启动C。
打印如下:


Java Synchronized 重量级锁原理深入剖析下(同步篇)_第5张图片
image.png

A比B先进同步队列,因此第一个notify的时候先将A移动到_EntryList里,第二个notify将B移动到_cxq头部,最后唤醒的时候优先从_EntryList里取,再从_cxq取。
当将两个notify换成一个notifyAll的时候,结果如下:


Java Synchronized 重量级锁原理深入剖析下(同步篇)_第6张图片
image.png

调用notifyAll的时候和单独调用多次notify的结果是相反的,这里是先唤醒了B,再唤醒了A,与我们之前的理论分析一致。

问:notify ?
答:

问:什么是虚假唤醒?
答:

public class TestThread {
    static Object object = new Object();
    static Thread a, b, c;
    static int count = 0;
    public static void main(String args[]) {
        a = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (object) {
                        System.err.println("A before wait " + System.nanoTime());
                        if (count == 0)
                            object.wait();
                        count--;
                        System.err.println("A count:" + count + " " + System.nanoTime());
                    }
                } catch (Exception e) {

                }
            }
        });
        a.start();

        b = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (object) {
                        System.err.println("B before wait " + System.nanoTime());
                        if (count == 0)
                            object.wait();
                        count--;
                        System.err.println("B count:" + count + " " + System.nanoTime());
                    }
                } catch (Exception e) {

                }
            }
        });
        b.start();
        try {
            //尽量确保线程A、B都已经运行
            Thread.sleep(1000);
        } catch (Exception e) {

        }
        c = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (object) {
                        System.err.println("C before wait " + System.nanoTime());
                        count++;
                        object.notifyAll();
                        System.err.println("C after wait " + +System.nanoTime());
                    }
                } catch (Exception e) {

                }
            }
        });
        c.start();
    }
}

如上有线程A、B、C。
A、B开启后判断count == 0,于是调用wait()进行挂起,C修改(生产)count后通知所有等待的线程,A、B被唤醒后修改(消费)count,最后打印如下:


Java Synchronized 重量级锁原理深入剖析下(同步篇)_第7张图片
image.png

可以看到,A被唤醒后拿到的count==-1,这并不是想要的数值。想象一下,若是C往队列里添加了一个元素,A、B被唤醒后都从队列里取出元素,现在元素已经被B取出了,A再取的时候会发生异常。
这就是大家熟知的虚假唤醒,修改一下条件即可预防此种问题:

        System.err.println("A before wait " + System.nanoTime());
        while (count == 0)
            object.wait();
        count--;
        System.err.println("A count:" + count + " " + System.nanoTime());

当线程被唤醒后,继续查看条件变量,不满足就再次挂起。

当然也不一定非得要加while,要看具体场景,比方说只有一个线程A调用wait,另一个线程B调用notify,这时候的A里没必要加。如果你不确定是否需要加或者不想区分场景是否加,那最好加上,毕竟也只是多了一次判断而已,更加保险。

至此,Synchronized相关知识已经分析完毕,接下来将重点分析AQS,并横向和Synchronized比较。

本文源码基于jdk1.8,运行环境也是jdk1.8。
因此上述demo在你的环境下可能有不同的效果,请注意甄别。虽然不同版本可能有不同的策略,但是核心思想都是一致的。

您若喜欢,请点赞、关注,您的鼓励是我前进的动力

持续更新中,和我一起步步为营系统、深入学习Android/Java

1、Android各种Context的前世今生
2、Android DecorView 一窥全貌(上)
3、Android DecorView 一窥全貌(下)
4、Window/WindowManager 不可不知之事
5、View Measure/Layout/Draw 真明白了
6、Android事件分发全套服务
7、Android invalidate/postInvalidate/requestLayout 彻底厘清
8、Android Window 如何确定大小/onMeasure()多次执行原因
9、Android事件驱动Handler-Message-Looper解析
10、Android 键盘一招搞定
11、Android 各种坐标彻底明了
12、Android Activity/Window/View 的background
13、Android IPC 之Service 还可以这么理解
14、Android IPC 之Binder基础
15、Android IPC 之Binder应用
16、Android IPC 之AIDL应用(上)
17、Android IPC 之AIDL应用(下)
18、Android IPC 之Messenger 原理及应用
19、Android IPC 之获取服务(IBinder)
20、Android 存储基础
21、Android 10、11 存储完全适配(上)
22、Android 10、11 存储完全适配(下)
23、Java 并发系列不再疑惑

你可能感兴趣的:(Java Synchronized 重量级锁原理深入剖析下(同步篇))