乐趣区

关于java:后端面经Java公平锁和加锁流程

1. 偏心锁和非偏心锁

1.1 基本概念

  • 偏心锁:线程依照到来的先后顺序,排队期待应用资源。
  • 非偏心锁:线程不肯定依照先后顺序应用资源,而是可能呈现“插队”的状况。

拿游乐场期待娱乐我的项目举例,一般游客只能依照先后顺序排队期待应用游乐设施,这就是 偏心锁 ,然而一般入口加上优速通,显然 VIP 游客能够快人一步,这就有点 非偏心锁 的意思了。

1.2 ReentrantLock 的偏心锁和非偏心锁

在《【后端面经 -Java】Synchronize 和 ReentrantLock 区别》这篇博客中,咱们比照过 synchronizedReentrantLock的区别,其中 synchronized 是一种 非偏心锁 ,而ReentrantLock 默认是 非偏心锁 ,然而也可设置为 偏心锁
具体设置形式如下:

// 生成一个偏心锁
static Lock lock = new ReentrantLock(true);
// 生成一个非偏心锁
static Lock lock = new ReentrantLock(false);
static Lock lock = new ReentrantLock();// 默认参数就是 false,这种写法也可

通过更改构造函数中的参数,咱们能够批改 ReentrantLock 的锁类型,true示意偏心锁,false示意非偏心锁。构造函数具体代码如下所示:

public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();//FairSync 示意偏心锁,NonfairSync 示意非偏心锁}

2. 加锁流程

2.1 ReentrantLock 和 AQS 的关系

在【后端面经 -Java】AQS 详解这篇博客中,咱们具体解说了 AQS 的原理,其中提到了

AQS 定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如罕用的 ReentrantLock。

可就是说,ReentrantLock也是通过 AQS 来实现的,而自定义同步锁须要实现 AQS 框架中的 tryAcquire()tryRelease()办法或者 tryAcquireShared()tryReleaseShared()办法。

因而,ReentrantLock的加锁流程咱们可用查看 tryAcquire() 办法理解。

2.2 偏心锁 - 加锁流程

偏心锁的 tryAcquire() 办法源码如下所示:

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;
}

代码流程如下所示:

  • 查看是否有其余线程在期待资源。
  • 如果没有其余线程在期待,查看资源是否可用,如果资源可用,间接获取资源。
  • 如果有其余线程在期待或者资源不可用(正在被应用),线程乖乖排到队尾,并切换为期待唤醒的休眠态。

2.3 非偏心锁 - 加锁流程

非偏心锁的 tryAcquire() 办法源码如下所示:

final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {if (compareAndSetState(0, acquires)) { // 这里只判断了资源是否可用,而没有判断是否有其余线程在期待
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
        throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

偏心锁 相比,非偏心锁 的加锁流程只是少了对 其余线程是否期待 的判断,因而,非偏心锁 的加锁流程如下所示:

  • 查看资源是否可用,如果资源可用,间接获取资源。
  • 如果资源不可用,不须要管是否有线程在排队,还是排在期待队列队尾。

2.4 加锁流程和性能的关系

偏心锁能保障线程获取资源的公平性,然而性能较低;
而非偏心锁尽管无奈保障公平性,然而性能更高,因而在大多数状况下,咱们都会应用非偏心锁。

  • 对于“偏心锁性能低,非偏心锁性能高”的解释
    了解这个论断,咱们须要晓得偏心锁和非偏心锁申请资源的流程。

    • 对于偏心锁,当一个线程创立之后,它会看是否有其余线程在期待资源,也就是看看 排队队伍外面有没有人,如果有其余线程在期待,它就乖乖排到队尾,并切换为期待唤醒的休眠态。而如果没有其余线程在期待,它就间接获取资源。
    • 对于非偏心锁,当一个线程创立之后,它会间接试着去获取资源,不论队伍里有没有人,如果这个时候正好资源被开释,那么非偏心锁因为是抢着应用资源的,提出资源申请比首个在队列中期待的线程要早,因而资源会间接给它。如果获取资源失败,它才会乖乖去队尾排队期待。

对于线程状态的切换,从休眠态到就绪态,这部分是须要工夫进行上下文切换的,因而,偏心锁每次都间接进入休眠态期待被唤醒,这自身就是很消耗工夫的事件,因而咱们才说 偏心锁性能低,非偏心锁性能高

(非偏心锁尽管不偏心,然而性能高,真的是很讥刺的一种状况呐。)

3. 面试问题模仿

Q:偏心锁是什么?加锁流程是什么?
A:偏心锁是指在资源获取过程中,线程依照到来程序排队应用资源的一种锁机制,而非偏心锁则可能呈现不按程序的随机获取状况。
偏心锁的加锁流程体现在 tryAcquire() 源码局部,当一个线程节点创立之后,它会判断以后是否有其余线程在期待以及资源是否可用,如果两个条件都满足,它则获取资源,如果不满足,它则乖乖排到队尾,期待被唤醒。

参考文献

  1. 面试突击 46:偏心锁和非偏心锁有什么区别?
  2. 讲一讲偏心锁和非偏心锁,为什么要“非偏心”?
  3. ReentrantLock 加锁过程源码详解
退出移动版