JUC源码解析七:Semaphore源码解析

一 前言

Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。

使用Semaphore可以同时允许指定的线程数量同时执行,超过该数量的线程必须等待直到有线程执行完其任务。

特别说明的是信号量只是在信号不够的时候挂起线程,但是并不能保证信号量足够的时候获取对象和返还对象是线程安全的,所以仍需要锁Lock来保证并发的正确性。

将信号量的许可值初始化为 1,使得它在使用时最多只有一个可用的许可,此时就相当于一个互斥锁。这通常也称为二进制信号量,因为它只能有两种状态:一个可用的许可,或零个可用的许可。按此方式使用时,可以实现互斥锁的功能,但也与真正的锁不同,该方式可以由线程释放“锁”,而不是由所有者(因为信号量没有所有权的概念)。

下面讲下基本的构造函数:

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

默认使用了非公平锁,并且后续调用了acquire(permits)方法。

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

可以对选择非公平锁与公平锁,都是调用各自的acquire(permits)方法。

二 acquire()

先从最基本的开始:

此处有两种acquire()方法:

	public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
	public void acquire(int permits) throws InterruptedException {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireSharedInterruptibly(permits);
    }

很明显可以看出事每次争夺的信号量的state不同,默认为1。

此处使用了AQS内的一个方法:

	// 以共享锁的模式竞争锁,线程被中断时抛出异常
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 尝试竞争共享锁,返回-1时即竞争失败
        if (tryAcquireShared(arg) < 0)
            // 以共享可中断模式获取。循环CAS直到成功获取或线程被中断
            doAcquireSharedInterruptibly(arg);
    }

关于该方法会在线程被中断的时候抛出异常,这里先说个题外话:Semaphore内部实现了一个方法,允许在忽略线程的中断操作:

	public void acquireUninterruptibly(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.acquireShared(permits);
    }
	public void acquireUninterruptibly() {
        sync.acquireShared(1);
    }

里面的acquireShared(permits)实现则是:

	public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

很容易就能看出差别了,相对于acquireSharedInterruptibly(int arg)来说减少了对线程中断的判断,并且在后面的尝试获取中是不会出现线程中断的判断的,因此这个可以做到忽略线程的中断。

那么让我们回到原来的位置:

很多继承AQS的类都会直接调用这个方法来争夺共享锁,不过一般会重写里面的tryAcquireShared(arg)方法来修改竞争共享锁的条件,下面让我们继续看Semaphore重写的tryAcquireShared(arg)方法:

在公平锁内:

		protected int tryAcquireShared(int acquires) {
            for (;;) {
                // 当前线程不是Sync头结点的下一个结点,即当前线程的Sync队列前面仍有结点
                if (hasQueuedPredecessors())
                    return -1;
                // 减去对应的state,返回剩余的状态值,若大于0即还可以进入锁
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

非公平锁会简单点:

		protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
		final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

上述两个方法都是判断是当前Semaphore剩余的状态位,若当前的状态位小于0则无法获得锁。但是不同的是公平锁维护了一个Sync队列,每次竞争都需要判断Sync头结点是否为空或者当前线程是否是Sync队列头结点的下一节点。若竞争失败则将当前线程阻塞。

三 tryAcquire()

在Semaphore中tryAcquire()有两个方法,默认以及限时:

	public boolean tryAcquire() {
        return sync.nonfairTryAcquireShared(1) >= 0;
    }

尝试竞争锁都用非公平竞争的方式去竞争。

	public boolean tryAcquire(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

由于Sync都没有实现tryAcquireSharedNanos(1, unit.toNanos(timeout))方法,因此此处调用的是AQS中的方法。

// 以共享锁的模式竞争锁,线程被中断或超时时抛出异常
    public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 尝试竞争共享锁,返回1即竞争成功,竞争失败时循环CAS直到成功获取或抛出异常
        return tryAcquireShared(arg) >= 0 ||
                doAcquireSharedNanos(arg, nanosTimeout);
    }

跟上述一样,有改变的仍然tryAcquireShared(arg)方法,即对是否成功获取锁的判定方法进行了重写,其他的跟原来无异。

四 release()

从Semaphore的功能以及tryAcquire()的内部实现我们也许可以猜到release()的功能:释放锁并使状态位加上指定的值。下面让我们来看一下:

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

下面同样使用了AQS中的方法:

	// 以共享模式发布。如果tryReleaseShared返回true,则通过解除阻塞一个或多个线程来实现。
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

不同的是Semaphore中重写了tryReleaseShared(arg)的操作。此处公平锁与非公平锁一样的实现方式:

		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");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

尝试减去状态位成功后就设置当前结点的等待状态并唤醒后继结点。

五 permit操作

    // 返回此信号量中可用的当前许可数。
    public int availablePermits() {
        return sync.getPermits();
    }
	final int getPermits() {
            return getState();
   	}

得到还能够进入的状态位。

	// 获取并返回所有可立即获得的许可。
    public int drainPermits() {
        return sync.drainPermits();
    }
	final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }

从代码实现上看应该是返回还剩下的可获得的许可并且状态位置为0,即不允许其他线程进入信号量。

	// 减少信号量的许可数量
    protected void reducePermits(int reduction) {
        if (reduction < 0) throw new IllegalArgumentException();
        sync.reducePermits(reduction);
    }
	final void reducePermits(int reductions) {
            for (;;) {
                int current = getState();
                int next = current - reductions;
                if (next > current) // underflow
                    throw new Error("Permit count underflow");
                if (compareAndSetState(current, next))
                    return;
            }
        }

大家可以看到reducePermits(int reductions)的代码实现跟Sycn内部中实现的锁的争夺操作有一些不同,之所以不跟公平锁、非公平锁一起实现应该相对于公平锁少了唤醒线程的步骤。虽然跟非公平锁的尝试锁争夺操作有极高的相似性,可能为了容易理解写了一个新的方法。

六 最后

上面就是Semaphore这个类的核心操作了。

总结一下:

Semaphore中很多地方都直接调用了AQS中的方法实现,而不同于AQS的地方则是,其实现了自己的状态位判断即处理体系(AQS设计得很好,对于不同的需求只需要让他们覆写Sycn内部类里面的方法就可以制定自定义的操作)。

Semaphore是一个计数器,一开始初始化的值即是计数器state也叫做许可permit。每次请求一个许可成功的时候都会使得state状态位减去指定的数量,这样也代表着可以放行的线程减少了。同时每次释放一个许可成功即释放锁成功的时候都会使得state状态位增加指定的数量。注意的是,Semaphore并不是可重入的,当许可为0时已进入Semaphore的线程并不能重复进入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值