Semaphore源码分析

阅读本篇文章基础前提是对AQS有一定认识,如果看过前面的ReentranLock和CountDownLatch源码分析,看这篇文章就会轻松无比,且本文仅对最基本的acquire()方法和release()方法进行源码分析,其他版本获取、释放信号方法实现原理基本一致。

概述

Semaphore的概念类比操作系统的信号量,当然本身人家就叫semaphore。可以理解成Semaphore代表一组资源个数,使用的时候要调用acquire()方法来获取,释放时调用release()方法。资源被获取完了后,调用acquire()方法会阻塞至此方法调用处。

下面抄一段《java并发编程实战》里面的使用实例,看一下用法。

下面这段代码实现了一个有界的HashSet,如果添加元素超过容量会被阻塞,直到容器中有元素被取出而空出了位置。

public class BoundedHashSet<T> {
    private final Set<T> set;
    private final Semaphore sem;

    public BoundedHashSet(int bound){
        set = Collections.synchronizedSet(new HashSet<T>());
        sem = new Semaphore(bound);
    }

    public boolean add(T o) throws InterruptedException {
        sem.acquire(); //每添加一个元素,acquire()一次获取一个信号,信号取完后会阻塞至此,直到调用Semaphore.release()释放一个信号
        boolean wasAdded = false;
        try {
            wasAdded = set.add(o);
            return wasAdded;
        }finally {
            if (!wasAdded)
                sem.release();
        }
    }

    public boolean remove(Object o){
        boolean wasRemoved = set.remove(o);
        if(wasRemoved)
            sem.release(); //每取出一个元素,release()一次释放一个信号
        return wasRemoved;
    }
}

Semaphore和concurrent包里的ReentranLock、CountDownLatch等都是以AQS为基础来实现的。使用内部类Sync继承AQS抽象类,并且提供继承Sync的FairSync和NonfairSync供实例化Semaphore时选择使用公平方式还是非公平方式获取信号。Semaphore和CountDownLatch一样也是使用AQS的共享模式

成员

1、Sync

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;

    Sync(int permits) {
        setState(permits);
    }

    final int getPermits() {
        return getState();
    }

    /**
     * 尝试以非公平方式获取acquires个信号,非公平方式即不管等待队列中等待已久的其他线程,直接尝试使用CAS获取,获取失败会被加入等待队列
     * 方法写到一个死循环中,只有获取成功或者"当前信号数 < 本次需要获取信号数"跳出循环
     * 获取成功返回正值,失败返回负值
     * <p>方法除了cas外没有用到任何同步的操作,原因在与for(;;)的骚操作,获取当前state值与cas都包在这个死循环
     * 代码块里,如果本线程由于其他线程竞争关系导致cas失败,会再次重新执行在当前的state值减去acquire后执行cas
     * </p>
     */
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                return remaining;
        }
    }

    /**
     * 尝试释放release个信号
     * 释放成功返回true,溢出抛Error
     * 方法除了cas外没有用到任何同步的操作,原因在与for(;;)的骚操作,获取当前state值与cas都包在这个死循环
     * 代码块里,如果本线程由于其他线程竞争关系导致cas失败,会再次重新执行在当前的state值减去acquire后执行cas
     */
    protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            int current = getState();
            int next = current + releases;
            if (next < current) // overflow 溢出抛Error
                throw new Error("Maximum permit count exceeded");
            if (compareAndSetState(current, next))
                return true;
        }
    }

    /**
     * 用于后续调整减少信号量
     * @param reductions 减少的个数
     */
    final void reducePermits(int reductions) {
        for (;;) {
            int current = getState();
            int next = current - reductions;
            if (next > current) // underflow 溢出抛Error
                throw new Error("Permit count underflow");
            if (compareAndSetState(current, next))
                return;
        }
    }

    /**
     * 将信号量置0
     * @return 0
     */
    final int drainPermits() {
        for (;;) {
            int current = getState();
            if (current == 0 || compareAndSetState(current, 0))
                return current;
        }
    }
}

2、NonfairSync和FairSync

真正使用的NonfairSyncFairSync实现如下:

/**
 * 非公平版本
 */
static final class NonfairSync extends Semaphore.Sync {
    private static final long serialVersionUID = -2694183684443567898L;

    NonfairSync(int permits) {
        super(permits);
    }

    /**
     * <p>非公平方式获取信号,非公平方式即不管等待队列中等待已久的其他线程,直
     * 接尝试使用CAS获取,获取失败会被加入等待队列</p>
     * 实例化AQS中对应方法,直接调用的Sync的nonfairTryAcquireShared()方法
     * @param acquires 获取信号量个数
     * @return 成功返回正值(当前总信号量),失败返回一个负值
     */
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}

/**
 * 公平版本
 */
static final class FairSync extends Semaphore.Sync {
    private static final long serialVersionUID = 2014338818796000944L;

