索引

  • synchronized的使用
    • 修饰实例方法
    • 修饰静态方法
    • 修饰代码块
    • 总结
  • Synchronzied的底层原理
    • 对象头和内置锁(ObjectMonitor)
    • synchronzied的底层原理
  • synchronized的优化
    • 偏向锁
    • 轻量级锁
    • 轻量级锁膨胀
    • 重量级锁
    • 自旋
    • 编译期间锁优化
  • 总结
  • 参考资料

 

synchronized的使用

synchronized关键字是Java中解决并发问题的一种常用方法,也是最简单的一种方法,其作用有三个:(1)互斥性:确保线程互斥的访问同步代码(2)可见性:保证共享变量的修改能够及时可见(3)有序性:有效解决重排序问题,其用法也有三个:

  1. 修饰实例方法
  2. 修饰静态方法
  3. 修饰代码块

修饰实例方法

  1. public class Thread1 implements Runnable{
  2. //共享资源(临界资源)
  3. static int i=0;
  4.  
  5. //如果没有synchronized关键字,输出小于20000
  6. public synchronized void increase(){
  7. i++;
  8. }
  9. public void run() {
  10. for(int j=0;j<10000;j++){
  11. increase();
  12. }
  13. }
  14. public static void main(String[] args) throws InterruptedException {
  15. Thread1 t=new Thread1();
  16. Thread t1=new Thread(t);
  17. Thread t2=new Thread(t);
  18. t1.start();
  19. t2.start();
  20. t1.join();//主线程等待t1执行完毕
  21. t2.join();//主线程等待t2执行完毕
  22. System.out.println(i);
  23. }
  24. /**
  25. * 输出结果:
  26. * 20000
  27. */
  28. }

修饰静态方法

  1. public class Thread1 {
  2. //共享资源(临界资源)
  3. static int i = 0;
  4.  
  5. //如果没有synchronized关键字,输出小于20000
  6. public static synchronized void increase() {
  7. i++;
  8. }
  9.  
  10. public static void main(String[] args) throws InterruptedException {
  11. Thread t1 = new Thread(new Runnable() {
  12. public void run() {
  13. for (int j = 0; j < 10000; j++) {
  14. increase();
  15. }
  16. }
  17. });
  18. Thread t2 = new Thread(new Runnable() {
  19. @Override
  20. public void run() {
  21. for (int j = 0; j < 10000; j++) {
  22. increase();
  23. }
  24. }
  25. });
  26. t1.start();
  27. t2.start();
  28. t1.join();//主线程等待t1执行完毕
  29. t2.join();//主线程等待t2执行完毕
  30. System.out.println(i);
  31. }
  32. /**
  33. * 输出结果:
  34. * 20000
  35. */
  36. }

修饰代码块

  1. public class Thread1 implements Runnable{
  2. //共享资源(临界资源)
  3. static int i=0;
  4.  
  5.  
  6. @Override
  7. public void run() {
  8. for(int j=0;j<10000;j++){
  9. //获得了String的类锁
  10. synchronized (String.class){
  11. i++;}
  12. }
  13. }
  14. public static void main(String[] args) throws InterruptedException {
  15. Thread1 t=new Thread1();
  16. Thread t1=new Thread(t);
  17. Thread t2=new Thread(t);
  18. t1.start();
  19. t2.start();
  20. t1.join();
  21. t2.join();
  22. System.out.println(i);
  23. }
  24. /**
  25. * 输出结果:
  26. * 20000
  27. */
  28. }

总结

  1. synchronized修饰的实例方法,多线程并发访问时,只能有一个线程进入,获得对象内置锁,其他线程阻塞等待,但在此期间线程仍然可以访问其他方法。
  2. synchronized修饰的静态方法,多线程并发访问时,只能有一个线程进入,获得类锁,其他线程阻塞等待,但在此期间线程仍然可以访问其他方法。
  3. synchronized修饰的代码块,多线程并发访问时,只能有一个线程进入,根据括号中的对象或者是类,获得相应的对象内置锁或者是类锁
  4. 每个类都有一个类锁,类的每个对象也有一个内置锁,它们是互不干扰的,也就是说一个线程可以同时获得类锁和该类实例化对象的内置锁,当线程访问非synchronzied修饰的方法时,并不需要获得锁,因此不会产生阻塞。

Synchronzied的底层原理

对象头和内置锁(ObjectMonitor)

根据jvm的分区,对象分配在堆内存中,可以用下图表示:

对象头

Hotspot虚拟机的对象头包括两部分信息,第一部分用于储存对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志,锁指针等,这部分数据在32bit和64bit的虚拟机中大小分别为32bit和64bit,官方称它为"Mark word",考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间中存储尽量多的信息,它会根据对象的状态复用自己的存储空间,详细情况如下图:

对象头的另外一部分是类型指针,即对象指向它的类元数据的指针,如果对象访问定位方式是句柄访问,那么该部分没有,如果是直接访问,该部分保留。句柄访问方式如下图:

直接访问如下图:

内置锁(ObjectMonitor)

通常所说的对象的内置锁,是对象头Mark Word中的重量级锁指针指向的monitor对象,该对象是在HotSpot底层C++语言编写的(openjdk里面看),简单看一下代码:

  1. //结构体如下
  2. ObjectMonitor::ObjectMonitor() {
  3. _header = NULL;
  4. _count = 0;
  5. _waiters = 0,
  6. _recursions = 0; //线程的重入次数
  7. _object = NULL;
  8. _owner = NULL; //标识拥有该monitor的线程
  9. _WaitSet = NULL; //等待线程组成的双向循环链表,_WaitSet是第一个节点
  10. _WaitSetLock = 0 ;
  11. _Responsible = NULL ;
  12. _succ = NULL ;
  13. _cxq = NULL ; //多线程竞争锁进入时的单向链表
  14. FreeNext = NULL ;
  15. _EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表,_EntryList是第一个节点
  16. _SpinFreq = 0 ;
  17. _SpinClock = 0 ;
  18. OwnerIsThread = 0 ;
  19. }

