Java并发源码分析之Semaphore

本文深入探讨了Java并发编程中的AQS(AbstractQueuedSynchronizer)及其应用,如ReentrantLock和Semaphore。Semaphore作为基于AQS的共享锁,允许指定数量的线程同时访问资源。文章详细分析了Semaphore的获取和释放锁的流程,包括公平锁与非公平锁的实现,展示了其在多线程并发控制中的作用。
摘要由CSDN通过智能技术生成

关联文章:
关联文章:
Java并发源码分析之AQS及ReentrantLock
Java并发源码分析之ReentrantReadWriteLock
Java并发源码分析之Condition
Java并发源码分析之CyclicBarrier
Java并发源码分析之CountDownLatch

工作原理概要

AQS是用来构建锁的基础框架,ReentrantLock是基于AQS构建的排它锁,而Semaphore则是基于AQS构建的共享锁。两者对比如下:

对比项ReentrantLockSemaphore
锁的类型排它锁共享锁
state锁的可重入次数锁的剩余个数
是否支持可重入支持支持
是否支持非公平锁支持支持
是否支持公平锁支持支持

类图

Semaphore相关类图
AbstractOwnableSynchronizer:抽象类,定义了独占锁线程,exclusiveOwnerThread
AbstractQueuedSynchronizer:抽象类,继承了AbstractOwnableSynchronizer,里面包含内部类Node
Semaphore:类,里面包含三个内部类Sync、NonfairSync、FairSync
Sync:类,继承了AbstractQueuedSynchronizer,重写了tryReleaseShared方法
NonfairSync:类,继承了Sync,重写了tryAcquireShared
FairSync:类,继承了Sync,重写了tryAcquireShared

原理概述

Semaphore维护了一个同时许可集(官方解释),其实就是表示同一时间可以访问共享资源的线程数量,底层通过state来维护。每当有一个线程申请锁,就将state-1,直至state为0,则新申请锁的线程就放入同步队列中等待。有线程释放锁之后,由头节点来唤醒队列中的线程。
Semaphore同样可以实现排它锁功能,将许可集的值设置为1,则同一时刻只有一个线程获取到锁,也就不实现了排它锁。

主要入口方法

 // 获取共享锁,并响应中断
 acquire()
 // 获取共享锁,不响应中断
 acquireUninterruptibly()
 // 尝试获取共享锁,并响应中断
 tryAcquire()
 // 带有超时时间,尝试获取共享锁,并响应中断
 tryAcquire(long timeout, TimeUnit unit)
 // 是否共享锁
 release()

由于代码都大致相同,这里只解析常用的acquire和release。

Semaphore获取锁流程

构造函数

 // permits,许可集,即共享锁最大个数
 // 默认为非公平锁
 public Semaphore(int permits) {
     sync = new NonfairSync(permits);
 }
 // fair,是否为公平锁
 public Semaphore(int permits, boolean fair) {
     sync = fair ? new FairSync(permits) : new NonfairSync(permits);
 }

1、Semaphore–acquire获取共享锁入口

 public void acquire() throws InterruptedException {
     sync.acquireSharedInterruptibly(1);
 }

2、AQS–acquireSharedInterruptibly响应中断获取共享锁

 public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
     // 当前线程有中断标识,抛出异常
     if (Thread.interrupted())
         throw new InterruptedException();
     // 先尝试获取共享锁,tryAcquireShared>=0,表明获取成功
     if (tryAcquireShared(arg) < 0)
     	 // 获取失败,将线程放入同步队列
         doAcquireSharedInterruptibly(arg);
 }

3、Semaphore–tryAcquireShared尝试共享锁

 // 尝试获取共享锁,非公平锁实现
 protected int tryAcquireShared(int acquires) {
     return nonfairTryAcquireShared(acquires);
 }
 final int nonfairTryAcquireShared(int acquires) {
 	 // 自旋
     for (;;) {
         // 获取剩余的共享锁数量
         int available = getState();
         // 计算获取 acquires个共享锁之后剩余的数量
         int remaining = available - acquires;
         // 如果剩余数量小于0,返回负值,表示尝试获取锁失败
    	 // 通过CAS修改state的值成功,返回0或者正值,获取锁成功
         if (remaining < 0 ||
             compareAndSetState(available, remaining))
             return remaining;
     }
 }

 // 尝试获取共享锁,公平锁实现
 protected int tryAcquireShared(int acquires) {
 	 // 自旋
     for (;;) {
         // 如果队列中有节点并且节点线程不为当前线程,返回-1,表示尝试获取锁失败
         if (hasQueuedPredecessors())
             return -1;
         // 走到这里说明队列里面没有节点或节点为当前线程,逻辑跟上面一致
         int available = getState();
         int remaining = available - acquires;
         if (remaining < 0 ||
             compareAndSetState(available, remaining))
             return remaining;
     }
 }

