基于Semaphore理解部分AQS源码

Semaphore

  • Semaphore是信号箱。作用是控制访问特定资源的线程书名。底层依赖AQS的状态state。
  • 一般用于多个线程对多个资源的抢夺;
  • 如果资源数码为1,则退化为ReentrantLock;

代码示例

private static void test2() {
    Semaphore semaphore=new Semaphore(2);
    for (int i=0;i<5;i++){
        new Thread(()->{
            try {
                semaphore.acquire();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"获得车位");
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"离开车位");
            semaphore.release();
        },String.valueOf(i)).start();
    }
}

会有两个资源,然后开启5个线程去争抢这两个资源。

构造方法

//参数permit为资源数量,默认使用非公平方式争抢
public Semaphore(int permits)
//fair表示创建的公平争抢还是非公平争抢; 
public Semaphore(int permits, boolean fair)

源码:

    public Semaphore(int permits) {
        sync = new NonfairSync(permits);
    }
    //传入为true,则创建公平锁,传入false,创建非公平锁;
    public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

重要方法

//请求资源
public void acquire() throws InterruptedException 
//释放资源
public void release() 
//在时间内请求资源,获取不到就退出
tryAcquire(long timeout, TimeUnit unit)
Semaphore.acquire()
public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
//Semaphore.acquire()会调用下面的
//AbstractQueuedSynchronizer.acquireSharedInterruptibly方法;
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
        //判断线程是否发生中断,发生中断就抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
        if (tryAcquireShared(arg) < 0)
            doAcquireSharedInterruptibly(arg);
    }    

Semaphore类里定义了两个内部类NonfairSync,FairSync代表公平和非公平模式。

tryAcquireShared(arg)

实际调用的是Semaphore.NonfairSync.tryAcquireShared方法;再调用内部类Sync的nonfairTryAcquireShared方法;

  protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }
        
  final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
            //获取可使用的资源数据
                int available = getState();
                //减去当前线程请求的资源数据
                int remaining = available - acquires;
                //因为多个线程可能同时获取线程,所以修改state资源数码使用CAS方式。
                //最后返回剩余资源数量;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

返回后,做判断,如果返回值也就是剩余资源数量小于0,则执行下一个方法doAcquireSharedInterruptibly;

doAcquireSharedInterruptibly()

该方法在AbstractQueuedSynchronizer定义了;用来阻塞将获取不到资源的线程给阻塞住。

     private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //将获取不到资源的线程封装成Node结点,放进CLH同步队列;
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
                //获取当前线程结点的上一个结点。
                //不同线程结点在队列中的位置不同。
                final Node p = node.predecessor();
                //如果当前线程结点的上一个结点是头结点(傀儡结点),则条件成立
                if (p == head) {
                    //条件成立,尝试去获取资源。
                    //返回值r是剩余资源数量
                    int r = tryAcquireShared(arg);
                    //如果剩余资源数量大于0,则唤醒CLH中当前结点后面的结点。
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                //如果当前结点不是头结点,或者是头结点,但是剩余资源数量r小于0
                //则首先将当前线程结点的上一个结点的等待状态waitStatus变为-1,即Signal,返回false后;
                //再进行一次自旋,调用parkAndCheckInterrupt阻塞将当前线程阻塞;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //如果线程发生了中断,则抛出异常;
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
            //如果发生中断,会将线程结点的等待状态waitStatus改为cancel,然后调用下面方法移除这些结点。
                cancelAcquire(node);
        }
    }
addWaiter(Thread)