① _owner:初始时为NULL。当有线程占有该monitor时,_owner标记为该线程的唯一标识。当线程释放monitor时,_owner又恢复为NULL。_owner是一个临界资源,JVM是通过CAS操作来保证其线程安全的。
② _cxq:竞争队列,所有请求锁的线程首先会被放在这个队列中(单向链接)。_cxq是一个临界资源,JVM通过CAS原子指令来修改_cxq队列。修改前_cxq的旧值填入了node的next字段,_cxq指向新值(新线程)。因此_cxq是一个后进先出的stack(栈)。
③ _EntryList:候选队列,_cxq队列中有资格成为候选资源的线程会被移动到该队列中
④ _WaitSet:因为调用wait方法而被阻塞的线程会被放在该队列中

ObjectMonitor队列之间的关系转换可以用下图表示:

既然提到了_WaitSet和_EntryList(_cxq队列后面会说),那就看一下底层的wait和notify方法
wait方法的实现过程:

  1. //1.调用ObjectSynchronizer::wait方法
  2. void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
  3. /*省略 */
  4. //2.获得Object的monitor对象(即内置锁)
  5. ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
  6. DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
  7. //3.调用monitor的wait方法
  8. monitor->wait(millis, true, THREAD);
  9. /*省略*/
  10. }
  11. //4.在wait方法中调用addWaiter方法
  12. inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
  13. /*省略*/
  14. if (_WaitSet == NULL) {
  15. //_WaitSet为null,就初始化_waitSet
  16. _WaitSet = node;
  17. node->_prev = node;
  18. node->_next = node;
  19. } else {
  20. //否则就尾插
  21. ObjectWaiter* head = _WaitSet ;
  22. ObjectWaiter* tail = head->_prev;
  23. assert(tail->_next == head, "invariant check");
  24. tail->_next = node;
  25. head->_prev = node;
  26. node->_next = head;
  27. node->_prev = tail;
  28. }
  29. }
  30. //5.然后在ObjectMonitor::exit释放锁,接着 thread_ParkEvent->park 也就是wait

总结:通过object获得内置锁(objectMonitor),通过内置锁将Thread封装成ObjectWaiter对象,然后addWaiter将它插入以_WaitSet为首结点的等待线程链表中去,最后释放锁。

notify方法的底层实现

  1. //1.调用ObjectSynchronizer::notify方法
  2. void ObjectSynchronizer::notify(Handle obj, TRAPS) {
  3. /*省略*/
  4. //2.调用ObjectSynchronizer::inflate方法
  5. ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
  6. }
  7. //3.通过inflate方法得到ObjectMonitor对象
  8. ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
  9. /*省略*/
  10. if (mark->has_monitor()) {
  11. ObjectMonitor * inf = mark->monitor() ;
  12. assert (inf->header()->is_neutral(), "invariant");
  13. assert (inf->object() == object, "invariant") ;
  14. assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is inva;lid");
  15. return inf
  16. }
  17. /*省略*/
  18. }
  19. //4.调用ObjectMonitor的notify方法
  20. void ObjectMonitor::notify(TRAPS) {
  21. /*省略*/
  22. //5.调用DequeueWaiter方法移出_waiterSet第一个结点
  23. ObjectWaiter * iterator = DequeueWaiter() ;
  24. //6.后面省略是将上面DequeueWaiter尾插入_EntrySet的操作
  25. /**省略*/
  26. }

总结:通过object获得内置锁(objectMonitor),调用内置锁的notify方法,通过_WaitSet结点移出等待链表中的首结点,将它置于_EntrySet中去,等待获取锁。注意:notifyAll根据policy不同可能移入_EntryList或者_cxq队列中,此处不详谈。

在了解对象头和ObjectMonitor后,接下来我们结合分析synchronzied的底层实现。

synchronzied的底层原理

synchronized修饰代码块

通过下列简介的代码来分析:

  1. public class test{
  2. public void testSyn(){
  3. synchronized(this){
  4. }
  5. }
  6. }

javac编译,javap -verbose反编译,结果如下:

  1. /**
  2. * ...
  3. **/
  4. public void testSyn();
  5. descriptor: ()V
  6. flags: ACC_PUBLIC
  7. Code:
  8. stack=2, locals=3, args_size=1
  9. 0: aload_0
  10. 1: dup
  11. 2: astore_1
  12. 3: monitorenter //申请获得对象的内置锁
  13. 4: aload_1
  14. 5: monitorexit //释放对象内置锁
  15. 6: goto 14
  16. 9: astore_2
  17. 10: aload_1
  18. 11: monitorexit //释放对象内置锁
  19. 12: aload_2
  20. 13: athrow
  21. 14: return

此处我们只讨论了重量级锁(ObjectMonitor)的获取情况,其他锁的获取放在后面synchronzied的优化中进行说明。源码如下:

  1. void ATTR ObjectMonitor::enter(TRAPS) {
  2. Thread * const Self = THREAD ;
  3. void * cur ;
  4. //通过CAS操作尝试把monitor的_owner字段设置为当前线程
  5. cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  6. //获取锁失败
  7. if (cur == NULL) {
  8. assert (_recursions == 0 , "invariant") ;
  9. assert (_owner == Self, "invariant") ;
  10. return ;
  11. }
  12.  
  13. //如果之前的_owner指向该THREAD,那么该线程是重入,_recursions++
  14. if (cur == Self) {
  15. _recursions ++ ;
  16. return ;
  17. }
  18. //如果当前线程是第一次进入该monitor,设置_recursions为1,_owner为当前线程
  19. if (Self->is_lock_owned ((address)cur)) {
  20. assert (_recursions == 0, "internal state error");
  21. _recursions = 1 ; //_recursions标记为1
  22. _owner = Self ; //设置owner
  23. OwnerIsThread = 1 ;
  24. return ;
  25. }
  26. /**
  27. *此处省略锁的自旋优化等操作,统一放在后面synchronzied优化中说
  28. **/

总结:

  1. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的owner
  2. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
  3. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权

synchronized修饰方法

还是从简洁的代码来分析:

  1. public class test{
  2. public synchronized void testSyn(){
  3. }
  4. }

javac编译,javap -verbose反编译,结果如下:

  1. /**
  2. * ...
  3. **/
  4. public synchronized void testSyn();
  5. descriptor: ()V
  6. flags: ACC_PUBLIC, ACC_SYNCHRONIZED
  7. Code:
  8. stack=0, locals=1, args_size=1
  9. 0: return
  10. LineNumberTable:
  11. line 3: 0

结果和synchronized修饰代码块的情况不同,仔细比较会发现多了ACC_SYNCHRONIZED这个标识,test.java通过javac编译形成的test.class文件,在该文件中包含了testSyn方法的方法表,其中ACC_SYNCHRONIZED标志位是1,当线程执行方法的时候会检查该标志位,如果为1,就自动的在该方法前后添加monitorenter和monitorexit指令,可以称为monitor指令的隐式调用。

上面所介绍的通过synchronzied实现同步用到了对象的内置锁(ObjectMonitor),而在ObjectMonitor的函数调用中会涉及到Mutex lock等特权指令,那么这个时候就存在操作系统用户态和核心态的转换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,这也是为什么早期的synchronized效率低的原因。在jdk1.6之后,从jvm层面做了很大的优化,下面主要介绍做了哪些优化。

synchronized的优化

在了解了synchronized重量级锁效率特别低之后,jdk自然做了一些优化,出现了偏向锁,轻量级锁,重量级锁,自旋等优化,我们应该改正monitorenter指令就是获取对象重量级锁的错误认识,很显然,优化之后,锁的获取判断次序是偏向锁->轻量级锁->重量级锁。

偏向锁

源码如下:

  1. //偏向锁入口
  2. void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) {
  3. //UseBiasedLocking判断是否开启偏向锁
  4. if (UseBiasedLocking) {
  5. if (!SafepointSynchronize::is_at_safepoint()) {
  6. //获取偏向锁的函数调用
  7. BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
  8. if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
  9. return;
  10. }
  11. } else {
  12. assert(!attempt_rebias, "can not rebias toward VM thread");
  13. BiasedLocking::revoke_at_safepoint(obj);
  14. }
  15. }
  16. //不能偏向,就获取轻量级锁
  17. slow_enter (obj, lock, THREAD) ;
  18. }

