Java并发编程之Semaphore源码分析

原创
2019/01/19 20:51
阅读数 249

Semaphore介绍

Semaphore是JDK1.5提供允许一组拿到许可证的线程访问共享资源,并禁止其他拿不到许可证的线程访问共享资源工具。Semaphore一般用来做系统的限流。

特点

Semaphore和ReentrantLock功能很类似都是限制线程访问共享资源而且都有公平锁和非公平锁模式。不同点如下表格:

Semaphore ReentrantLock
允许线程访问共享资源个数 可配多个 1个
可重入

Semaphore原理分析

Semaphore的实现是ReentrantLock和CyclicBarrier的结合体,很多源码跟ReentrantLock和CyclicBarrier一样。

acquire()源码分析

acquire()源码如下

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

acquire()是直接调用了AQS里的acquireSharedInterruptibly(1)方法我们来看下acquireSharedInterruptibly(1)方法

    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
	//判断当前线程是否被中断,中断的话抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
	//判断当前计数器是否为0是的话返回1否则返回-1
        if (tryAcquireShared(arg) < 0)
	//加入到同步阻塞队列等待被唤醒
            doAcquireSharedInterruptibly(arg);
    }

先判断当前线程是否被中断,中断的话抛出异常。然后调用tryAcquireShared(arg)减许可证并返回剩余许可证,这里如果许可证为0了就表示许可证已经用完需要进行阻塞等待,否则获取到许可证,则调用 doAcquireSharedInterruptibly(arg)加入到同步阻塞队列等待被唤醒。

tryAcquireShared(arg)的实现分公平模式和非公平模式,先看下默认非公平模式的实现:

protected int tryAcquireShared(int acquires) {
     return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
			//获取当前许可证
                int available = getState();
			//减许可证
                int remaining = available - acquires;
			//如果小于0直接返回否则CAS替换剩余值
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

实现很简单就是减掉一个许可证,并返回剩余许可证。

再看下公平模式的实现:

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

该方法跟非公平锁基本都一样,只是在获取锁的时候加了hasQueuedPredecessors()判断,这个方法主要判断了当前线程是否在头节点的下个节点,这样保证了获取锁的顺序性。

doAcquireSharedInterruptibly(arg)方法在之前文章已经讲过,这里不再累述链接

release()源码分析

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

release()是直接调用了AQS里的releaseShared(1)方法我们来看下releaseShared(1)方法

releaseShared(1)源码如下

 public final boolean releaseShared(int arg) {
 		//许可证加1并返回是否成功
        if (tryReleaseShared(arg)) {
		//唤醒同步阻塞队列中的头节点的下个节点线程
            doReleaseShared();
            return true;
        }
        return false;
    }

先调用tryReleaseShared(arg)将许可证加1并返回是否成功,如果是调用doReleaseShared()唤醒同步阻塞队列中的头节点的下个节点线程。

下面来看下tryReleaseShared(arg)方法

        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
		//获取当前许可证
                int current = getState();
		//获取许可证加releases
                int next = current + releases;
		//加了以后的值比原来的值小,说明releases传的是负数,直接抛出异常
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
		//CAS替换原来的值
                if (compareAndSetState(current, next))
                    return true;
            }
        }

实现很简单就是增加一个许可证,并用CAS替换掉原来的值,如果失败自旋直至成功。

doReleaseShared()方法在之前文章已经讲过,这里不再累述链接

展开阅读全文
打赏
0
0 收藏
分享
加载中
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部