Semaphore信号量源码详解

Semaphore信号量底层依赖于AQS实现的信号量同步工具,可以用来做限流操作;我们可以通过Semaphore来定义信号量的资源池,通过acquire方法来获取资源,使用完成资源后,通过调用release方法释放资源池资源

使用实例

 

public class SemaphoreTest {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(2);
        for (int i = 0; i < 5; i++) {
            new Thread(new Task(semaphore,"jiy"+i)).start();
        }
    }

    static class Task extends Thread{
        Semaphore semaphore;
        public Task(Semaphore semaphore,String name){
            this.semaphore =semaphore;
            this.setName(name);
        }

        public void run(){
            try {
                semaphore.acquire();
                System.out.println("获取线程资源了"+Thread.currentThread().getName());
                Thread.sleep(3000);
                semaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

定义一个Task实现Thread类,在run方法里面打印线程名称,并且获取资源锁,睡眠3秒之后释放锁;

执行的结果会看到 每次都只会打印出两个线程名称,其他的线程会被阻塞,3秒之后又会打印出两个线程名称。。

 

底层实现 Semaphore源码

abstract static class Sync extends AbstractQueuedSynchronizer {
     
 }
 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);
        }
    }
 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;
            }
        }
    }

Semaphore构造方法

Semaphore内部持有Sync实现自AQS,内部自己定义了公平和非公平的类;

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

通过Semaphore构造方法,可以看出,Semaphore默认是使用非公平锁;

 

		//NonfairSync
        NonfairSync(int permits) {
            super(permits);
        }
		
 		Sync(int permits) {
            setState(permits);
        }	

 

setState(permits);设置初始化的资源数大小;

Semaphore的acquire方法

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

//AQS
 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
     	//尝试进行加锁
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }

首先我们可以看到调用acquire方法会调用sync.acquireSharedInterruptibly(1)方法;通过这个方法底层调用的AQS的acquireSharedInterruptibly()

在AQS的acquireSharedInterruptibly方法中,先判断当前线程是否中断;尝试调用tryAcquireShared(arg)进行尝试加锁;tryAcquireShared(arg)是一个钩子方法,

会调用Semaphore类里面的tryAcquireShared方法;

 

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

 

开始尝试加锁操作,可以从nonfairTryAcquireShared方法中看到;我们从第一个线程加锁来看

  1. 获取当前信号量数量 (设置的初始值2)

  2. 获取可用的信号量数量(2-1 = 1)

  3. 判断可用信号量是否小于0;(由于是第一次加锁,所以可用信号量为1 是大于0,则需要执行第四步)

  4. 使用CAS的方式来修改state数量(将state从2修改为1)

  5. 返回可用信号量数(1)

整个使用tryAcquireShared的流程是这样的;这种是可以加锁成功的场景,由于信号量是存在的;那么接下来我们来看一下当信号量已经使用完了情况;

  1. 获取当前信号量数量 (0)

  2. 获取可用的信号量数量(0-1 = -1)

  3. 判断可用信号量是否小于0;(-1)

  4. 返回可用信号量数(-1)

//AQS
 public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
     	//尝试进行加锁
        if (tryAcquireShared(arg) < 0)
            //由于tryAcquireShared()方法返回的值为-1,所以需要执行下面方法
            doAcquireSharedInterruptibly(arg);
    }

通过第二个场景,我们可以看到由于tryAcquireShared()返回值为-1,所以会执行doAcquireSharedInterruptibly(arg);

	//AQS
	private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        // 1 把获取锁失败的线程添加到等待队列中
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    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);
        }
    }

 首先我们看到的是1 final Node node = addWaiter(Node.SHARED); 把获取锁失败的线程添加到等待队列

