Java并发编程 - 深入剖析ReentrantLock之公平锁(第4篇)

Java并发编程 - 深入剖析ReentrantLock之非公平锁重要点(第3篇)

前面3篇文章我们讲得是ReentrantLock作为非公平锁使用,在这个类中我们可以看到,除了无参构造方法外,还有一个有参的构造方法:

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

当fair设置为true是,sync被初始化为为FairSync,从名字看这是个公平的同步器,那么这个公平同步器又是怎么回事?

首先我们来比较一下FairSync和NonFairSync的lock方法:

NonFairSync

 final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

FairSync

final void lock() {
    acquire(1);
}

通过上面的比较可以看到FairSync没有快速获取锁的逻辑,每次都是通过acquire方法的调用获取。

AbstractQueuedSynchronizer.java

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

现在来看看FairSync中tryAcquire的实现:

FairSync

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

与ReentrantLock作为非公平锁模式调用nonfairTryAcquire相比,多了下面的代码:

!hasQueuedPredecessors() 

这个方法的调用有什么作用?

看下这个方法:

AbstractQueuedSynchronizer.java

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

API解释如下:

Queries whether any threads have been waiting to acquire longer than the current thread. 

查询等待队列中是否有比当前线程等待获取锁的时间更长的线程存在。

这里&&之后的条件使用 || 来连接的,&&前面的条件满足了表示非空队列,
那么&&后面的条件组合有两个:true || (不用判断) 和 false || true 。

  • true || (不用判断) : 不是&&前面的条件满足表示非空队列了吗,那为什么还要判断h.next是否为null,因为可能出现代表线程的节点正在入队列,还没来得对h.next设置。
  • false || true :此时s代表head节点的下一个节点,这个节点代表的线程不是当前线程。

回到tryAcquire,与之前非公平的获取方式不同的是,每次线程获取锁不仅仅只看能否将state设置为1,还要判断当前队列中是否有比它等待锁时间更长的线程存在,如果有的话,那么它无法获取锁,而是入队列。

在我们之前的非公平锁分析中,我们说过队列中被唤醒的线程重新请求获取锁,这时候有新来的线程会与它争抢,导致它获取不到锁,只能再次重试获取锁。而我们这里的模式就不会出现这样的情况,新来的只能入队列。

先来先得,体现了公平性,这就是公平锁的由来。

公平模式:排队打饭,同学们都有素质,自动遵守排在队头的人先打饭,新来的自动排到队尾的规则。

非公平模式:排队打饭,已排队的人都有素质,但是这时候来了一个没素质的,跟排在队头的人争抢先打饭,于是他们打了一架,排在队头的同学打赢了,先打饭,新来的同学灰溜溜到队尾了;排在队头的同学打输了,新来的同学先打饭,扬长而去,排在队头的同学继续打饭(期望不会又来新的没素质的同学)。

这篇文章我们可以说只是简要得谈了一下公平锁,因为它的内部执行流程除了我们上面说的之外,其他的几乎跟非公平锁是一样的,所以就没必要再次深入源码进行分析。

非公平锁vs公平锁

通过前面的文章,我们讲解了非公平锁和公平锁,比较了一下它们的不同点。

ReentrantLock默认采用非公平的加锁方式。那么说明非公平锁应该是我们实际使用中比较用得多,也是推荐的方式,使用非公平锁会带来什么好处呢?

这里摘抄和整理《Java编程实践》中说明。

  • 减少性能开销
    当线程请求锁而锁被其他线程占用的时候,线程会被挂起,之后会被重新唤醒。这里就涉及到线程的上下文切换问题,我们知道线程的上下文切换是需要操作系统调度的,如果大量线程出现被挂起然后又被唤醒,而且每个线程持有锁的时间比较小的话,那么就会带来巨大的性能开销。解决这种损耗的其中一个办法就是避免线程被挂起和唤醒,如果允许新来的线程可以跟之前队列中被唤醒线程争夺锁,而且争夺成功,那么它就不用入队列并被挂起,这样就减少了需要上下文切换的带来的性能损耗,而非公平锁就是这样处理的。允许"闯入式"得抢夺锁。
  • 提供系统处理的并发数(提高吞吐量)
    我们知道线程被唤醒之后,它并不是马上就运行的,也就是说挂起的线程重新开始与它真正开始运行,两者之间在竞争激烈的情况下会产生严重的延迟(需CPU调度)。如果新来的线程能利用好这个时间,在这个时间内加锁并解锁,那么就提供了系统处理并发的能力,单位时间内就能多多地执行线程。这样如果是公平地模式,锁被释放了而又只能队列中被唤醒的线程处理,白白地就浪费了唤醒到真正执行的这段时间,使用非公平获取锁方式就能很好地利用这段时间。

上面说了非公平锁的好处,那么是不是什么情况下都使用非公平锁呢?

不是的,当线程持有锁的时间相对较长或者说请求锁的平均时间较长的情况下,那么使用公平锁比较好。

因为我们上面说的是要利用队列中的等待线程唤醒但是还没获取锁的这段时间。但是在上面说的持有锁或请求锁时间相对较长,那么这种在线程正在唤醒中,还没有得到锁这种情况不太容易出现。

锁被占用的时间较长,新来的线程大多数情况下也是请求不到的,那么还是直接入队列比较好,避免了多个新来的线程的无用竞争。

在synchronized和ReentrantLock之间进行选择

这里摘抄自《Java并发编程实践》

在内部锁不能满足使用时,ReentrantLock才被作为更高级的工具。当你需要以下高级特性时,才应该使用:可定时的、可轮询的与可中断的锁获取操作,公平队列,或者非块结构的锁。否则,请使用synchronized。

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

推荐阅读更多精彩内容