BiasedLocking::revoke_and_rebias调用过程如下流程图:

偏向锁的撤销过程如下:

 

轻量级锁

轻量级锁获取源码:

  1. //轻量级锁入口
  2. void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
  3. markOop mark = obj->mark(); //获得Mark Word
  4. assert(!mark->has_bias_pattern(), "should not see bias pattern here");
  5. //是否无锁不可偏向,标志001
  6. if (mark->is_neutral()) {
  7. //图A步骤1
  8. lock->set_displaced_header(mark);
  9. //图A步骤2
  10. if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
  11. TEVENT (slow_enter: release stacklock) ;
  12. return ;
  13. }
  14. // Fall through to inflate() ...
  15. } else if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) { //如果Mark Word指向本地栈帧,线程重入
  16. assert(lock != mark->locker(), "must not re-lock the same lock");
  17. assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock");
  18. lock->set_displaced_header(NULL);//header设置为null
  19. return;
  20. }
  21. lock->set_displace
  22.  
  23. d_header(markOopDesc::unused_mark());
  24. //轻量级锁膨胀,膨胀完成之后尝试获取重量级锁
  25. ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD);
  26. }

轻量级锁获取流程如下:

轻量级锁撤销源码:

  1. void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
  2. assert(!object->mark()->has_bias_pattern(), "should not see bias pattern here");
  3. markOop dhw = lock->displaced_header();
  4. markOop mark ;
  5. if (dhw == NULL) {//如果header为null,说明这是线程重入的栈帧,直接返回,不用回写
  6. mark = object->mark() ;
  7. assert (!mark->is_neutral(), "invariant") ;
  8. if (mark->has_locker() && mark != markOopDesc::INFLATING()) {
  9. assert(THREAD->is_lock_owned((address)mark->locker()), "invariant") ;
  10. }
  11. if (mark->has_monitor()) {
  12. ObjectMonitor * m = mark->monitor() ;
  13. }
  14. return ;
  15. }
  16.  
  17. mark = object->mark() ;
  18. if (mark == (markOop) lock) {
  19. assert (dhw->is_neutral(), "invariant") ;
  20. //CAS将Mark Word内容写回
  21. if ((markOop) Atomic::cmpxchg_ptr (dhw, object->mark_addr(), mark) == mark) {
  22. TEVENT (fast_exit: release stacklock) ;
  23. return;
  24. }
  25. }
  26. //CAS操作失败,轻量级锁膨胀,为什么在撤销锁的时候会有失败的可能?
  27. ObjectSynchronizer::inflate(THREAD, object)->exit (THREAD) ;
  28. }

轻量级锁撤销流程如下:

 

轻量级锁膨胀

源代码:

  1. ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
  2. assert (Universe::verify_in_progress() ||
  3. !SafepointSynchronize::is_at_safepoint(), "invariant") ;
  4. for (;;) { // 为后面的continue操作提供自旋
  5. const markOop mark = object->mark() ; //获得Mark Word结构
  6. assert (!mark->has_bias_pattern(), "invariant") ;
  7.  
  8. //Mark Word可能有以下几种状态:
  9. // * Inflated(膨胀完成) - just return
  10. // * Stack-locked(轻量级锁) - coerce it to inflated
  11. // * INFLATING(膨胀中) - busy wait for conversion to complete
  12. // * Neutral(无锁) - aggressively inflate the object.
  13. // * BIASED(偏向锁) - Illegal. We should never see this
  14.  
  15. if (mark->has_monitor()) {//判断是否是重量级锁
  16. ObjectMonitor * inf = mark->monitor() ;
  17. assert (inf->header()->is_neutral(), "invariant");
  18. assert (inf->object() == object, "invariant") ;
  19. assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
  20. //Mark->has_monitor()为true,说明已经是重量级锁了,膨胀过程已经完成,返回
  21. return inf ;
  22. }
  23. if (mark == markOopDesc::INFLATING()) { //判断是否在膨胀
  24. TEVENT (Inflate: spin while INFLATING) ;
  25. ReadStableMark(object) ;
  26. continue ; //如果正在膨胀,自旋等待膨胀完成
  27. }
  28.  
  29. if (mark->has_locker()) { //如果当前是轻量级锁
  30. ObjectMonitor * m = omAlloc (Self) ;//返回一个对象的内置ObjectMonitor对象
  31. m->Recycle();
  32. m->_Responsible = NULL ;
  33. m->OwnerIsThread = 0 ;
  34. m->_recursions = 0 ;
  35. m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ;//设置自旋获取重量级锁的次数
  36. //CAS操作标识Mark Word正在膨胀
  37. markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
  38. if (cmp != mark) {
  39. omRelease (Self, m, true) ;
  40. continue ; //如果上述CAS操作失败,自旋等待膨胀完成
  41. }
  42. m->set_header(dmw) ;
  43. m->set_owner(mark->locker());//设置ObjectMonitor的_owner为拥有对象轻量级锁的线程,而不是当前正在inflate的线程
  44. m->set_object(object);
  45. /**
  46. *省略了部分代码
  47. **/
  48. return m ;
  49. }
  50. }
  51. }

