Semaphore源码简析

之间已经讲过了CountdownLatch和CyclicBarrier,现在来讲讲Semaphore,Semaphore信号量的作用是指定个数的线程通过之后,主线程就能继续执行了,老规矩先上demo

public class SemaphoreTest {
    public static void main(String[] args) throws Exception {
        Semaphore semaphore = new Semaphore(0);

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    System.out.println("线程1执行完毕");
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    System.out.println("线程2执行完毕");
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

        semaphore.acquire(1);
        System.out.println("一个线程执行完毕");
    }
}

输出

线程2执行完毕
一个线程执行完毕
线程1执行完毕

在demo中有三个地方使用到了Semaphore,一个是构造方法,一个是semaphore.acquire(1),一个是semaphore.release()
这三个方法,acquire方法就可以指定调用多少次release方法,当调用了指定数量的release方法之后,主线程才能继续往下执行,而且之后的线程也不会继续被阻塞,接下来分别分析一下源码中的这几个方法

首先是构造方法

// 这里的构造函数可以传两个参数,第二个参数指定非公平还是公平,不传的话默认是非公平的
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;

    NonfairSync(int permits) {
        // Sync(int permits) {
        //     setState(permits);
        // } 
        // 父类的方法我贴在上面了,这里Sync继承自AQS,将permits赋值给state,state在AQS中就是加锁的次数
        super(permits);
    }

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

可以看到构造函数其实就是给state变量赋了个值,在demo的代码中,我们传的是0,所以这里state = 0
我们接着看Semaphore的acquire方法

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

public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
   	// tryAcquireShared其实调用的是Semaphore中的nonfairTryAcquireShared方法,具体在下面分析了
    if (tryAcquireShared(arg) < 0)
    	// 这个方法我们放到后面分析,这里我们先知道会在这里将线程挂起即可
        doAcquireSharedInterruptibly(arg);
}

final int nonfairTryAcquireShared(int acquires) {
    for (;;) {
    	// 在上面的构造函数中,我们设置了state = 0,所以这里的available = 0
        int available = getState();
        // remaining = 0 - 1 = -1
        int remaining = available - acquires;
        // remaining < 0则直接返回remaining,这里直接返回-1
        if (remaining < 0 ||
            compareAndSetState(available, remaining))
            return remaining;
    }
}

之前说过了,acquire方法(假设参数为n)需要调用acquire方法参数的个数的release方法才能继续往下执行,demo中的代码,当两个工作线程还没执行的时候,主线程执行到acquire方法时,此时还没有其他线程调用release方法的时候,主线程被挂起来了

接下来是Semaphore的release方法

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

public final boolean releaseShared(int arg) {
	// 详细分析在下面,最后实现的效果就是 state += arg
    if (tryReleaseShared(arg)) {
        // 唤醒阻塞的线程
        doReleaseShared();
        return true;
    }
    return false;
}

protected final boolean tryReleaseShared(int releases) {
    for (;;) {
        // 获取state,此时state还是0
        int current = getState();
        // releases入参为1,next = 1
        int next = current + releases;
        if (next < current) // overflow
            throw new Error("Maximum permit count exceeded");
        // cas操作,将state设置为了next的值,此时就相当于state += releases
        if (compareAndSetState(current, next))
            return true;
    }
}

分析完了release方法,结合之前的注释,我们知道了acquire方法在没有调用release方法的时候,其实主线程会被挂起,当调用了release方法之后,就会尝试唤醒阻塞的线程
接下来我们再回过头来看看线程被挂起的那段逻辑,也就是之前acquire方法里面没有分析的doAcquireSharedInterruptibly方法

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                // 还是执行上面分析过的nonfairTryAcquireShared方法
                // 这里执行的核心就是用 state - arg(这个arg其实就是acquire方法的参数),然后返回结果
                int r = tryAcquireShared(arg);
                // r >= 0时进入,将所有阻塞住的线程唤醒继续执行
                // 满足semaphore需要通过的线程数时,之后的线程就可以直接执行,不会被阻塞了
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            // 最开始执行acquire方法时,就会进入下面的逻辑判断,使用LockSupport.park方法挂起当前线程
            // 如果在这被阻塞的线程被唤醒了,那么就会继续进入循环,重新判断Semaphore是否还会被阻塞
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

其实我也不知道我上面写了些什么玩意儿,如果看不懂,那就直接记结论吧

做个比喻来说明一下Semaphore的作用,有点类似于生产者消费者,Semaphore初始化的时候,相当于初始化库存量,当使用acquire方法的时候,就是来消费商品,当库存不足时,就会被阻塞住,而release方法,就是补充库存,当库存满足消费量时,才能继续往下执行

还有一种常见的用法就是在初始化构造的时候指定Semaphore的值,可以认为是一个计数器,然后调用acquire方法就相当于计数器减1,当计数器减为0之后,那么其他的线程调用acquire方法就会被挂起,直到一个调用acquire方法的线程执行release方法,被挂起的线程才能继续尝试调用acquire方法。其实可以类比一下线程池的用法,都可以限制工作的线程数量,hystrix的限流降级中就支持线程池和Semaphore两种方式

最后我们来梳理一下demo的运行流程吧,首先主线程构造了一个Semaphore,参数传的是0(相当于库存为0),接着开启两个工作线程,并且休眠一段时间,主线程执行了acquire方法,但是此时没有线程执行release方法,所以主线程被阻塞了(想消费,但是没有库存,只能等待补货了),隔了1s之后,线程2执行release方法(补货),输出 线程2执行完毕,那么此时主线程就会从阻塞中被唤醒(有足够的货物能够消费了), 接着输出了 一个线程执行完毕,最后在系统执行2s之后,线程1执行release方法(继续补货),此时执行输出 线程1执行完毕

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值