这个方法就是将线程封装成结点,然后放进CLH同步队列;

  private Node addWaiter(Node mode) {
        //1、 将当前结点封装为一个Node结点
        //mode为共享Share模式;
        Node node = new Node(Thread.currentThread(), mode);
        
        //获得同步队列的尾结点tail;
        //如果已经有队列中已经有Node结点了,则tail不为空;
        //由于我们线程A获得锁、其他线程里的第一个线程运行到这里的时,同步队列是空的,所以tail为null; 先执行enq方法; 
        Node pred = tail;
        //pred指向tail指针指向的结点。
        if (pred != null) {
        //如果tail尾指针不为空,则将当前结点的前向指针指向尾指针所指向的结点。
            node.prev = pred;
            
            if (compareAndSetTail(pred, node)) {
                //再通过CAS将尾指针指向当前结点。
                //将结点的前一个结点的next指针指向当前结点。
                pred.next = node;
                return node;
            }
        }
        //创建傀儡结点,这个方法可能会有多个线程同时进入。
        enq(node);
        //返回当前结点。
        return node;
    }
//首个进入队列的结点才会执行这个方法;
//可能会有多个线程进入这个方法,所以需要自旋和CAS
//即自旋锁来保证原子性;
  private Node enq(final Node node) {
        //自旋操作,可能多个线程同时进来
        for (;;) {
        //先获得队列尾结点tail;
            Node t = tail;
            //刚开始tail为空,判断成立
            if (t == null) { // Must initialize
            //多个线程可能同时进来,只有一个线程使用CAS创建一个傀儡结点;
            //其他线程CAS设置失败,返回false,跳出if方法体;
            //new Node(),是一个Thread为null的结点,也称为傀儡结点,使用CAS设置Head头结点;
            
                if (compareAndSetHead(new Node()))
                //然后再将头结点的引用设置为tail尾结点。
                //即使tail结点指向傀儡结点。
                    tail = head;
            } else {	
            //因为可能多个线程同时进入了这个方法,由于第一个线程创建了傀儡结点,并通过CAS将tail和head结点指向了傀儡结点。
            //所以tail不为空,其他的线程会将Node结点接入傀儡结点的后面
                
                node.prev = t;
                //将结点的前向指针指向队列的尾指针所指向的结点。
                if (compareAndSetTail(t, node)) {
                //再通过CAS将尾指针指向当前结点。
                //将结点的前一个结点的next指针指向当前结点。
                    t.next = node;
                 //返回当前结点。  
                    return t;
                }
            }
        }
    }
    
//判断头结点是否为null,如果为null,就把Update对象引用设置为头结点。
//这里的Update对象引用是傀儡结点的引用
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }

这样addWaiter执行结束,所有线程都封装成Node结点,入队了;

node.predecessor()

获取当前结点的上一个结点;

  final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }
shouldParkAfterFailedAcquire(p,node)

将上一个结点的等待状态waitStatus改为Signal,即可唤醒状态;

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //获取当前结点的上一个结点的waitStatus;
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
        //如果是-1,即Signal状态,则是可被唤醒装填。可以获得锁;
            return true;
        if (ws > 0) {
       
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
         //这里通过CAS将上一个结点的waitStatus改为Signal装填;
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        //返回false;
        return false;
    }

parkAndCheckInterrupt()

使用LockSupport的park方法将线程阻塞住。

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

到这里,线程才算是真正的阻塞,在等待资源了。

Semaphore.release()

释放资源,让其他线程去占有;

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

调用AQS的releaseShared方法释放1个资源;

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
tryReleaseShared(arg)
  protected final boolean tryReleaseShared(int releases) {
           //可能多个线程同时释放资源,所以需要自旋CAS操作;
            for (;;) {
          //获取剩余资源数量;
                int current = getState();
                //释放当前线程持有的资源数量;
                int next = current + releases;
             
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                    //多个线程可能会同时修改state资源数量;避免覆盖,使用CAS来保证数据正确性;
                if (compareAndSetState(current, next))
                //返回true;
                    return true;
            }
        }

执行该方法后,返回true,条件判断成立,调用doReleaseShared方法;