    FairSync(int permits) {
        super(permits);
    }

    /**
     * 公平方式获取信号,如果等待队列中有线程在等待信号,直接返回负值表示失败
     * 实例化AQS中对应方法
     * @param acquires 获取信号量个数
     * @return 成功返回正值(当前总信号量),失败返回一个负值
     */
    protected int tryAcquireShared(int acquires) {
        for (;;) {
            //如果等待队列中有线程在等待信号,直接返回负值表示失败,自己会被加入等待队列
            if (hasQueuedPredecessors())
                return -1;
            //等待队列中没有等待信号的线程,大家公平竞争,谁cas成功谁获取信号,失败的下次循环重新获取
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 || //信号量此时不能满足需求,返回负值表示失败,自己会被加入等待队列
                    compareAndSetState(available, remaining))
                return remaining;
        }
    }
}

源码分析

构造函数

提供两个版本构造函数,一个只传入初始信号量大小,另一个传入初始信号量大小和所选Semaphore模式为公平或非公平的boolean值来创建Semaphore。

只传入初始信号量大小的版本默认使用非公平模式,非公平模式可以从宏观上提供更高的效率,能一定程度避免了线程的阻塞唤醒代价。

public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

传入初始信号量大小和所选Semaphore模式为公平或非公平。可以选择使用公平模式还是非公平模式来初始化Semaphore

public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

获取信号量

1、acquire()方法

从源码可以看到直接调用父类AQS实现的acquireSharedInterruptibly(1)方法,共享模式下获取一个信号。

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

acquireSharedInterruptibly()方法主要步骤为:

  1. 判断本线程是否已经被中断,如果是直接抛中断异常
  2. 尝试调用tryAcquireShared()方法获取信号
  3. 尝试获取失败后,调用doAcquireSharedInterruptibly()方法获取信号

实现如下:

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0) //FairSync或NofairSync实例化的tryAcquireShared()方法,尝试获取arg个信号,请回头看前文实现分析
        doAcquireSharedInterruptibly(arg);//尝试失败后调用该方法获取arg个信号
}

doAcquireSharedInterruptibly()方法我们在CountDownLatch源码分析中做过详细解释,这里直接抄过来。这个方法处理步骤描述如下:

  1. 为当前线程创建一个共享模式节点链接到等待链表尾部
  2. 自旋的等待获取锁,自旋到一定条件会被阻塞,被阻塞后就等待被释放信号的线程唤醒重新自旋的获取锁。
private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
    // 将当前线程以共享模式创建一个等待节点,并加入等待队列尾部
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true; // 用于记录是否在调用tryAcquireShared()方法是否抛出异常
    try {
        for (;;) {
            /**
             * 自旋的等待当前线程等待节点的前序节点为head节点;结束自旋的方式有两个,1、当前节点前驱节点就是head,2、调用LockSupport.park()中断循环,等待被unpark()唤醒再次进入循环
             * 如果当前等待线程节点前序节点为head节点时,使用tryAcquireShared()方法尝试获取锁,
             * 获取成功将当前线程等待节点设置为head节点,并且如果后继节点为共享状态节点,则唤醒它们,让它们自旋的等待获取锁
             */
            final Node p = node.predecessor();
            // 如果当前线程等待节点前驱节点为head节点
            if (p == head) {
                int r = tryAcquireShared(arg); // 尝试获取锁
                if (r >= 0) { // 获取成功
                    setHeadAndPropagate(node, r); // 将当前线程等待节点设置为head节点,并且如果后继节点为共享状态节点,则唤醒它们,让它们自旋的等待获取锁
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) && // 判断当前线程是否应该被park()中断于此
                    parkAndCheckInterrupt()) // 调用LockSupport.park(this)阻塞当前获取锁的线程,并且调用Thread.interrupted()返回线程是否被中断了
                throw new InterruptedException(); // 如果线程被中断了抛出InterruptedException异常
        }
    } finally {
        // 如果AQS子类实现的tryAcquireShared()方法抛异常了,failed不会被置false,这时就执行cancelAcquire方法清理现场
        if (failed)
            cancelAcquire(node);
    }
}

2、释放信号方法

使用release()方法来释放一个信号,方法就一行,就是直接调用AQS实现的releaseShared(1)方法来释放一个信号。

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

releaseShared()方法在CountDownLatch源码分析中也做了详细解释,这里直接抄过来。

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) { // 调用FairSyn或NofairSync重写的tryReleaseShared()方法尝试把代表信号的state减1, state通过减1成功后需要执行doReleaseShared()方法(即表达信号刚刚被当前线程释放了)
        doReleaseShared(); // 传递的唤醒后面共享模式等待节点,让它们接着在doAcquireSharedInterruptibly()方法自旋的获取信号
        return true;
    }
    return false;
}

tryReleaseShared(int releases) 方法实现请查看上文中FairSync和NofairSync中的实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值