轻量级锁膨胀流程图:

现在来回答下之前提出的问题:为什么在撤销轻量级锁的时候会有失败的可能?
假设thread1拥有了轻量级锁,Mark Word指向thread1栈帧,thread2请求锁的时候,就会膨胀初始化ObjectMonitor对象,将Mark Word更新为指向ObjectMonitor的指针,那么在thread1退出的时候,CAS操作会失败,因为Mark Word不再指向thread1的栈帧,这个时候thread1自旋等待infalte完毕,执行重量级锁的退出操作

重量级锁

重量级锁的获取入口:

  1. void ATTR ObjectMonitor::enter(TRAPS) {
  2. Thread * const Self = THREAD ;
  3. void * cur ;
  4. cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  5. if (cur == NULL) {
  6. assert (_recursions == 0 , "invariant") ;
  7. assert (_owner == Self, "invariant") ;
  8. return ;
  9. }
  10.  
  11. if (cur == Self) {
  12. _recursions ++ ;
  13. return ;
  14. }
  15.  
  16. if (Self->is_lock_owned ((address)cur)) {
  17. assert (_recursions == 0, "internal state error");
  18. _recursions = 1 ;
  19. // Commute owner from a thread-specific on-stack BasicLockObject address to
  20. // a full-fledged "Thread *".
  21. _owner = Self ;
  22. OwnerIsThread = 1 ;
  23. return ;
  24. }
  25. /**
  26. *上述部分在前面已经分析过,不再累述
  27. **/
  28.  
  29. Self->_Stalled = intptr_t(this) ;
  30. //TrySpin是一个自旋获取锁的操作,此处就不列出源码了
  31. if (Knob_SpinEarly && TrySpin (Self) > 0) {
  32. Self->_Stalled = 0 ;
  33. return ;
  34. }
  35. /*
  36. *省略部分代码
  37. */
  38. for (;;) {
  39. EnterI (THREAD) ;
  40. /**
  41. *省略了部分代码
  42. **/
  43. }
  44. }

进入EnterI (TRAPS)方法(这段代码个人觉得很有意思):

  1. void ATTR ObjectMonitor::EnterI (TRAPS) {
  2. Thread * Self = THREAD ;
  3. if (TryLock (Self) > 0) {
  4. //这下不自旋了,我就默默的TryLock一下
  5. return ;
  6. }
  7.  
  8. DeferredInitialize () ;
  9. //此处又有自旋获取锁的操作
  10. if (TrySpin (Self) > 0) {
  11. return ;
  12. }
  13. /**
  14. *到此,自旋终于全失败了,要入队挂起了
  15. **/
  16. ObjectWaiter node(Self) ; //将Thread封装成ObjectWaiter结点
  17. Self->_ParkEvent->reset() ;
  18. node._prev = (ObjectWaiter *) 0xBAD ;
  19. node.TState = ObjectWaiter::TS_CXQ ;
  20. ObjectWaiter * nxt ;
  21. for (;;) { //循环,保证将node插入队列
  22. node._next = nxt = _cxq ;//将node插入到_cxq队列的首部
  23. //CAS修改_cxq指向node
  24. if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
  25. if (TryLock (Self) > 0) {//我再默默的TryLock一下,真的是不想挂起呀!
  26. return ;
  27. }
  28. }
  29. if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
  30. // Try to assume the role of responsible thread for the monitor.
  31. // CONSIDER: ST vs CAS vs { if (Responsible==null) Responsible=Self }
  32. Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
  33. }
  34. TEVENT (Inflated enter - Contention) ;
  35. int nWakeups = 0 ;
  36. int RecheckInterval = 1 ;
  37.  
  38. for (;;) {
  39. if (TryLock (Self) > 0) break ;//临死之前,我再TryLock下
  40.  
  41. if ((SyncFlags & 2) && _Responsible == NULL) {
  42. Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
  43. }
  44. if (_Responsible == Self || (SyncFlags & 1)) {
  45. TEVENT (Inflated enter - park TIMED) ;
  46. Self->_ParkEvent->park ((jlong) RecheckInterval) ;
  47. RecheckInterval *= 8 ;
  48. if (RecheckInterval > 1000) RecheckInterval = 1000 ;
  49. } else {
  50. TEVENT (Inflated enter - park UNTIMED) ;
  51. Self->_ParkEvent->park() ; //终于挂起了
  52. }
  53.  
  54. if (TryLock(Self) > 0) break ;
  55. /**
  56. *后面代码省略
  57. **/
  58. }

try了那么多次lock,接下来看下TryLock:

  1. int ObjectMonitor::TryLock (Thread * Self) {
  2. for (;;) {
  3. void * own = _owner ;
  4. if (own != NULL) return 0 ;//如果有线程还拥有着重量级锁,退出
  5. //CAS操作将_owner修改为当前线程,操作成功return>0
  6. if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
  7. return 1 ;
  8. }
  9. //CAS更新失败return<0
  10. if (true) return -1 ;
  11. }
  12. }

重量级锁获取入口流程图:

重量级锁的出口:

  1. void ATTR ObjectMonitor::exit(TRAPS) {
  2. Thread * Self = THREAD ;
  3. if (THREAD != _owner) {
  4. if (THREAD->is_lock_owned((address) _owner)) {
  5. _owner = THREAD ;
  6. _recursions = 0 ;
  7. OwnerIsThread = 1 ;
  8. } else {
  9. TEVENT (Exit - Throw IMSX) ;
  10. if (false) {
  11. THROW(vmSymbols::java_lang_IllegalMonitorStateException());
  12. }
  13. return;
  14. }
  15. }
  16. if (_recursions != 0) {
  17. _recursions--; // 如果_recursions次数不为0.自减
  18. TEVENT (Inflated exit - recursive) ;
  19. return ;
  20. }
  21. if ((SyncFlags & 4) == 0) {
  22. _Responsible = NULL ;
  23. }
  24.  
  25. for (;;) {
  26. if (Knob_ExitPolicy == 0) {
  27. OrderAccess::release_store_ptr (&_owner, NULL) ; // drop the lock
  28. OrderAccess::storeload() ;
  29. if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
  30. TEVENT (Inflated exit - simple egress) ;
  31. return ;
  32. }
  33. TEVENT (Inflated exit - complex egress) ;
  34. if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
  35. return ;
  36. }
  37. TEVENT (Exit - Reacquired) ;
  38. } else {
  39. if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
  40. OrderAccess::release_store_ptr (&_owner, NULL) ;
  41. OrderAccess::storeload() ;
  42. if (_cxq == NULL || _succ != NULL) {
  43. TEVENT (Inflated exit - simple egress) ;
  44. return ;
  45. }
  46. if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
  47. TEVENT (Inflated exit - reacquired succeeded) ;
  48. return ;
  49. }
  50. TEVENT (Inflated exit - reacquired failed) ;
  51. } else {
  52. TEVENT (Inflated exit - complex egress) ;
  53. }
  54. }
  55. ObjectWaiter * w = NULL ;
  56. int QMode = Knob_QMode ;
  57. if (QMode == 2 && _cxq != NULL) {
  58. /**
  59. *模式2:cxq队列的优先权大于EntryList,直接从cxq队列中取出一个线程结点,准备唤醒
  60. **/
  61. w = _cxq ;
  62. ExitEpilog (Self, w) ;
  63. return ;
  64. }
  65.  
  66. if (QMode == 3 && _cxq != NULL) {
  67. /**
  68. *模式3:将cxq队列插入到_EntryList尾部
  69. **/
  70. w = _cxq ;
  71. for (;;) {
  72. //CAS操作取出cxq队列首结点
  73. ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
  74. if (u == w) break ;
  75. w = u ; //更新w,自旋
  76. }
  77. ObjectWaiter * q = NULL ;
  78. ObjectWaiter * p ;
  79. for (p = w ; p != NULL ; p = p->_next) {
  80. guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
  81. p->TState = ObjectWaiter::TS_ENTER ; //改变ObjectWaiter状态
  82. //下面两句为cxq队列反向构造一条链,即将cxq变成双向链表
  83. p->_prev = q ;
  84. q = p ;
  85. }
  86. ObjectWaiter * Tail ;
  87. //获得_EntryList尾结点
  88. for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
  89. if (Tail == NULL) {
  90. _EntryList = w ;//_EntryList为空,_EntryList=w
  91. } else {
  92. //将w插入_EntryList队列尾部
  93. Tail->_next = w ;
  94. w->_prev = Tail ;
  95. }
  96. }
  97.  
  98. if (QMode == 4 && _cxq != NULL) {
  99. /**
  100. *模式四:将cxq队列插入到_EntryList头部
  101. **/
  102. w = _cxq ;
  103. for (;;) {
  104. ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
  105. if (u == w) break ;
  106. w = u ;
  107. }
  108. ObjectWaiter * q = NULL ;
  109. ObjectWaiter * p ;
  110. for (p = w ; p != NULL ; p = p->_next) {
  111. guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
  112. p->TState = ObjectWaiter::TS_ENTER ;
  113. p->_prev = q ;
  114. q = p ;
  115. }
  116. if (_EntryList != NULL) {
  117. //q为cxq队列最后一个结点
  118. q->_next = _EntryList ;
  119. _EntryList->_prev = q ;
  120. }
  121. _EntryList = w ;
  122. }
  123.  
  124. w = _EntryList ;
  125. if (w != NULL) {
  126. ExitEpilog (Self, w) ;//从_EntryList中唤醒线程
  127. return ;
  128. }
  129. w = _cxq ;
  130. if (w == NULL) continue ; //如果_cxq和_EntryList队列都为空,自旋
  131.  
  132. for (;;) {
  133. //自旋再获得cxq首结点
  134. ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
  135. if (u == w) break ;
  136. w = u ;
  137. }
  138. /**
  139. *下面执行的是:cxq不为空,_EntryList为空的情况
  140. **/
  141. if (QMode == 1) {//结合前面的代码,如果QMode == 1,_EntryList不为空,直接从_EntryList中唤醒线程
  142. // QMode == 1 : drain cxq to EntryList, reversing order
  143. // We also reverse the order of the list.
  144. ObjectWaiter * s = NULL ;
  145. ObjectWaiter * t = w ;
  146. ObjectWaiter * u = NULL ;
  147. while (t != NULL) {
  148. guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
  149. t->TState = ObjectWaiter::TS_ENTER ;
  150. //下面的操作是双向链表的倒置
  151. u = t->_next ;
  152. t->_prev = u ;
  153. t->_next = s ;
  154. s = t;
  155. t = u ;
  156. }
  157. _EntryList = s ;//_EntryList为倒置后的cxq队列
  158. } else {
  159. // QMode == 0 or QMode == 2
  160. _EntryList = w ;
  161. ObjectWaiter * q = NULL ;
  162. ObjectWaiter * p ;
  163. for (p = w ; p != NULL ; p = p->_next) {
  164. guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
  165. p->TState = ObjectWaiter::TS_ENTER ;
  166. //构造成双向的
  167. p->_prev = q ;
  168. q = p ;
  169. }
  170. }
  171. if (_succ != NULL) continue;
  172. w = _EntryList ;
  173. if (w != NULL) {
  174. ExitEpilog (Self, w) ; //从_EntryList中唤醒线程
  175. return ;
  176. }
  177. }
  178. }

ExitEpilog用来唤醒线程,代码如下:

  1. void ObjectMonitor::ExitEpilog (Thread * Self, ObjectWaiter * Wakee) {
  2. assert (_owner == Self, "invariant") ;
  3. _succ = Knob_SuccEnabled ? Wakee->_thread : NULL ;
  4. ParkEvent * Trigger = Wakee->_event ;
  5. Wakee = NULL ;
  6. OrderAccess::release_store_ptr (&_owner, NULL) ;
  7. OrderAccess::fence() ;
  8. if (SafepointSynchronize::do_call_back()) {
  9. TEVENT (unpark before SAFEPOINT) ;
  10. }
  11. DTRACE_MONITOR_PROBE(contended__exit, this, object(), Self);
  12. Trigger->unpark() ; //唤醒线程
  13. // Maintain stats and report events to JVMTI
  14. if (ObjectMonitor::_sync_Parks != NULL) {
  15. ObjectMonitor::_sync_Parks->inc() ;
  16. }
  17. }

