一、 故事起源

很久很久以前,大概也就是昨天,肠胃作祟,急需排泄,提裤奔跑,进入厕所,

左右扫视,没有坑位,灵光一闪,换了楼层,找到坑位, 拉完粑粑接着回去写bug。

二、不安现状

身为程序员的我,想着改变世界,但遗憾暂未实现。改变一个厕所总可以吧,我不配吗?

所以我打算写一个程序来模拟厕所剩余坑位(当然了,业务上可能不会这样写,我之所以写这种代码是为了学习Semaphore,你应该懂我的良苦用心,还有就是不要给我找多线程的问题,这个示例禁不起大佬review,因为我过得浑浑噩噩。)

1. 代码先上

wc.java

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;/*** @author 发现更多精彩  关注公众号:木子的昼夜编程* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作*/
public class WC {// 假设一共有2个坑位(真少哈哈)private Semaphore s = new Semaphore(2);// 查看剩余坑位public int surplus(){// 获取剩余可用凭证数return s.availablePermits();}// 找坑位public void enter(String name){// 尝试找坑boolean b = s.tryAcquire();if (b) {System.out.println(name+"来了,有坑位,开始占用");// 模拟占用时间sleepRandom();// 释放凭证s.release();System.out.println("爽呆呆,"+name+"释放坑位");} else {System.out.println(name+"来了,木有空位,伤心离去");}}// 随机让线程睡会儿 模拟占坑时间private void sleepRandom(){try {int random=(int)(Math.random()*10+1);Thread.sleep(TimeUnit.SECONDS.toMillis(random));} catch (InterruptedException e) {e.printStackTrace();}}
}

Test.java

