Semaphore源码解析

Semaphore源码解析

简介

Semaphore是常用的同步工具之一,我们设置n,代表同一时间最多只有n个线程在访问
这个工具依赖AQS实现,所以在看源码前推荐先去看AQS的介绍,方便理解

public class SemaphoneTest {
	private Semaphore semaphore = new Semaphore(5);
	private ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(20));

	public void exec() {
		try {
			semaphore.acquire(1);
			System.out.println("执行方法,时间:" + new Date());
			Thread.sleep(1000);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			semaphore.release(1);
		}
	}

	@Test
	public void test() throws InterruptedException {
		while ( true ) {
			Thread.sleep(100);
			executor.execute(() -> exec());
		}
	}
}

当我们的线程进来后,抢占成功的将会继续执行,抢占失败的就会进入同步队列,等待。

下面开始介绍他的源码

静态内部类
	//继承了AQS,主要有两种实现,一种是公平的实现,一种是非公平的实现
	abstract static class Sync extends AbstractQueuedSynchronizer {
        Sync(int permits) {
            setState(permits);
        }

        final int nonfairTryAcquireShared(int acquires) {
            //自旋,然后如果剩余的state<0,直接返回remaining,否则尝试CAS修改state为剩余的
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                        compareAndSetState(available, remaining))
                    return remaining;
            }
        }

        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current)
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }
    }

然后是Sync的两个实现

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

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

        //非公平的获取资源
        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
    }
FairSync
	static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;

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

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

然后是重要属性

重要属性
	//所有的操作都是依靠这个AQS的实现类来进行的
	//有公平和非公平两种,可选
	private final Sync sync;

然后是常用的方法
主要有两个,一个是acquire(),一个是release()

acquire()

	/* **
     * 获取资源
     */
    public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
    public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //如果线程被中断了,抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();

        //尝试去获取n个资源,小于0获取失败
        //为0就不需要加入到阻塞队列
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

上面一段基本没做什么事情,关键在tryAcquireShared()以及doAcquireShareInterruptibly()两个中
如果尝试获取成功,就不需要进入到同步队列中
这边会有两种选择,一种是NonfairSync的实现,一种是FairSync的实现,先看NonFairSync的实现

NonfairSync.tryAcquireShared()

		//非公平的获取资源
        protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
        final int nonfairTryAcquireShared(int acquires) {
            //自旋,然后如果剩余的state<0,直接返回remaining,否则尝试CAS修改state为剩余的
            //这里就是看我们的state在减去我们需要的数据之后,还剩下下多少,会配合前面的<0来看是不是需要入队
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                        compareAndSetState(available, remaining))
                    return remaining;
            }
        }

上面是我们的NonFairSync的流程,下面来看我们的FairSync的流程

FairSync.tryAcquireShared()
	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;
            }
        }

	//其实也就是看头节点后面是不是有元素,并且这个元素不是自己
	public final boolean hasQueuedPredecessors() {
        AbstractQueuedSynchronizer.Node t = tail; // Read fields in reverse initialization order
        AbstractQueuedSynchronizer.Node h = head;
        AbstractQueuedSynchronizer.Node s;
        return h != t &&
                ((s = h.next) == null || s.thread != Thread.currentThread());
    }

其实和非公平的差不多,不过要多了一个判断条件,也就是是不是有已经在同步队列中排队的线程,
因为是公平锁,所以不能插队

然后如果没有成功的获取到我们的state,我们就会尝试入队,

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) {
                    //尝试去获取资源
                    int r = tryAcquireShared(arg);
                    //如果getState为0,说明count为0,等待的线程可以执行了
                    if (r >= 0) {
                        //设置头
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //如果不是头,就会将线程挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

如果没有获取成功就会把当前线程包装成一个Node,然后加入到队列中

然后来看我们的释放逻辑

release()
	public void release() {
        sync.releaseShared(1);
    }
	
	
    public final boolean releaseShared(int arg) {
        //尝试修改我们的state
        if (tryReleaseShared(arg)) {
            //修改成功,开始释放
            doReleaseShared();
            //state==0,所以从队列里面一个接一个的退出
            return true;
        }
        return false;
    }
tryReleaseShared()
	protected final boolean tryReleaseShared(int releases) {
			//自旋,然后将我们的state加上一定的值,然后CAS尝试修改
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current)
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }
doReleaseShared()
	//尝试自旋然后去唤醒其他的节点,然后其他的节点就回去尝试进行调用tryAcquireShared去进行获取
	private void doReleaseShared() {
        for (;;) {
            AbstractQueuedSynchronizer.Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == AbstractQueuedSynchronizer.Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, AbstractQueuedSynchronizer.Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                        !compareAndSetWaitStatus(h, 0, AbstractQueuedSynchronizer.Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }
总结

其实,这个的获取资源大概逻辑是这样的
非公平模式

  • 查看state减去自己需要的数量
  • 如果剩余数量小于0,准备开始进入队列尝试自旋
  • 如果剩余数量大于0并且CAS修改成功,直接返回,代表获取成功
  • 进入队列是将自己包装成一个共享模式的Node节点,然后自旋,如果自己的前驱不是头节点,就会被阻塞
  • 被唤醒后,还在自旋的循环中,然后判断如果前驱是头节点,就会去尝试修改我们的state,修改成功,就会去设置头节点,失败的话就会继续进行自旋

公平模式

  • 查看同步队列中是不是有线程,有的话直接进入队列阻塞,没有的话进行底下的步骤
  • 查看state减去自己需要的数量
  • 如果剩余数量小于0,准备开始进入队列尝试自旋
  • 如果剩余数量大于0并且CAS修改成功,直接返回,代表获取成功
  • 进入队列是将自己包装成一个共享模式的Node节点,然后自旋,如果自己的前驱不是头节点,就会被阻塞
  • 被唤醒后,还在自旋的循环中,然后判断如果前驱是头节点,就会去尝试修改我们的state,修改成功,就会去设置头节点,失败的话就会继续进行自旋

然后是释放的逻辑,两边都是一样的

  • 尝试去自旋的CAS修改我们的state为state+释放的量
  • 修改成功后,自旋的去唤醒节点,然后这些被唤醒的节点又会去尝试获取资源
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值