好烦,面试官逮着我问ReentrantLock的这几个问题!

公平锁和非公平锁的区别?

之前分析AQS的时候,了解到AQS依赖于内部的两个FIFO队列来完成同步状态的管理,当线程获取锁失败的时候,会将当前线程以及等待状态等信息构造成Node对象并将其加入同步队列中,同时会阻塞当前线程。当释放锁的时候,会将首节点的next节点唤醒(head节点是虚拟节点),使其再次尝试获取锁。

同样的,如果线程因为某个条件不满足,而进行等待,则会将线程阻塞,同时将线程加入到等待队列中。当其他线程进行唤醒的时候,则会将等待队列中的线程出队加入到同步队列中使其再次获得执行权。

按照我们的分析,无论是同步队列还是等待队列都是FIFO,看起来就很公平呀?为什么ReentrankLock还分公平锁和不公平锁呢?

还是直接看源码吧,看看它是怎么做的?

首先看看锁的创建

// 默认是不公平锁 
public ReentrantLock() {
  sync = new NonfairSync();
}

// true表示公平锁,false表示不公平锁
public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

可以看到对应不同的锁,只是代表他们内部的Sync变量不同而已。

其中NonfairSync和FairSync两个类是Sync的子类,Sync又继承自AbstractQueuedSynchronizer

公平锁和非公平锁继承关系

当我们使用ReentrantLock加锁的时候实际上调用的是sync.lock()方法,也就是说,我们需要看看他们加锁的时候有什么不同之处?

lock的区别

可以看到在lock方法内部,非公平锁会先直接通过CAS修改state变量的值,如果修改成功则表示获取到了锁,而公平锁则是直接调用AQS的acquire方法来获取锁。

也就是说有可能当其他线程释放锁的时候,非公平锁能率先修改state的值成功,从而获取到锁。这样就比其他等待的线程率先获取到锁了,这就是不公平。

之前也有提到过,子类会根据自己的需求以实现tryAcquire方法,同样的非公平锁和公平锁的实现也实现了这个方法,我们可以来看看,两个的实现有什么不同

区别

可以看到公平锁比非公平锁的实现多了一个判断条件(!hasQueuedPredecessors()),我们来看看这个方法的实现

public final boolean hasQueuedPredecessors() {
  Node t = tail;
  Node h = head;
  Node s;
  return h != t &&
      ((s = h.next) == null || s.thread != Thread.currentThread());
}

这个方法很简单,它的意思是如果当前线程之前有排队的线程,则返回true;如果当前线程位于队列的开头或队列为空,则返回false。

也就是说公平锁在获取锁的时候会判断队列中是否已经有排队的线程,如果有则进行阻塞,如果没有则去通过CAS申请锁。

这就实现了公平锁,先来的先获取到锁,后来的后获取到锁。

所以我们可以总结下公平锁和非公平锁实现上的两点区别:

  1. 非公平锁在调用lock()方法后,首先会通过CAS抢占锁,如果恰巧这个时候锁没有被占用,则获取锁成功
  2. 非公平锁在CAS失败后,和公平锁一样会调用tryAcquire()方法,在tryAcquire()方法中,如果发现锁被释放了(state=0),非公平锁会直接CAS进行抢占,而公平锁会判断同步队列中是否有线程处于等待状态,如果有则不去抢占,而是排队获取。

这就是两者将细微的区别,如果这非公平锁两次CAS都失败了,那么会和公平锁一样,乖乖的在同步队列中排队。

相对而言,非公平锁的吞吐量更大,但是让获取锁的时间变得不确定,可能会导致同步队列中的线程长期处于饥饿状态。

ReentrantLock靠什么保证可见性?

synchronized 之所以能够保证可见性,是因为有一条happens-before原则,那Java SDK 里面 ReentrantLock 靠什么保证可见性呢?

它是利用了 volatile 相关的 Happens-Before 规则。AQS内部有一个 volatile 的成员变量 state,当获取锁的时候,会读写state 的值;解锁的时候,也会读写 state 的值。

对一个volatile变量的写操作happens-before 于后面对这个变量的读操作。这里的happens-before是时间上的先后顺序

这样说起来挺抽象的,我们直接去看JVM中对volatile是否有特殊的处理,在src/hotspot/share/interpreter/bytecodeinterpreter.cpp中,我们找到getfield和getstatic字节码执行的位置

现在这个执行器基本不再使用了,基本都会使用模板解释器,但是模板解释器的代码基本都是汇编,而我们只是想要快速了解其原理,所以可以看这个,对模板解释器感兴趣的可以去看templateTable_x86.cpp::getfield查看相关细节

...
CASE(_getfield):
CASE(_getstatic):
{
   ...

   ConstantPoolCacheEntry* cache;
   ...
   if (cache->is_volatile()) {
     if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
       OrderAccess::fence();
     }
     ...
   }
  ...
}

可以看到在访问对象字段的时候,会判断它是不是volatile的,如果是,且当前CPU平台支持多核atomic操作(现在大多数CPU都支持),就调用OrderAccess::fence()

JDK中的Unsafe也提供了内存屏障的方法,在JVM层面也是通过OrderAccess实现

接下来来看下Linux x86下的实现是怎样的(src/hotspot/os_cpu/linux_x86/orderAccess_linux_x86.cpp)

inline void OrderAccess::fence() {
// always use locked addl since mfence is sometimes expensive
#ifdef AMD64
  __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");
#else
  __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
  compiler_barrier();
}

指令中的"addl $0,0(%%esp)"(把ESP寄存器的值加0)是一个空操作,采用这个空操作而不是空操作指令nop是因为IA32手册规定lock前缀不允许配合nop指令使用,所以才采用加0这个空操作。

而lock有如下作用

  1. lock锁定的时候,如果操作某个数据,那么其他CPU核不能同时操作
  2. lock 锁定的指令,不能上下文随意排序执行,必须按照程序上下顺序执行
  3. 在 lock 锁定操作完毕之后,如果某个数据被修改了,那么需要立即告诉其他 CPU 这个值被修改了,是它们的缓存数据立即失效,需要重新到内存获取

关于lock的实现有两种,一种是锁总线,一种是锁缓存。锁缓存就涉及到CPU Cache,缓存行以及MESI了,所以这里就不展开了,有兴趣的童鞋咱们可以私下交流下。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容