/*** @author 发现更多精彩  关注公众号:木子的昼夜编程* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作* @create 2021-09-19 9:25*/
public class Test {public static void main(String[] args) {WC wc = new WC();// 多个人进入厕所new Thread(()-> wc.enter("小强")).start();new Thread(()-> wc.enter("小月月")).start();new Thread(()-> wc.enter("小月鸟")).start();// 小明比较聪明 先从程序看看有没有坑位 有再去没有就不去了new Thread(()->{int sp = wc.surplus();// 如果有坑再去(但是不一定去了就有,说不定刚好别人占了呢)if (sp > 0) {wc.enter("小明");} else {System.out.println("小明知道没坑,先不去");}}).start();}
}

输出结果:

2. 理论跟上

咦? 好神奇,一个Semaphore就能实现。对,就是这么牛.

这时候就有人问了,他是怎么实现这么高级的功能的,少年你是问出这个问题的时候我就知道你没有看我前边的文章(这时候可以关注:木子的昼夜编程 发现更多精彩)。

他之所以这么厉害,是因为他有一个好友名字叫做:AbstractQueuedSynchronizer 用我蹩脚的英语翻译:抽象队列同步器,不重要,每个人都有小名,你可以叫他小名:AQS 。

AOS人品极好,他有很多朋友,有你熟悉的ReentrantLockCountDownLatch

这里再简单介绍一下我们的兄弟AQS吧。 他的主要构成有两个:state , queue

state: 记录可用凭证数(剩余坑位)

queue: 记录等待获取凭证的线程(厕所外排队拉粑粑的人)

获取坑的流程是这样的:

看着是不是很简单,判断是否有空位,有就进入,没有就排队,等待有空位再进入。

越简单的事情其实越复杂,比如为什么 1+1 = 2 。

3. 聊聊我们的 Semaphore方法
import java.util.concurrent.Semaphore;/*** @author 发现更多精彩  关注公众号:木子的昼夜编程* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作* @create 2021-09-19 10:51*/
public class TestMethod {public static void main(String[] args) throws InterruptedException {Semaphore s = new Semaphore(2);//(常用) 尝试获取凭证,直到获取到凭证或者线程被中断s.acquire();// acquire()加强版,可以一次获取多个凭证,屁股大占俩坑位s.acquire(2);// 尝试获取凭证,直到获取到凭证s.acquireUninterruptibly();// acquireUninterruptibly()的加强版 可以一次获取多个凭证s.acquireUninterruptibly(2);// 不会死等 尝试获取 获取不到就返回false 获取到就返回true// 用一个成语来形容叫:浅尝辄止boolean b = s.tryAcquire();// tryAcquire() 加强版boolean nb = s.tryAcquire(10);// 查询剩余凭证 其实就是state的值s.availablePermits();//(常用) 释放凭证 也就是离开坑位 这里需要注意,多次release 凭证会变多// 也就是release 100次 坑位就会多出100个  这个业务上需要注意,必须acquire才能release// 不然大家一看100个坑位 都去了那不打起来了吗s.release();// s.release();加强版 释放多个凭证s.release(2);// 这个方法绝了 获取所有剩余凭证并返回凭证数量// 有点儿像保洁阿姨,大喊一声:别进来了,我要开始打扫了。// drain:排水;排空;(使)流光;放干;(使)流走,流出;喝光;喝干int count = s.drainPermits();// 获取AQS的实现是否是公平模式  这个就涉及到公平、非公平了 后边再聊boolean fair = s.isFair();// 看有多少人排队int queueLength = s.getQueueLength();// 看是否还有排队的人 有就返回true 没有返回falseboolean b1 = s.hasQueuedThreads();}
}
4. AQS 对朋友的标准是什么

AQS说了,我有我的原则,你们只要遵循我的原则,我们就能成为好朋友。

原则是什么呢?AQS定义了很多方法,但是都是空实现,他的朋友们需要实现这些方法。

这不就是“模板方法”模式。

包括但不限于以下方法:

protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();
}protected boolean tryRelease(int arg) {throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {throw new UnsupportedOperationException();
}

我们拿tryAcquire 举例调用链是这样的:

AQS定义了流程,朋友在遵循流程的基础上进行自我发挥就好了。

我们来看一下代码简单流程:

5.Semaphore的属性sync

sync是Semaphore属性,他可能是FairSync 也可能是NonfairSync

// AQS子类
abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 1192457210091910933L;// 设置凭证 调用AQS方法Sync(int permits) {setState(permits);}// 获取凭证 调用AQS方法final int getPermits() {return getState();}// 默认实现了一个非公平的获取凭证方式// 这里大家可能有疑问,就是公平和非公平有什么不一样下边我画个图解释一下// 抢坑位final int nonfairTryAcquireShared(int acquires) {for (;;) {// 获取可用凭证数int available = getState();// 可用凭证数-申请凭证数int remaining = available - acquires;// 如果remaining小于0 也就是凭证不够// 或者cas设置state成功(获取凭证成功)// 返回剩余凭证数if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}// 释放凭证// 蹲完坑 给别人用protected final boolean tryReleaseShared(int releases) {for (;;) {// 获取当前剩余凭证数int current = getState();// 当前凭证数+释放的凭证数int next = current + releases;// (当前凭证数+释放的凭证数) < current 可能releases是个负数 // 我没理解他为什么不直接for外边就判断releases的正负 if (next < current) // overflowthrow new Error("Maximum permit count exceeded");// 如果cas设置成功了就直接返回 否则for循环接着执行循环逻辑// for+cas 是很常用的一种乐观锁实现方式if (compareAndSetState(current, next))return true;}}// 这个可高级了 直接减掉凭证数量// 某个坑位冲水坏了,保洁直接外边立个牌子叫:禁止使用final void reducePermits(int reductions) {for (;;) {// 获取当前剩余凭证int current = getState();// 减去要削减的凭证数int next = current - reductions;// 不能大于当前凭证数// 我还是觉得在for外边判断一下reductions的正负就可以了if (next > current) // underflowthrow new Error("Permit count underflow");// casif (compareAndSetState(current, next))return;}}// 申请使用所有剩余可用凭证// 就是保洁来打扰 锁了所有没有人在使用的坑位final int drainPermits() {for (;;) {int current = getState();// cas设置为0if (current == 0 || compareAndSetState(current, 0))return current;}}
}/*** 非公平版本*/static final class NonfairSync extends Sync {private static final long serialVersionUID = -2694183684443567898L;// 调用父类构造NonfairSync(int permits) {super(permits);}// 非公平直接使用他爹 Sync的方法 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 (;;) {// 判断有没有人排队 有就返回-1if (hasQueuedPredecessors())return -1;// 没人排队 尝试获取凭证int available = getState();int remaining = available - acquires;if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}}

什么是公平,什么是非公平:

我先讲个例子看大家能不能理解了。

一堆人在厕所门口排队,小强也来排队了,一看好多人排队,就乖乖的站在最后,等前边人蹲完坑了自己再去蹲,过了会儿小月鸟来了,一看厕所门口没有人排队,他就直接进去看看有没有坑,有的话就蹲,没有就算了,过了会儿小月月来了,他直接去厕所看有没有可用坑位,有的话他就占用,没有他再去后边排队。

请问他们三个人谁是公平,谁是不公平。

答案:小强、小月鸟是公平的,小月月是不公平的 。

怎么区分出来的,就是他们来了有没有先看看是否有排队,如果有排队就站后边,没有人排队才能直接进去找坑位。

6. 排队是什么操作:AQS的方法之addWaiter

一直在说排队排队,是怎么排的呢,直接上代码

// 添加到队尾并返回新创建Node
final Node node = addWaiter(Node.SHARED);
// 添加到队尾
private Node addWaiter(Node mode) {// 创建一个Node Node node = new Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failure// 获取尾结点 Node pred = tail;// 如果尾结点不是空 尝试快速把新Node追加到尾节点后边if (pred != null) {// 当前节点前一个节点设置为现在的队尾节点node.prev = pred;// cas设置node为队尾if (compareAndSetTail(pred, node)) {// 之前的队尾节点的下一个节点指向新创建的节点 这是链表添加的步骤 pred.next = node;return node;}}// 如果没有快速添加成功 就执行enq 循环添加直到成功为止enq(node);return node;
}
// 兜底循环
private Node enq(final Node node) {for (;;) {Node t = tail;// 如果tail为空 也就是链表为null 先初始化一个空节点的链表// 链表刚开始head = tail 也就是俩指针都指向空的head节点// 这个空的head节点 你一定要记住 要不然某些地方会有疑惑// 也就是说他这个队列跟排队等坑还是有区别的,最前边一个不是人// 你可以把head看成是一个排队起止线 大家都在这个线后边排队if (t == null) { // Must initializeif (compareAndSetHead(new Node()))tail = head;} else {// 追加到链表尾部 node.prev = t;if (compareAndSetTail(t, node)) {t.next = node;return t;}}}
}
// cas设置头结点
private final boolean compareAndSetHead(Node update) {return unsafe.compareAndSwapObject(this, headOffset, null, update);
}// cas 设置  expect:原队尾节点   update:新创建Node
private final boolean compareAndSetTail(Node expect, Node update) {return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
7. 队列中的我们是怎么知道前边没人了呢

现实中我们可能会自己盯着,前边没人了我们自己就知道了,还有一种情况不知道大家遇到过没?

在你上学的时候,每次考完试,班主任会找那些成绩波动大的人谈话,老师谈完一个后,就会让这个人通知下一个人出去跟老师谈话,你在那惶恐不安,等着被叫。

private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {// 这个是排队 前边讲了final Node node = addWaiter(Node.SHARED);boolean failed = true;try {for (;;) {// 获取当前节点的前一个节点final Node p = node.predecessor();// 看看是不是headif (p == head) {// 如果前边是head就尝试找坑int r = tryAcquireShared(arg);if (r >= 0) {// 把自己设置为头结点 并通知后边人可以找坑了setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}
}private void setHeadAndPropagate(Node node, int propagate) {Node h = head; // Record old head for check below// 当前节点设置为头结点 setHead(node);// 符合各种条件就可以释放凭证 通知下一个人可以找坑了if (propagate > 0 || h == null || h.waitStatus < 0 ||(h = head) == null || h.waitStatus < 0) {Node s = node.next;if (s == null || s.isShared())doReleaseShared();}
}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))continue;            // loop to recheck casesunparkSuccessor(h);}else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue;                // loop on failed CAS}if (h == head)                   // loop if head changedbreak;}}

三、还有谁

大概用到的几个点都写了写,要再细的我也没有很了解了。

更多精彩关注:木子的昼夜编程

知道公司抢“坑位”是什么意思吗?相关推荐

  1. 快手公司厕所装坑位计时器,网友:再也不能带薪拉屎了!

    编辑:可可 转自:开发者技术前线报道 最近带薪拉屎这个词又火了. 农老李 带薪拉屎,网络流行语,指工作想偷懒的时候,借由上厕所,带着手机把明明在5分钟内拉完的屎在公司花20分钟拉完. 最早起源于一位日 ...

  2. 重磅:快手公司厕所装坑位计时器,网友:再也不能带薪拉屎了!

    这将是个极不好的开端,会席卷整个互联网界,就像当年xiaomi带动这个行业 996一样...估计很多公司会跟进 最近带薪拉屎这个词又火了. 农老李 带薪拉屎,网络流行语,指工作想偷懒的时候,借由上厕所 ...

  3. 神策数据薛创宇:数据分析与场景实践之“坑位运营”

    本文内容由神策数据根据数据驱动大会现场演讲整理而成.本文主要内容如下: 坑位运营起源 数据分析与坑位运营 如何进行坑位运营 实践案例 温馨提示:完整版 PPT 可点击阅读原文下载. 开始分享之前,想了 ...

  4. 安装厕所坑位计时器???快手回应:为了测试如厕次数与时间增加坑位数量...

    Python实战社群 Java实战社群 长按识别下方二维码,按需求添加 扫码关注添加客服 进Python社群▲ 扫码关注添加客服 进Java社群▲ 来源丨人工智能那点事 新浪科技讯 10月23日晚间消 ...

  5. 你需要掌握的有关.NET DateTime类型的知识点和坑位 都在这里

    引言    DateTime数据类型是一个复杂的问题,复杂到足以让你在编写[将日期从Web服务器返回到浏览器]简单代码时感到困惑. ASP.NET MVC 5和 Web API 2/ASP.NETCo ...

  6. 电脑公司Win11 64位全新旗舰版镜像V2021.08

    电脑公司Win11 64位全新旗舰版镜像V2021.08以微软官方原版作为母盘对系统进行了全面优化更新,用户使用更加流畅顺手,轻松体验到系统的优秀性能,适用目前市场最新机型以及老旧机型,多种安装方式供 ...

  7. 电脑公司win11 64位旗舰版镜像文件v2021.07

    电脑公司win11 64位旗舰版镜像文件v2021.07是目前非常受欢迎的电脑操作系统,系统中的功能十分的强大,能够智能判断出电脑的型号,然后为广大用户们安装相对应的驱动程序等,可以满足广大用户的所有 ...

  8. 电脑公司win11 32位官方版镜像v2021.07

    电脑公司win11 32位官方版镜像v2021.07是一款全新的电脑系统,用户可以轻松感受到微软在新版本系统方面的强大性.无论开机菜单的实用性还是桌面的简洁性都给用户带来了全新的感受.而且对于海量功能 ...

  9. 【点击链接,自动下载安装APP,小米公司的坑】在浏览器中, 我们以为回退就能解决误点击。其实是不管用的。

    2018-5-29,更新解释 不是西瓜视频的坑. 是小米手机,小米公司的坑,浏览器点击什么链接后,后台或下拉菜单中就自动开始了下载安装  APP. 这类链接大多数是广告链接.小米手机没有提示提醒,也不 ...

最新文章

  1. 《从缺陷中学习C/C++》——6.15 试图产生的指针很可能不存在
  2. 哈佛大学 NLP 组开源神经机器翻译系统 OpenNMT
  3. Java中的intern变量的讲解
  4. matlab有模糊分析,用matlab进行模糊综合评判_模糊综合评判matlab
  5. nodejs的内存管理,垃圾回收机制
  6. INTERNET的完整形式是什么?
  7. android butterknife使用详解
  8. Spark性能优化指南——基础篇
  9. windows x64 软件约定
  10. python 面授_5天Python实战营(面授)
  11. AndroidStudio安卓原生开发_Activity的启动方法_隐式启动2种方法_activity关闭---Android原生开发工作笔记83
  12. bash linux .ee,Linux下Bash shell学习笔记.md
  13. Python3 | UserWarning: findfont: Font family [‘SimHei‘] not found. Falling back to DejaVu Sans.
  14. Opencv之人脸识别
  15. 期刊论文公式编号、居中技巧
  16. linux yum apr,CentOS安装、配置APR和tomcat
  17. java epoch_获取Java中的Epoch的天数,周数和月份
  18. eval函数python原理_Python 中 eval 函数的神奇用法
  19. 原力计划S5上榜博主名单公布(第四期已更新)
  20. java中如何将字符串转化为字符_如何在Java中将字符串转换为运算符?

热门文章

  1. 安装指定版本的Mariadb数据库
  2. IMX系列设备树引脚复用解析
  3. Spotify的牛逼是如何炼成的?
  4. 游戏设计-《游戏改变世界》-思维导图
  5. linux系统ttl端口,利用TTL值来鉴别操作系统
  6. 基于51单片机的烟雾火灾报警器proteus仿真设计
  7. Unity Render Texture 的使用
  8. 基于SSM的售后故障报修服务管理系统(维修人员\售后人员\零单件\维修资料\顾客客户管理\故障量统计分析)javaweb/j2ee/php/asp.net/C#
  9. 身份证验证接口API(仅需一行代码,公安部实时接口)
  10. 小睿睿的等式 (思维dp)