JUC Semaphore原理

使用demo

Semaphore,的用法,demo;_三井08的博客-CSDN博客_semaphoredemo

原理简介

以下链标识的是AQS中的node链表

head->node1->node2->node3->tail(node4)

1、初始化时对state进行赋值。比如说2,标识着允许的信号量

2、当线程1调用acquire方法时,当前需要一个信号量,判断2大于等于1,则设置state为1,设置当前节点为头结点,并尝试去唤醒下一个node(因为node2没有添加,所以不会继续唤醒),并且允许当前线程运行

此时的链表结构是head(node1)->node2->node3->tail(node4)

3、当线程2调用acquire方法时,当前需要一个信号量,判断1大于等于1,则设置state为0,设置当前节点为头结点,并尝试去唤醒下一个node(因为node2没有添加,所以不会继续唤醒),并且允许当前线程运行

此时的链表结构是head(node2)->node3->tail(node4)

4、当线程3调用acquire方法时,当前需要一个信号量,判断0小于1,则设置state为1,则标识当前节点竞争失败,并且park住。

此时的链表结构是head(node2)->node3->tail(node4)

5、当线程4调用acquire方法时,当前需要一个信号量,判断0小于1,则设置state为1,则标识当前节点竞争失败,并且park住。

此时的链表结构是head(node2)->node3->tail(node4)

6、当线程1调用release方法时,此时会设置state为1,同时会从头结点的下一个节点进行唤醒,发现线程3需要1个信号量,此时则设置state为1,并且unpark当前线程。然后把当前线程变成变为头结点,尝试唤醒线程4,发现信号量不足,则线程4继续park

此时的链表结构是head(node3)->tail(node4)

        

方法分析

acquire

本质是调用AQS的acquire方法

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

release

释放许可证,并且会调用releaseShared方法。(会从头部节点开始按顺序唤醒后续的共享节点)

public void release(int permits) {
        if (permits < 0) throw new IllegalArgumentException();
        sync.releaseShared(permits);
    }

构造方法 Semaphore

设置允许的信号量,默认使用非公平竞争

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

Semaphore(int permits, boolean fair)

可以设置是公平还是非公平

public Semaphore(int permits, boolean fair) {
        sync = fair ? new FairSync(permits) : new NonfairSync(permits);
    }

内部类Sync

构造方法 Sync

其主要是通过控制AQS的state来标识有几个状态可以使用

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

nonfairTryAcquireShared

获取能设置的信号量,当剩余可以获取的信号量大于需要获取的信号量时,通过cas的方式修改剩下可以获取的信号量。

如果剩余的信号量比要获取的数量多,并且没有设置成功(被别的线程抢占设置),则进入自旋

final int nonfairTryAcquireShared(int acquires) {
            for (;;) {
                int available = getState();
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

tryReleaseShared

尝试释放信号量。通过cas的方法,让state的值加上当前释放的值

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

reducePermits

把允许的信号量值减少。即是设置state的值为当前state的值减去要减小的值

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

drainPermits 

把允许获取的信号量值置为0

final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }

内部类NonfairSync(继承自Sync)

NonfairSync(int permits) {
            super(permits);
        }
protected int tryAcquireShared(int acquires) {
            return nonfairTryAcquireShared(acquires);
        }

内部类FairSync(继承自Sync)

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

tryAcquireShared

在尝试去修改state值的时候判断Node的双向链表中是否有正在等待的节点。其公平非公平原理和ReentrantLock基本一致

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内部共享方法

acquireShared

调用子Sync实现的tryAcquire方法

public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

doAcquireShared

1、把当前节点添加到队列中

2、循环获取头结点

3、判断头结点是否可以能获取到足够的凭证,如果可以,则设置当前节点为头结点,并且唤醒下一个节点

4、如果不行,则park住当前节点

private void doAcquireShared(int arg) {
        
        在addWaiter中以Node.SHARE构建一个node节点,添加到node队列的结尾并返回构建的节点
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                获取node节点的前驱节点
                final Node p = node.predecessor();
                
                如果node节点的前驱节点是头结点,注意这里和独占式的区别,独占式在这里CAS设置状态
                if (p == head) {
                    获取节点的状态
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        将node节点设置为头结点 ,如果r大于0,原来的头结点的状态小于0,就获取node节点的后继节点,如果后继节点为null或者后继节点是共享节点,就激活node节点的后继节点
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

releaseShared

释放共享节点

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

doReleaseShared

释放头结点,头结点在被释放之后,会经过原先的acquire中的park之后的循环。唤起后续的节点。

private void doReleaseShared() {
        for (;;) {
            Node h = head; //获取头结点
            if (h != null && h != tail) {
                int ws = h.waitStatus; //获取头结点的状态
                if (ws == Node.SIGNAL) {//如果头节点线程节点需要被激活,就尝试更新头结点的状态为0,如果更新状态失败,就继续循环,如果更新状态成功,就激活头结点的有效后继节点。
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)){ //更新状态失败,就继续循环
                        continue;
                 }
                    //更新状态成功就激活头结点的有效后继节点
                    unparkSuccessor(h);
                }else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) //如果头结点的初始状态为0,就CAS将状态更新为-3,如果成功,就判断头结点是否被修改,
                    continue; //CAS 失败就一直循环
            }
            if (h == head) //如果头结点指针没有变化,就一直循环,否则,退出循环
                break;
        }
    }

setHeadAndPropagate

把当前节点设置为头结点,并且在判断为共享节点的情况下,往下释放节点

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; //记录下原来的头结点 
        setHead(node);//将node节点设置为头结点
       
        if (propagate > 0 || h == null || h.waitStatus < 0) {
            Node s = node.next; 
            if (s == null || s.isShared()) //如果是共享节点就激活头结点的后继节点
                doReleaseShared();
        }
    }

CountDownLatch demo

初始化构造传入参数3,即设置aqs中的state状态为3

countDown方法其主要是设置其state值减一,并且判断其state值减到0的时候会唤醒在等待的线程。

await方法主要是判断当前state值是否为0,如果为0则继续执行,如果不为0则等待

public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        // 只能使用一次
        CountDownLatch countDownLatch = new CountDownLatch(3);
        countDownLatch.countDown();
        countDownLatch.await();
    }

CyclicBarrier demo

循环栅栏是相当于有构造方法个数的await之后会统一放行

public static void main(String[] args) {
        CyclicBarrier cb = new CyclicBarrier(2);
        new Thread(() -> {
            System.out.println("线程1开始");
            try {
                cb.await();
                System.out.println("线程1结束");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (BrokenBarrierException e) {
                throw new RuntimeException(e);
            }
        }).start();

        new Thread(() -> {
            System.out.println("线程2已开始");
            try {
                cb.await();
                System.out.println("线程2结束");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (BrokenBarrierException e) {
                throw new RuntimeException(e);
            }
        }).start();

        new Thread(() -> {
            try {
                Thread.sleep(2000L);
                System.out.println("线程3已开始");
                cb.await();
                System.out.println("线程3结束");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (BrokenBarrierException e) {
                throw new RuntimeException(e);
            }
        }).start();

        new Thread(() -> {
            System.out.println("线程4已开始");
            try {
                cb.await();
                System.out.println("线程4结束");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (BrokenBarrierException e) {
                throw new RuntimeException(e);
            }
        }).start();


    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值