java Semaphore共享锁实现原理解析

 更新时间:2023年01月09日 09:29:03   作者:小海编码日记  
这篇文章主要为大家介绍了Semaphore共享锁实现原理解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

正文

在线程间通信方式中,我们了解到可以使用Semaphore信号量来实现线程间通信,Semaphore支持公平锁和非公平锁,Semaphore底层是通过共享锁来实现的,其支持两种构造函数,如下所示:

 // 默认使用非公平锁实现
 public Semaphore(int permits) {
     sync = new NonfairSync(permits);
 }
 ​
 public Semaphore(int permits, boolean fair) {
     sync = fair ? new FairSync(permits) : new NonfairSync(permits);
 }

Semaphore提供的常用函数如下所示:

函数名说明备注
acquire获取锁/
release释放锁/

下面我们来看下Semaphore内部的实现原理

Semaphore内部类及继承关系

可以看出Semaphore和ReentrantLock实现原理基本一致,包含NonfairSync和FairSync两个内部类,这两个内部类的父类均为AQS,不妨大胆猜测Semaphore也是依赖AQS实现的,接下来我们一起来看下Semaphore获取和释放锁的流程。

Semaphore.acquire流程分析(以非公平锁为例)

从上图可以看出,针对阻塞线程的部分实现,和ReentrantLock基本一致,我们不做赘述,主要来看下前半部分的源码实现:

 // Semaphore.java
 public void acquire() throws InterruptedException {
     sync.acquireSharedInterruptibly(1);
 }
 // AbstractQueuedSynchronizer.java
 public final void acquireSharedInterruptibly(int arg)
         throws InterruptedException {
     // 如果线程是中断状态,抛出异常
     if (Thread.interrupted())
         throw new InterruptedException();
     // 尝试获取共享资源
     if (tryAcquireShared(arg) < 0)
         doAcquireSharedInterruptibly(arg);
 }

从源码可以看出acquire主要依赖于tryAcquireShared和doAcquireSharedInterruptibly,接下来我们来分别看下这两块的代码

tryAcquireShared

 // NonfairSync
 protected int tryAcquireShared(int acquires) {
     return nonfairTryAcquireShared(acquires);
 }
 // Sync
 final int nonfairTryAcquireShared(int acquires) {
     for (;;) {
         int available = getState();
         int remaining = available - acquires;
         if (remaining < 0 ||
             compareAndSetState(available, remaining))
             return remaining;
     }
 }
 // AbstractQueuedSynchronizer.java
 protected final boolean compareAndSetState(int expect, int update) {
     // See below for intrinsics setup to support this
     return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
 }

从代码可以看出这里主要是根据申请的许可证数量,比较时否有许可证数量,如果可用许可证数量小于0,则直接返回,如果大于0,则通过CAS将state设置为可用许可证数量。

doAcquireSharedInterruptibly

当tryAcquireShared中返回的可用许可证数量小于0时,执行doAcquireSharedInterruptibly流程,代码如下:

 // AbstractQueuedSynchronizer.java
 // 在队尾新建Node对象并添加
 private Node addWaiter(Node mode) {
     Node node = new Node(Thread.currentThread(), mode);
     // Try the fast path of enq; backup to full enq on failure
     Node pred = tail;
     if (pred != null) {
         node.prev = pred;
         if (compareAndSetTail(pred, node)) {
             pred.next = node;
             return node;
         }
     }
     enq(node);
     return node;
 }
 // AbstractQueuedSynchronizer.java
 private void doAcquireSharedInterruptibly(int arg)
     throws InterruptedException {
     // 将当前线程添加到等待队列
     final Node node = addWaiter(Node.SHARED);
     boolean failed = true;
     try {
         // for循环自旋
         for (;;) {
             // 获取node的前一个节点
             final Node p = node.predecessor();
             // 如果前一个节点是头节点
             if (p == head) {
                 // 尝试获取锁
                 int r = tryAcquireShared(arg);
                 if (r >= 0) {
                     // 获取锁成功,更新node信息设置为头节点,并通知其他节点
                     setHeadAndPropagate(node, r);
                     p.next = null; // help GC
                     failed = false;
                     return;
                 }
             }
             // 判断是否需要阻塞线程,设置waitStatus并阻塞
             if (shouldParkAfterFailedAcquire(p, node) &&
                 parkAndCheckInterrupt())
                 throw new InterruptedException();
         }
     } finally {
         if (failed)
             cancelAcquire(node);
     }
 }
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            doReleaseShared();
    }
}