重量级锁出口流程图:

 

自旋

通过对源码的分析,发现多处存在自旋和tryLock操作,那么这些操作好不好,如果tryLock过少,大部分线程都会挂起,因为在拥有对象锁的线程释放锁后不能及时感知,导致用户态和核心态状态转换较多,效率低下,极限思维就是:没有自旋,所有线程挂起,如果tryLock过多,存在两个问题:1. 即使自旋避免了挂起,但是自旋的代价超过了挂起,得不偿失,那我还不如不要自旋了。 2. 如果自旋仍然不能避免大部分挂起的话,那就是又自旋又挂起,效率太低。极限思维就是:无限自旋,白白浪费了cpu资源,所以在代码中每个自旋和tryLock的插入应该都是经过测试后决定的。

 

编译期间锁优化

锁消除

还是先看一下简洁的代码

  1. public class test {
  2. public String test(String s1,String s2) {
  3. return s1+s2;
  4. }
  5. }

javac javap后:

  1. public class test {
  2. public test();
  3. Code:
  4. 0: aload_0
  5. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  6. 4: return
  7.  
  8. public java.lang.String test(java.lang.String, java.lang.String);
  9. Code:
  10. 0: new #2 // class java/lang/StringBuilder
  11. 3: dup
  12. 4: invokespecial #3 // Method java/lang/StringBuilder."<init>":()V
  13. 7: aload_1
  14. 8: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  15. 11: aload_2
  16. 12: invokevirtual #4 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  17. 15: invokevirtual #5 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
  18. 18: areturn
  19. }

上述字节码等价成java代码为:

  1. public class test {
  2. public String test(String s1,String s2) {
  3. StringBuilder sb = new StringBuilder();
  4. sb.append(s1);
  5. sb.append(s2);
  6. return sb.toString();
  7. }
  8. }

sb的append方法是同步的,但是sb是在方法内部,每个运行的线程都会实例化一个StringBuilder对象,在私有栈持有该对象引用(其他线程无法得到),也就是说sb不存在多线程访问,那么在jvm运行期间,即时编译器就会将锁消除

锁粗化

将前面的代码稍微变一下:

  1. public class test {
  2. StringBuilder sb = new StringBuilder();
  3. public String test(String s1,String s2) {
  4. sb.append(s1);
  5. sb.append(s2);
  6. return sb.toString();
  7. }
  8. }

首先可以确定的是这段代码不能锁消除优化,因为sb是类的实例变量,会被多线程访问,存在线程安全问题,那么访问test方法的时候就会对sb对象,加锁,解锁,加锁,解锁,很显然这一过程将会大大降低效率,因此在即时编译的时候会进行锁粗化,在sb.appends(s1)之前加锁,在sb.append(s2)执行完后释放锁。

总结

引入偏向锁的目的:在只有单线程执行情况下,尽量减少不必要的轻量级锁执行路径,轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只依赖一次CAS原子指令置换ThreadID,之后只要判断线程ID为当前线程即可,偏向锁使用了一种等到竞争出现才释放锁的机制,消除偏向锁的开销还是蛮大的。如果同步资源或代码一直都是多线程访问的,那么消除偏向锁这一步骤对你来说就是多余的,可以通过-XX:-UseBiasedLocking=false来关闭
引入轻量级锁的目的:在多线程交替执行同步块的情况下,尽量避免重量级锁引起的性能消耗(用户态和核心态转换),但是如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁
重入:对于不同级别的锁都有重入策略,偏向锁:单线程独占,重入只用检查threadId等于该线程;轻量级锁:重入将栈帧中lock record的header设置为null,重入退出,只用弹出栈帧,直到最后一个重入退出CAS写回数据释放锁;重量级锁:重入_recursions++,重入退出_recursions--,_recursions=0时释放锁
最后放一张摘自网上的一张大图(保存本地,方便食用):

 

 

转载自:https://www.linuxidc.com/Linux/2018-02/150798.htm

(9条消息) 公平锁和非公平锁介绍,为什么要“非公平”?_vincent_wen0766的博客-CSDN博客_非公平锁的优势

什么是公平和非公平

公平锁指的是按照线程请求的顺序,来分配锁;而非公平锁指的是不完全按照请求的顺序,在一定情况下,可以允许插队。但需要注意这里的非公平并不是指完全的随机,不是说线程可以任意插队,而是仅仅“在合适的时机”插队

什么时候是合适的时机呢?

假设当前线程在请求获取锁的时候,恰巧前一个持有锁的线程释放了这把锁,那么当前申请锁的线程就可以不顾已经等待的线程而选择立刻插队。但是如果当前线程请求的时候,前一个线程并没有在那一时刻释放锁,那么当前线程还是一样会进入等待队列

为什么要设置非公平策略呢?

我们都知道非公平是 ReentrantLock的默认策略,如果我们不加以设置的话默认就是非公平的,难道我的这些排队的时间都白白浪费了吗,为什么别人比我有优先权呢?毕竟公平是一种很好的行为,而非公平是一种不好的行为

让我们考虑一种情况,假设线程 A 持有一把锁,线程 B 请求这把锁,由于线程 A 已经持有这把锁了,所以线程 B 会陷入等待,在等待的时候线程 B 会被挂起,也就是进入阻塞状态,那么当线程 A 释放锁的时候,本该轮到线程 B 苏醒获取锁,但如果此时突然有一个线程 C 插队请求这把锁,那么根据非公平的策略,会把这把锁给线程 C,这是因为唤醒线程 B 是需要很大开销的,很有可能在唤醒之前,线程 C 已经拿到了这把锁并且执行完任务释放了这把锁。相比于等待唤醒线程 B 的漫长过程,插队的行为会让线程 C 本身跳过陷入阻塞的过程,如果在锁代码中执行的内容不多的话,线程 C 就可以很快完成任务,并且在线程 B 被完全唤醒之前,就把这个锁交出去,这样是一个双赢的局面,对于线程 C 而言,不需要等待提高了它的效率,而对于线程 B 而言,它获得锁的时间并没有推迟,因为等它被唤醒的时候,线程 C 早就释放锁了,因为线程 C 的执行速度相比于线程 B 的唤醒速度,是很快的,所以 Java 设计非公平锁,是为了提高整体的运行效率