公平锁实现多了一步先判断同步队列中是否有节点,有节点直接加入到队列尾部。

4、AQS–doAcquireSharedInterruptibly将线程放入同步队列

 private void doAcquireSharedInterruptibly(int arg)
     throws InterruptedException {
     // 将当前线程封装成共享节点,并加入同步队列
     final Node node = addWaiter(Node.SHARED);
     boolean failed = true;
     try {
         // 自旋
         for (;;) {
             // 获取node的前驱节点
             final Node p = node.predecessor();
             // 如果前驱节点为头节点
             if (p == head) {
                 // 尝试获取共享锁
                 int r = tryAcquireShared(arg);
                 // r>=0,说明获取共享锁成功
                 if (r >= 0) {
                     setHeadAndPropagate(node, r);
                     p.next = null; // help GC
                     failed = false;
                     return;
                 }
             }
             // 将当前线程挂起,如果线程有中断标志,响应中断抛出异常
             if (shouldParkAfterFailedAcquire(p, node) &&
                 parkAndCheckInterrupt())
                 throw new InterruptedException();
         }
     } finally {
         // 出现异常,取消申请共享锁
         if (failed)
             cancelAcquire(node);
     }
 }

 // 设置头节点,并传播状态
 private void setHeadAndPropagate(Node node, int propagate) {
     Node h = head; 
     // 设置头节点
     setHead(node);
     /* 尝试唤醒队列中的下一个节点,如果满足以下条件:
      * 调用者明确表示传递(propagate > 0)| 头节点的等待状态 < 0(SIGNAL或PROPAGATE)
      * 并且node的后继节点处于共享模式或null
      * 这两项检查会导致不必要的唤醒,只有在多个线程申请锁时才会发生,所以大多数情况下会立刻获取需要的信号
      */
     if (propagate > 0 || h == null || h.waitStatus < 0 ||
         (h = head) == null || h.waitStatus < 0) {
         Node s = node.next;
         if (s == null || s.isShared())
         	 // 唤醒后继节点,因为是共享模式,所有允许多个线程同时获取同步状态
             doReleaseShared();
     }
 }

addWaiter、shouldParkAfterFailedAcquire、parkAndCheckInterrupt这三个方法之前解析过,这里不做赘述。

Semaphore释放锁流程

1、Semaphore–release释放共享锁入口

 public void release() {
     sync.releaseShared(1);
 }

2、AQS–releaseShared释放共享锁

 public final boolean releaseShared(int arg) {
     // 尝试释放共享锁
     if (tryReleaseShared(arg)) {
         doReleaseShared();
         return true;
     }
     return false;
 }

3、Semaphore–tryReleaseShared尝试释放共享锁

 protected final boolean tryReleaseShared(int releases) {
     // 自旋
     for (;;) {
         // 获取当前剩余共享锁数量
         int current = getState();
         // 计算释放 releases个共享锁之后的数量
         int next = current + releases;
         // 释放共享锁之后小于当前数量,抛出异常
         if (next < current) 
             throw new Error("Maximum permit count exceeded");
         // 通过CAS更新state
         if (compareAndSetState(current, next))
             return true;
     }
 }

4、AQS–doReleaseShared唤醒同步队列中的节点

 private void doReleaseShared() {
    // 自旋
    for (;;) {
        Node h = head;
        // 同步队列不为空
        if (h != null && h != tail) {
            // 获取头节点的等待状态
            int ws = h.waitStatus;
            // 如果等待状态为等待唤醒
            if (ws == Node.SIGNAL) {
                // 更新等待状态为0
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;  
                // 唤醒头节点的后继节点          
                unparkSuccessor(h);
            }
            // 如果ws=0,说明有线程释放共享锁之后,更新了头节点的等待状态,并且唤醒了后继节点
            // 但是后继节点还未获取到共享锁,又一个线程释放了共享锁
            // 通过CAS将ws更新成更新成Node.PROPAGATE(-3)
            // 如果后继节点获取共享锁失败(即又有两个线程获取到共享锁),还是会将ws更新成Node.SIGNAL(shouldParkAfterFailedAcquire方法里)
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                
        }
        // 如果头节点发生变化,跳出循环
        // 释放锁之后会有其他线程获取到锁,如果是头节点的后继节点,该节点再获取到锁之后会更新头节点
        if (h == head)                   
            break;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值