执行setHeadAndPropagate的主要目的在于,这里能获取到说明在该线程自旋过程中有线程释放了许可证,释放的许可证数量有可能还有剩余,所以传递给其他节点的线程,唤醒其他阻塞状态的线程也尝试去获取许可证。

Semaphore.release流程分析(以非公平锁为例)

Semaphore.release流程相对而言,就比较简单,将release传递到AQS内部通过CAS更新许可证数量信息,更新完成后,遍历队列中Node节点,将Node waitStatus设置为0,并对对应线程执行unpark,相关代码如下:

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        int current = getState();
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        // 通过CAS更新许可证数量
        if (compareAndSetState(current, next))
            return true;
    }
}
private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}
// 许可证数量更新完成后,调用该方法唤醒线程
private void doReleaseShared() {
    // 自旋
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 唤醒后继节点线程抢占许可证
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

综上,我们分析了Smaphore非公平锁的实现,感兴趣的可以分析下公平锁的实现,其本质区别在于在tryAcquireShared中只有当等待队列为空时,才会去尝试更新剩余许可证数量

以上就是Semaphore共享锁实现原理解析的详细内容,更多关于Semaphore共享锁的资料请关注脚本之家其它相关文章!

相关文章

  • Java Integer如何获取第一位和最后一位,并截取

    Java Integer如何获取第一位和最后一位,并截取

    这篇文章主要介绍了Java Integer如何获取第一位和最后一位并截取,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-01-01
  • java的主要特性学习总结

    java的主要特性学习总结

    在本篇文章里小编给大家分享了一篇关于java的主要特性学习总结内容,有兴趣的朋友们可以参考下。
    2020-05-05
  • Mybatis通用Mapper(tk.mybatis)的使用

    Mybatis通用Mapper(tk.mybatis)的使用

    本文主要介绍了Mybatis通用Mapper(tk.mybatis)的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • spring boot security 没有合适的构造器问题

    spring boot security 没有合适的构造器问题

    这篇文章主要介绍了spring boot security 没有合适的构造器问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12
  • NIO深入理解FileChannel使用方法原理

    NIO深入理解FileChannel使用方法原理

    这篇文章主要为大家介绍了NIO深入理解FileChannel的源码示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-05-05
  • Java使用递归法解决汉诺塔问题的代码示例

    Java使用递归法解决汉诺塔问题的代码示例

    这篇文章主要介绍了Java使用递归法解决汉诺塔问题的代码示例,汉诺塔问题是使用递归解决问题的经典范例,用到的算法非常简单,需要的朋友可以参考下
    2016-04-04
  • SpringBoot集成drools的实现示例

    SpringBoot集成drools的实现示例

    本文主要介绍了SpringBoot集成drools的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • Java OCR tesseract 图像智能文字字符识别技术实例代码

    Java OCR tesseract 图像智能文字字符识别技术实例代码

    这篇文章主要介绍了Java OCR tesseract 图像智能文字字符识别技术实例代码,非常具有实用价值,需要的朋友可以参考下
    2017-06-06
  • SpringBoot+RabbitMq具体使用的几种姿势

    SpringBoot+RabbitMq具体使用的几种姿势

    这篇文章主要介绍了SpringBoot+RabbitMq具体使用的几种姿势,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-05-05
  • Java流程控制语句最全汇总(中篇)

    Java流程控制语句最全汇总(中篇)

    这篇文章主要介绍了Java流程控制语句最全汇总(中篇),本文章内容详细,通过案例可以更好的理解数组的相关知识,本模块分为了三部分,本次为中篇,需要的朋友可以参考下
    2023-01-01

最新评论