公平的场景

用图示来说明公平和非公平的场景,先来看公平的情况。假设我们创建了一个公平锁,此时有 4 个线程按顺序来请求公平锁,线程 1 在拿到这把锁之后,线程 2、3、4 会在等待队列中开始等待,然后等线程 1 释放锁之后,线程 2、3、4 会依次去获取这把锁,线程 2 先获取到的原因是它等待的时间最长

不公平的场景

假设线程 1 在解锁的时候,突然有线程 5 尝试获取这把锁,那么根据我们的非公平策略,线程 5 是可以拿到这把锁的,尽管它没有进入等待队列,而且线程 2、3、4 等待的时间都比线程 5 要长,但是从整体效率考虑,这把锁此时还是会交给线程 5 持有

代码演示公平和非公平

  1. /**
  2.  * 描述:演示公平锁,分别展示公平和不公平的情况,非公平锁会让现在持有锁的线程优先再次获取到锁
  3.  */
  4. public class FairAndUnfair {
  5.  
  6.  
  7.     public static void main(String args[]) {
  8.         PrintQueue printQueue new PrintQueue();
  9.  
  10.  
  11.         Thread thread[] = new Thread[10];
  12.         for (int 0; i < 10; i++) {
  13.             thread[i] = new Thread(new Job(printQueue), "Thread " + i);
  14.         }
  15.  
  16.  
  17.         for (int 0; i < 10; i++) {
  18.             thread[i].start();
  19.             try {
  20.                 Thread.sleep(100);
  21.             } catch (InterruptedException e) {
  22.                 e.printStackTrace();
  23.             }
  24.         }
  25.     }
  26.  
  27.  
  28. }
  29.  
  30.  
  31. class Job implements Runnable {
  32.  
  33.  
  34.     private PrintQueue printQueue;
  35.  
  36.  
  37.     public Job(PrintQueue printQueue) {
  38.         this.printQueue = printQueue;
  39.     }
  40.  
  41.  
  42.     @Override
  43.     public void run() {
  44.         System.out.printf("%s: Going to print a job\n", Thread.currentThread().getName());
  45.         printQueue.printJob(new Object());
  46.         System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName());
  47.     }
  48.  
  49.  
  50. }
  51.  
  52.  
  53. class PrintQueue {
  54.  
  55.  
  56.     private final Lock queueLock new ReentrantLock(false);
  57.  
  58.  
  59.     public void printJob(Object document) {
  60.         queueLock.lock();
  61.  
  62.  
  63.         try {
  64.             Long duration = (long) (Math.random() * 10000);
  65.             System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n",
  66.                     Thread.currentThread().getName(), (duration / 1000));
  67.             Thread.sleep(duration);
  68.         } catch (InterruptedException e) {
  69.             e.printStackTrace();
  70.         } finally {
  71.             queueLock.unlock();
  72.         }
  73.  
  74.  
  75.         queueLock.lock();
  76.         try {
  77.             Long duration = (long) (Math.random() * 10000);
  78.             System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n",
  79.                     Thread.currentThread().getName(), (duration / 1000));
  80.             Thread.sleep(duration);
  81.         } catch (InterruptedException e) {
  82.             e.printStackTrace();
  83.         } finally {
  84.             queueLock.unlock();
  85.             }
  86.     }
  87. }

可以通过改变 new ReentrantLock(false) 中的参数来设置公平/非公平锁,以上代码在公平的情况下的输出

  1. Thread 0: Going to print a job
  2. Thread 0: PrintQueue: Printing a Job during 5 seconds
  3. Thread 1: Going to print a job
  4. Thread 2: Going to print a job
  5. Thread 3: Going to print a job
  6. Thread 4: Going to print a job
  7. Thread 5: Going to print a job
  8. Thread 6: Going to print a job
  9. Thread 7: Going to print a job
  10. Thread 8: Going to print a job
  11. Thread 9: Going to print a job
  12. Thread 1: PrintQueue: Printing a Job during 3 seconds
  13. Thread 2: PrintQueue: Printing a Job during 4 seconds
  14. Thread 3: PrintQueue: Printing a Job during 3 seconds
  15. Thread 4: PrintQueue: Printing a Job during 9 seconds
  16. Thread 5: PrintQueue: Printing a Job during 5 seconds
  17. Thread 6: PrintQueue: Printing a Job during 7 seconds
  18. Thread 7: PrintQueue: Printing a Job during 3 seconds
  19. Thread 8: PrintQueue: Printing a Job during 9 seconds
  20. Thread 9: PrintQueue: Printing a Job during 5 seconds
  21. Thread 0: PrintQueue: Printing a Job during 8 seconds
  22. Thread 0: The document has been printed
  23. Thread 1: PrintQueue: Printing a Job during 1 seconds
  24. Thread 1: The document has been printed
  25. Thread 2: PrintQueue: Printing a Job during 8 seconds
  26. Thread 2: The document has been printed
  27. Thread 3: PrintQueue: Printing a Job during 2 seconds
  28. Thread 3: The document has been printed
  29. Thread 4: PrintQueue: Printing a Job during 0 seconds
  30. Thread 4: The document has been printed
  31. Thread 5: PrintQueue: Printing a Job during 7 seconds
  32. Thread 5: The document has been printed
  33. Thread 6: PrintQueue: Printing a Job during 3 seconds
  34. Thread 6: The document has been printed
  35. Thread 7: PrintQueue: Printing a Job during 9 seconds
  36. Thread 7: The document has been printed
  37. Thread 8: PrintQueue: Printing a Job during 5 seconds
  38. Thread 8: The document has been printed
  39. Thread 9: PrintQueue: Printing a Job during 9 seconds
  40. Thread 9: The document has been printed