private Node addWaiter(Node mode) {
        //把当前线程封装成一个node
        Node node = new Node(Thread.currentThread(), mode);
        // 记录tail节点
        Node pred = tail;
        //如果tail节点不等于null
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

 private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

首先在整个阻塞队列里面是空 head和tail都为空;

1 Node pred = tail; 记录tail节点 if (pred != null) 判断tail节点是否为空;(由于是等待队列里面没有任何数据,tail节点为空)

2 直接执行enq(node);方法;自旋(死循环),Node t = tail;获取tail节点;if (t == null) 判断tail节点是否为空(初始值的时候为空);

所以进入方法执行 if (compareAndSetHead(new Node())) 通过CAS的方式设置一个head节点为一个空的节点(new Node()); tail = head;然后将head和tail都指向这个空节点;(此时head和tail都不为null了)

3 继续执行 for (;;)自旋操作,if (t == null) 再一次判断tail节点是否为空,这一次tail节点不为空了,执行else操作

4 node.prev = t;将传入的节点的前置节点指向tail节点,compareAndSetTail(t, node)然后通过CAS的方式将当前节点Node设置为tail节点, t.next = node;然后将t.next指向当前节点(注意,此时的t不是tail节点,而是head节点,tail节点已经通过CAS设置成为了当前的node节点了);接下来返回t(head节点);

 

上述看完了final Node node = addWaiter(Node.SHARED);把获取锁失败的线程加入到阻塞队列中之后,继续看下面的操作;

//AQS
	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);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //shouldParkAfterFailedAcquire设置前置节点的waitStatus=-1,并且清除waitStatus>0的无用数据
                //parkAndCheckInterrupt 将当前线程进行挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

 

1 首先进行一个自旋 for (;;)final Node p = node.predecessor()获取当前node节点的前置节点;if (p == head)判断当前节点是否为头节点(根据我们上图看到的Node的前置节点是头节点,所以执行第二步);

2 int r = tryAcquireShared(arg); 尝试再次获取锁;正常情况下这一步是会失败的;

3 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) 执行这个判断方法;

     3.1 shouldParkAfterFailedAcquire(p, node)

 

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取前置节点的等待状态
        int ws = pred.waitStatus;
        //如果前置节点的等待状态为-1,则直接返回true,
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * 如果前置节点是初始值,或者是共享值,将前置节点设置为-1,阻塞后续的节点
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

       3.1.1 首先获取头节点等待状态,由于是new Node()创建的头节点,所以ws = 0的;compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 执行这一步将头节点的waitStatus从0修改为-1;并且返回false;

      3.1.2 然后继续触发步骤1的自旋操作,然后来到了3.1的方法;这一次ws = -1的,所以会返回true;然后执行3.2的方法;

  3.2 parkAndCheckInterrupt() 将当前的线程通过 LockSupport.park(this);进行挂起,然后返回当前线程的中断状态(false,当前线程只是阻塞并没有中断)

 

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

所以整个线程就阻塞在了doAcquireSharedInterruptibly方法上自旋里面等待被唤醒;

Semaphore的release方法

 

public void release() {
        sync.releaseShared(1);
    }
//AQS
  public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

首先调用semaphore的release方法,会调用AQS的releaseShared方法;

通过钩子方法tryReleaseShared(arg)来尝试解锁操作

 

 protected final boolean tryReleaseShared(int releases) {
     		//自旋
            for (;;) {
                //获取当前的信号量数量	
                int current = getState();
                // 当前信号量数量 + 1 (0 + 1)
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                //通过CAS的方式修改state值
                if (compareAndSetState(current, next))
                    return true;
            }
        }

 在tryReleaseShared方法里面进行自旋操作,首先获取到当前信号量,然后加上要释放的信号量的数量,然后通过CAS的方式进行修改state(使用自旋是为了避免在并发场景下CAS操作的失败);然后返回true,继续执行tryReleaseShared()方法,唤醒后置节点线程

private void doReleaseShared() {
    	//自旋
        for (;;) {
            //获取头节点
            Node h = head;
            //判断头节点不为空,并且头节点和尾节点不相等(中间还有其他的节点)
            if (h != null && h != tail) {
                //获取头节点的等待状态
                int ws = h.waitStatus;
                //判断头节点的等待状态是否为-1
                if (ws == Node.SIGNAL) {
                    //如果头节点的等待状态为-1,则将它修改为0
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    //执行唤醒节点方法
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

  private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        //如果当前头节点等待状态小于0,则将状态设置为0
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        //获取后置节点
        Node s = node.next;
        //如果后置节点为空;或者后置节点等待状态大于0
        if (s == null || s.waitStatus > 0) {
            s = null;
            //获取尾节点,从后往前遍历,找到等待状态小于0的节点
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //如果后置节点不为Null
        if (s != null)
            //唤醒后置节点
            LockSupport.unpark(s.thread);
    }

 

doReleaseShared()方法主要是判断是否还有其他的等待节点,然后将头节点的waitStatus修改为0

unparkSuccessor(h)方法是,获取头节点的后置节点,如果后置节点不为空或者waitStatus不大于0,则唤醒后置节点;如果不满足,则从尾节点进行向前循环,找到第一个满足waitStatus<=0的节点进行唤醒;

流程图

 

Guess you like

Origin blog.csdn.net/JIY743761374/article/details/112145416