IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> 知道公司抢“坑位”是什么意思吗? -> 正文阅读

[PHP知识库]知道公司抢“坑位”是什么意思吗?

一、 故事起源

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

左右扫视,没有坑位,灵光一闪,换了楼层,找到坑位, 拉完粑粑接着回去写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 没有返回false
        boolean 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) // overflow
                throw 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) // underflow
                throw new Error("Permit count underflow");
            // cas
            if (compareAndSetState(current, next))
                return;
        }
    }
	// 申请使用所有剩余可用凭证
    // 就是保洁来打扰 锁了所有没有人在使用的坑位
    final int drainPermits() {
        for (;;) {
            int current = getState();
            // cas设置为0
            if (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 (;;) {
                // 判断有没有人排队 有就返回-1
                if (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 initialize
            if (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();
            // 看看是不是head
            if (p == head) {
                // 如果前边是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);
    }
}

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

三、还有谁

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

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

在这里插入图片描述

  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-09-20 15:33:56  更:2021-09-20 15:35:59 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/19 2:18:50-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码