【JUC】Semaphore实现和原理

379 阅读4分钟

一、概述

Semaphore信号量,主要功能是限制能够运行的线程量,指定固定数量的许可,只有获取许可的线程可以运行,若没有许可获取则阻塞,直到获取许可。

二、使用示例

public class SemaphoreDemo {

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(1);
        new Thread(new ThreadDemo(semaphore)).start();
        new Thread(new ThreadDemo(semaphore)).start();
    }

    private static class ThreadDemo implements Runnable {

        private Semaphore semaphore;

        ThreadDemo(Semaphore semaphore) {
            this.semaphore = semaphore;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("我获取了许可");
                Thread.sleep(2000);
                System.out.println("我要释放许可了");
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果:

image.png 许可只有1个,只有当获取许可的线程释放之后,另一个线程才能执行

三、源码分析

Semaphore内部持有的同步器同样是通过AQS的共享锁实现,使用state表示许可的数量

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

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

        final int getPermits() {
            return getState();
        }
        /**
        * 这里是非公平的实现方式,仅仅就比公平方式少了一步是否存在排队的判断
        **/
        final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                // 如果剩余许可小于0就直接返回负数,否则通过CAS赋值,自旋直至成功或者许可耗尽
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
        // 释放许可,CAS加自旋
        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;
            }
        }
        // 减少许可数量
        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;
            }
        }
        // 清空许可
        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }

Semaphore中同步器Sync继承AQS实现AQS中的共享模式tryReleaseShared(int releases)方法,同步器只实现了共享锁的释放,并未实现锁的获取tryAcquireShared(int arg)方法;由于公平和非公平获取许可的方式有所不同,所以该方法由对应子类实现。

1、构造函数

    /**
     * Creates a {@code Semaphore} with the given number of
     * permits and nonfair fairness setting.
     *	创建给定许可证数量信号量和非公平设置
     * @param permits the initial number of permits available.
     *        This value may be negative, in which case releases
     *        must occur before any acquires will be granted.
     * 初始许可证数量.
     * 这个值允许为负数,在这种情况下必须在任何获取之前释放
     */
	public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
    /**
    * @param fair {@code true} 保证竞争先进先出
    */
	public Semaphore(int permits, boolean fair) {
	        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

Semaphore中公平和非公平是通过它的两个私有的静态内部类FairSync、NonFairSync实现,他们通过继承Sync并实现共享锁的获取tryAcquireShared(int arg)方法;

2、void acquire()获取许可

这个方法是从信号量获取一个许可,在获取到许可,或线程中断之前,当前线程阻塞;获取许可后立即返回并将许可数减一

	/**
	* 如果没有许可可用,则会休眠,直到发生以下两种情况
	* 1、其他调用release方法释放许可,并且当前线程获取到许可
	* 2、其他线程中断了当前线程
	* 	1)当前线程在进入这个方法时设置了中断标志位
	* 	2)等待许可时发生了中断,则抛出中断异常
	*/
 	public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

这个方法是直接调用AQS的acquireSharedInterruptibly(int ard)方法; 方法实现原理请查看AQS源码

	/**
	* 首先检测是否中断.中断后抛出异常
	* 尝试获取许可,成功退出;失败则进入AQS队列,直至成功获取或中断
	*/
	public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        // 尝试获取锁,返回剩余共享锁的数量;小于0则加入同步队列,自旋
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

tryAcquireShared(arg)则会调用Semaphore中两个同步器的tryAcquireShared实现方法; 如果获取失败则加入队列等待唤醒;

非公平模式的实现

非公平实现都是首先查看是否有可获取的许可,如果有则获取成功,没有则进队列等待;利用此可以提高并发量

	protected int tryAcquireShared(int acquires) {
       return nonfairTryAcquireShared(acquires);
    }

直接调用其父类Sync中非公平共享获取

	final int nonfairTryAcquireShared(int acquires) {
		// 自旋直到无许可或者状态位赋值成功
        for (;;) {
            int available = getState();
            int remaining = available - acquires;
            // 如果小于0则直接返回,否则利用CAS给AQS状态位赋值
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }

通过自旋+CAS来一直尝试获取许可,直到获取成功或者没有许可,返回剩余的许可数

公平模式的实现

公平与非公平的区别在于始终按照AQS队列FIFO的顺序来的

protected int tryAcquireShared(int acquires) {
	//自旋 CAS 实现线程安全
    for (;;) {
    	// 判断是否有前置任务排队
        if (hasQueuedPredecessors())
            return -1;
        int available = getState();
        int remaining = available - acquires;
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

如果等待队列不为空,则直接返回-1。 以上两种模式获取失败后都会调用doAcquireSharedInterruptibly(int arg);自旋等待获取锁

3、void release()

公平和非公平使用相同的释放 释放许可

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

调用AQS中的releaseShared(int arg)

    public final boolean releaseShared(int arg) {
    	// 调用Sync实现的tryReleaseShared
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

tryReleaseShared(int arg)是父类Sync实现。自旋直到释放成功,许可数量+releases

        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;
            }
        }

如果释放许可成功,则调用AQS中的doReleaseShared()方法来唤醒AQS队列中等待的线程