面试官:你来说说什么是Semaphore?

4 篇文章 0 订阅
4 篇文章 0 订阅

面试官:前面说到了CyclicBarrier 和CountDownLatch,你在说说Semaphore,你对它了解有多少?

在这里插入图片描述

初识Semaphore:

       Semaphore 信号量,用来控制同一时间,资源可被访问的线程数量,一般可用于流量的控制。

       Semaphore用于限制可以访问某些资源(物理或逻辑的)的线程数目,他维护了一个许可证集合,有多少资源需要限制就维护多少许可证集合,假如这里有N个资源,那就对应于N个许可证,同一时刻也只能有N个线程访问。一个线程获取许可证就调用acquire方法,用完了释放资源就调用release方法。

       Semaphore管理一系列许可。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可这个对象,Semaphore只是维持了一个可获得许可证的数量。

Semaphore 只有3个操作:

  • 初始化
  • 增加
  • 减少

API解读:

在这里插入图片描述

  • 构造方法一:Semaphore(int permits) 创建具有给定的许可数和非公平的公平设置的 Semaphore
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
  • 构造方法二:Semaphore(int permits, boolean fair) 创建具有给定的许可数和给定的公平设置的 Semaphore
/**
 * @param fair :是否公平,默认非公平;
 * false:非公平;true:公平
 */
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
  • acquire() 获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
public void acquire() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
}
  • acquire(int permits) 获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireSharedInterruptibly(permits);
}
  • acquireUninterruptibly() 获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
public void acquireUninterruptibly(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireShared(permits);
}
  • tryAcquire()尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
public boolean tryAcquire() {
    return sync.nonfairTryAcquireShared(1) >= 0;
}
  • tryAcquire(long timeout, TimeUnit unit)尝试获得令牌,在超时时间内循环尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