而以上代码在非公平的情况下的输出是这样的

  1. Thread 0: Going to print a job
  2. Thread 0: PrintQueue: Printing a Job during 6 seconds
  3. Thread 1: Going to print a job
  4. Thread 2: Going to print a job
  5. Thread 3: Going to print a job
  6. Thread 4: Going to print a job
  7. Thread 5: Going to print a job
  8. Thread 6: Going to print a job
  9. Thread 7: Going to print a job
  10. Thread 8: Going to print a job
  11. Thread 9: Going to print a job
  12. Thread 0: PrintQueue: Printing a Job during 8 seconds
  13. Thread 0: The document has been printed
  14. Thread 1: PrintQueue: Printing a Job during 9 seconds
  15. Thread 1: PrintQueue: Printing a Job during 8 seconds
  16. Thread 1: The document has been printed
  17. Thread 2: PrintQueue: Printing a Job during 6 seconds
  18. Thread 2: PrintQueue: Printing a Job during 4 seconds
  19. Thread 2: The document has been printed
  20. Thread 3: PrintQueue: Printing a Job during 9 seconds
  21. Thread 3: PrintQueue: Printing a Job during 8 seconds
  22. Thread 3: The document has been printed
  23. Thread 4: PrintQueue: Printing a Job during 4 seconds
  24. Thread 4: PrintQueue: Printing a Job during 2 seconds
  25. Thread 4: The document has been printed
  26. Thread 5: PrintQueue: Printing a Job during 2 seconds
  27. Thread 5: PrintQueue: Printing a Job during 5 seconds
  28. Thread 5: The document has been printed
  29. Thread 6: PrintQueue: Printing a Job during 2 seconds
  30. Thread 6: PrintQueue: Printing a Job during 6 seconds
  31. Thread 6: The document has been printed
  32. Thread 7: PrintQueue: Printing a Job during 6 seconds
  33. Thread 7: PrintQueue: Printing a Job during 4 seconds
  34. Thread 7: The document has been printed
  35. Thread 8: PrintQueue: Printing a Job during 3 seconds
  36. Thread 8: PrintQueue: Printing a Job during 6 seconds
  37. Thread 8: The document has been printed
  38. Thread 9: PrintQueue: Printing a Job during 3 seconds
  39. Thread 9: PrintQueue: Printing a Job during 5 seconds
  40. Thread 9: The document has been printed

可以看出,非公平情况下,存在抢锁“插队”的现象,比如Thread 0 在释放锁后又能优先获取到锁,虽然此时在等待队列中已经有 Thread 1 ~ Thread 9 在排队了

公平和非公平的优缺点

 优势劣势
公平锁 各线程公平平等,每一个线程在等待一段时间后,总有执行的机会 更慢,吞吐量更少
非公平锁 更快,吞吐量更大 有可能产生线程饥饿,也就是某些线程在长时间内,始终得不到执行

源码分析

下面我们来分析公平和非公平锁的源码,具体看下它们是怎样实现的,可以看到在 ReentrantLock 类包含一个 Sync 类,这个类继承自AQS(AbstractQueuedSynchronizer),代码如下

  1. public class ReentrantLock implements Lock, java.io.Serializable {
  2.  
  3. private static final long serialVersionUID 7373984872572414699L;
  4.  
  5. /** Synchronizer providing all implementation mechanics */
  6.  
  7. private final Sync sync;

Sync 类的代码

abstract static class Sync extends AbstractQueuedSynchronizer {...}

Sync 有公平锁 FairSync 和非公平锁 NonfairSync两个子类

  1. static final class NonfairSync extends Sync {...}
  2. static final class FairSync extends Sync {...}

公平锁的锁获取源码如下

  1. protected final boolean tryAcquire(int acquires) {
  2.     final Thread current = Thread.currentThread();
  3.     int = getState();
  4.     if (c == 0) {
  5.         if (!hasQueuedPredecessors() && //这里判断了 hasQueuedPredecessors()
  6.                 compareAndSetState(0, acquires)) {
  7.             setExclusiveOwnerThread(current);
  8.             return true;
  9.         }
  10.     } else if (current == getExclusiveOwnerThread()) {
  11.         int nextc = c + acquires;
  12.         if (nextc < 0) {
  13.             throw new Error("Maximum lock count exceeded");
  14.         }
  15.         setState(nextc);
  16.         return true;
  17.     }
  18.     return false;
  19. }

非公平锁的锁获取源码如下

  1. final boolean nonfairTryAcquire(int acquires) {
  2.     final Thread current = Thread.currentThread();
  3.     int = getState();
  4.     if (c == 0) {
  5.         if (compareAndSetState(0, acquires)) { //这里没有判断 hasQueuedPredecessors()
  6.             setExclusiveOwnerThread(current);
  7.             return true;
  8.         }
  9.     }
  10.     else if (current == getExclusiveOwnerThread()) {
  11.         int nextc = c + acquires;
  12.         if (nextc < 0) // overflow
  13.         throw new Error("Maximum lock count exceeded");
  14.         setState(nextc);
  15.         return true;
  16.     }
  17.     return false;
  18. }

通过对比,我们可以明显的看出公平锁与非公平锁的 lock() 方法唯一的区别就在于公平锁在获取锁时多了一个限制条件:hasQueuedPredecessors() 为 false,这个方法就是判断在等待队列中是否已经有线程在排队了。这也就是公平锁和非公平锁的核心区别,如果是公平锁,那么一旦已经有线程在排队了,当前线程就不再尝试获取锁;对于非公平锁而言,无论是否已经有线程在排队,都会尝试获取一下锁,获取不到的话,再去排队

注意:针对 tryLock() 方法,它不遵守设定的公平原则

例如,当有线程执行 tryLock() 方法的时候,一旦有线程释放了锁,那么这个正在 tryLock 的线程就能获取到锁,即使设置的是公平锁模式,即使在它之前已经有其他正在等待队列中等待的线程,简单地说就是 tryLock 可以插队

看源码就会发现

  1. public boolean tryLock() {
  2.     return sync.nonfairTryAcquire(1);
  3. }

这里调用的就是 nonfairTryAcquire(),表明了是不公平的,和锁本身是否是公平锁无关

公平锁就是会按照多个线程申请锁的顺序来获取锁,从而实现公平的特性。非公平锁加锁时不考虑排队等待情况,直接尝试获取锁,所以存在后申请却先获得锁的情况,但由此也提高了整体的效率