doReleaseShared()
  private void doReleaseShared() {
 
        for (;;) {
        //获取头指针指向的结点。(傀儡结点)
            Node h = head;
            //如果队列不为空,则成立
            if (h != null && h != tail) {
            //获取头指针指向的结点的等待状态
            //是否为Signal,可唤醒状态
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                //如果是Signal,则通过CAS修改该节点的等待状态变为0;修改成功,返回true,条件判断不成立
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                        //将该节点的下一个结点使用LockSupport的unpark唤醒;
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            //唤醒后,判断h结点是否还是头结点,如果是跳出循环。
            if (h == head)                   // loop if head changed
                break;
        }
    }
unparkSuccessor(h)

唤醒傀儡结点的下一个结点。

//node:传入的是队列头指针所指向的结点对象、即傀儡对象;
 private void unparkSuccessor(Node node) {
		//获取傀儡对象的等待状态waitStatuS
        int ws = node.waitStatus;
        //如果waitStatus小于0,则说明是Signal状态
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
            //将其waitStatus通过CAS改为0;
        Node s = node.next;
        //获取傀儡结点的下一个结点,是真正要被唤醒的结点。
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    	//如果傀儡结点的下一个结点存在,不为Null,则将这个结点里面封装的线程唤醒
    }

唤醒线程后,就回到线程线程阻塞的地方
parkAndCheckInterrupt;
在这里插入图片描述

setHeadAndPropagate(node, r)

设置头指针指向的结点,以及传播唤醒其他线程结点。

 private void setHeadAndPropagate(Node node, int propagate) {
 //传入的被唤醒的结点。
      //获取头指针指向的线程结点。
        Node h = head; // Record old head for check below
        //使头指针指向传入的node结点。
        setHead(node);
      //propagate为资源数量,肯定大于0;才会调用当前这个方法; 
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
        //获取被唤醒的线程结点node结点的下一个结点。
            Node s = node.next;
            if (s == null || s.isShared())
            //唤醒线程结点的方法,上面有讲。
                doReleaseShared();
        }
    }

在这里插入图片描述
在这里插入图片描述

Semaphore的源码解析基本完成,基本可以理解其工作流程;底层还是依赖AQS框架。

CountDownLatch

  • 让一些线程阻塞,知道另一些线程完成操作后,才被唤醒;
  • CountDownLatch维护了一个计数器,有两个方法:countDown()和await();
    A:调用countDown()方法,计数器-1;
    B:当计数器不为零,线程调用await方法时,会被阻塞
    C:当计数值为0时,因调用await方法被阻塞的线程会被唤醒;

实例代码

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        countDown();
    }

    private static void countDown() throws InterruptedException {
        CountDownLatch latch=new CountDownLatch(5);
        for (int i=0;i<5;i++){
            new Thread(()->{
                System.out.println("线程"+Thread.currentThread().getName()+"come in");
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();
            },String.valueOf(i)).start();
        }
        latch.await();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"exit");
        }).start();
    }
}
结果:
线程1come in
线程3come in
线程4come in
线程2come in
线程0come in
Thread-0exit

底层也是依靠AQS完成。

CyclicBarrier

  • 字面意思为可循环使用的屏障,他要做的事情是:让一组线程到到一个屏障时被阻塞,知道最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才会继续干活。
  • 通过await方法使线程进入屏障;
  • CountDownLatch是减,这个是增加。

实例代码

public class BarrierTest {
    public static void main(String[] args) {
        CyclicBarrier barrier=new CyclicBarrier(7,()->{
            System.out.println("召唤神龙");
        });
        for (int i = 0; i < 7; i++) {
            final int index=i;
            new Thread(()->{
                System.out.println("收集了第"+index+"龙珠");

                try {
                    TimeUnit.SECONDS.sleep(3);
                    barrier.await() ;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}
结果:
收集了第0龙珠
收集了第4龙珠
收集了第3龙珠
收集了第2龙珠
收集了第1龙珠
收集了第6龙珠
收集了第5龙珠
召唤神龙
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值