public boolean tryAcquire(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
  • release() 释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
 public void release() {
     sync.releaseShared(1);
 }
  • release(int permits)释放给定数目的许可,将其返回到信号量
public void release(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    sync.releaseShared(permits);
}
  • hasQueuedThreads() 等待队列里是否还存在等待线程。
public final boolean hasQueuedThreads() {
    return sync.hasQueuedThreads();
}
  • getQueueLength()获取等待队列里阻塞的线程数。
 public final int getQueueLength() {
     return sync.getQueueLength();
 }
  • drainPermits()清空令牌把可用令牌数置为0,返回清空令牌的数量。
public int drainPermits() {
    return sync.drainPermits();
}
  • availablePermits()返回可用的令牌数量
public int availablePermits() {
    return sync.getPermits();
}

代码示例:

场景分析:相信有车一族最头疼的就是停车了吧,每次到一个景点去停车,都会被保安告诉车位已满,需要等待,并且只能有车离开才能让下一辆车进去;有的车库在门前会显示有几个车位,剩余多少车位。那么这个功能通过代码是如何实现的呢?简易版代码如下:

public class SemaphoreTest {
  private static int count = 20;
  //停车场同时容纳的车辆8, 此处默认使用非公平的锁,你们可以私下去使用公平锁去试试,就会发现,先到先得
  // 写法示例: Semaphore semaphore = new Semaphore(8, true);
  private static Semaphore semaphore = new Semaphore(8);
  private static ExecutorService service = Executors.newFixedThreadPool(count);
  public static void main(String[] args) {
    test();
  }
  public static void test() {
      //模拟100辆车进入停车场
      for (int i = 0; i < 20; i++) {
        int num = i + 1;
        service.execute(() -> {
              try {
                System.out.println("车牌 00000" + num + "来到停车场");
                if (semaphore.availablePermits() == 0) {
                  System.out.println("车位不足,请耐心等待");
                }
                semaphore.acquire();
                System.out.println("车牌 00000" + num + "成功进入停车场");
                Thread.sleep(new Random().nextInt(10000));
                System.out.println("车牌 00000" + num  + "驶出停车场");
                semaphore.release();//释放令牌,腾出停车场车位
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
          });
      }
      service.shutdown();
    }
}

执行结果:


```java
车牌 000001来到停车场
车牌 000003来到停车场
车牌 000002来到停车场
车牌 000003成功进入停车场
车牌 000001成功进入停车场
车牌 000005来到停车场
车牌 000005成功进入停车场
车牌 000006来到停车场
车牌 000006成功进入停车场
车牌 000004来到停车场
车牌 000002成功进入停车场
车牌 000004成功进入停车场
车牌 000007来到停车场
车牌 000007成功进入停车场
车牌 000008来到停车场
车牌 000008成功进入停车场
==================================
车牌 000009来到停车场
车位不足,请耐心等待
车牌 0000010来到停车场
车位不足,请耐心等待
车牌 0000011来到停车场
车位不足,请耐心等待
车牌 0000012来到停车场
车位不足,请耐心等待
车牌 0000013来到停车场
车位不足,请耐心等待
车牌 0000014来到停车场
车位不足,请耐心等待
车牌 0000015来到停车场
车位不足,请耐心等待
车牌 0000016来到停车场
车位不足,请耐心等待
车牌 0000017来到停车场
车位不足,请耐心等待
车牌 0000018来到停车场
车位不足,请耐心等待
车牌 0000019来到停车场
车位不足,请耐心等待
车牌 0000020来到停车场
车位不足,请耐心等待
===================================
车牌 000004驶出停车场
车牌 000009成功进入停车场
车牌 000002驶出停车场
车牌 0000010成功进入停车场
车牌 0000010驶出停车场
车牌 0000011成功进入停车场
车牌 0000011驶出停车场
车牌 0000012成功进入停车场
车牌 000003驶出停车场
车牌 0000013成功进入停车场
车牌 000006驶出停车场
车牌 0000014成功进入停车场
车牌 000008驶出停车场
车牌 0000015成功进入停车场
车牌 000007驶出停车场
车牌 0000016成功进入停车场
车牌 0000015驶出停车场
车牌 0000017成功进入停车场
车牌 000005驶出停车场
车牌 0000018成功进入停车场
车牌 000009驶出停车场
车牌 0000019成功进入停车场
车牌 0000019驶出停车场
车牌 0000020成功进入停车场
车牌 000001驶出停车场
车牌 0000012驶出停车场
车牌 0000017驶出停车场
车牌 0000016驶出停车场
车牌 0000018驶出停车场
车牌 0000013驶出停车场
车牌 0000014驶出停车场
车牌 0000020驶出停车场

源码解析:(默认使用不公平许可证)
在这里插入图片描述
通过上面案例,我们可以发现,首先通过 semaphore.availablePermits() 当前资源是否充足,不足则告诉其他车主,停车场满了别进来了。然后调用 semaphore.acquire() 去获取令牌,源码如下:

/**
 * 获取1个令牌
 */
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}
/**
 * 共享模式下获取令牌,获取成功则返回,失败则加入阻塞队列,挂起线程
 */
public final void acquireSharedInterruptibly(int arg)
            throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //尝试获取令牌,arg为获取令牌个数,当可用令牌数减当前令牌数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}
/**
 * 1、创建节点,加入阻塞队列,
 * 2、重双向链表的head,tail节点关系,清空无效节点
 * 3、挂起当前节点线程
 */
private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
    //创建节点加入阻塞队列
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            //获得当前节点pre节点
            final Node p = node.predecessor();
            if (p == head) {
                //返回锁的state
                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);
    }
}

获取到了车位,然后开始停车。。。。。。下班了,开车回家,此时我们是不是要离开车库,即去释放车位;
在这里插入图片描述
源码如下:

/**
 * 释放令牌
 */
public void release() {
    sync.releaseShared(1);
}   
/**
 * 释放共享锁,同时唤醒所有阻塞队列共享节点线程
 */
public final boolean releaseShared(int arg) {
    //释放共享锁
    if (tryReleaseShared(arg)) {
        //唤醒所有共享节点线程
        doReleaseShared();
        return true;
    }
    return false;
}
/**
 * 共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
 */
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;
    }
}
/**
 * 唤醒所有共享节点线程
 */
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {//是否需要唤醒后继节点
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))//修改状态为初始0
                    continue;
                unparkSuccessor(h);//唤醒h.nex节点线程
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE));
        }
        if (h == head)                   // loop if head changed
            break;
    }
}

================== 结束 ====================

参考文章:https://zhuanlan.zhihu.com/p/98593407

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值