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

前言

线程并发系列文章:

Java 线程基础
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 是双向循环链表。
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()方法后,等待队列如下:
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 流程图

至此,三者原理已经剖析完毕,将三者关系用图串联起来:
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队列头部。

最后用图表示如下:
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。
打印如下:
image.png
A比B先进同步队列,因此第一个notify的时候先将A移动到_EntryList里,第二个notify将B移动到_cxq头部,最后唤醒的时候优先从_EntryList里取,再从_cxq取。
当将两个notify换成一个notifyAll的时候,结果如下:
image.png
调用notifyAll的时候和单独调用多次notify的结果是相反的,这里是先唤醒了B,再唤醒了A,与我们之前的理论分析一致。

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

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,最后打印如下:
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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值