锁
最后更新于:2022-04-02 04:29:00
## 锁
[TOC=3,8]
>[danger] 锁在并发编程中很重要,很多时候为了保证业务逻辑、数据正确,就必须要对程序的执行过程,或者资源(数据资源等多种资源)进行加锁。
* * * * *
### 锁的思考
既然锁的本质就是保证过程的顺序性(锁过程,而不是静态资源,其实初衷是要锁静态资源,但是由于一个操作里面往往需要锁多个静态资源,而这样的操作又有很多种,这就导致锁的情况很复杂,不可控,极易出现死锁,并且数据库性能也有问题,所以我们才另辟蹊径转而去锁过程的,因为我们发现,如果我们给所有的过程排队,资源竞争的问题就不存在了),而过程可以分类的,所以可以做一个宽锁 窄锁的设计 key jy:order jy:order:goods:3 宽锁覆盖域下面的所有窄锁,获取窄锁时依次获取所有的宽锁(为了提高性能,先判断锁是否注册,而不是直接获取)
比如后台批量生成会员卡号就可以用一个宽锁(保证同一批次的卡卡id是连续的)
而购买商品就可以用一个窄锁
不知道这个思想业界是不是已经有了
> 锁的本质其实是排队,不同的锁排队的空间和时间不同而已。
> 对于竞态资源做上锁处理防止并发问题,或者换一种思路,不让其变成竞态资源问题,比如消息队列取模分开处理,但是对于账户资源处理的问题,就只能用锁排队处理了。
*****
#### php文件锁实战
实现单例一个操作,**保证同一时刻只有一个线程(最小粒度的执行实例 / 程序执行流的最小单元)在执行此操作** ,其他进程看见它正在执行,就自觉的走开。
```php
$fp = fopen(config('lock_path') . 'action/incrementGet.txt', 'w+');
// LOCK_NB 获取锁,非阻塞型,也就是一旦 获取锁失败/加锁失败 则直接返回false,设置为LOCK_EX则会一直等待加锁成功才返回()
// 这个用数据库的行锁也没用,也是阻塞获取锁的,要的就是高效,发现有锁,直接退出,如果检测是否有锁还要一直傻傻的阻塞等着那要你干什么
// 无奈,LOCK_NB好像用不了
// 原来要用这个姿势:LOCK_EX | LOCK_NB
// http://www.php.net/manual/zh/function.flock.php 看来范例还是很有用的
if (flock($fp, LOCK_EX | LOCK_NB)) {
Db::startTrans();
$errMsg = '';
try {
} catch (\Exception $e) {
Db::rollback();
$errMsg = $e->getMessage() ?: '系统忙!';
$errCode = $e->getCode() ?: 1;
}
if ($errMsg == '') {
// 成功就将当前写入,成为下一个点,否则的话下次继续,这样确保了只有成功才会“移动这个点”
fwrite($fp, $end_update_time);
} else {
trace($errMsg, 'error');
}
// 无论成功还是失败,都要释放锁
flock($fp, LOCK_UN);
} else {
// 当前已在运行,直接退出
echo 'Be gone';
}
fclose($fp);
```
封装成函数:
```php
// 实现的锁,无阻塞,无等待
function lock($fp, $ok, $gone)
{
try {
if (is_string($fp)) {
$lockFile = $fp;
if (!file_exists($lockFile)) {
file_put_contents($lockFile, '');
}
if (!$fp = fopen($lockFile, 'r')) {
throw new \Exception("unable to open lock file!");
}
}
if (flock($fp, LOCK_EX | LOCK_NB)) {
// 获得锁
echo PHP_EOL . 'lock ok!' . PHP_EOL;
return $ok();
} else {
// 当前已在运行,直接退出
echo PHP_EOL . 'Be gone' . PHP_EOL;
return $gone();
}
} catch (\Exception $e) {
throw $e;
} finally {
// echo "string";
// 无论成功还是失败,都要释放锁
flock($fp, LOCK_UN);
// 保证无论怎样都会释放文件资源句柄
// 如果 没有释放文件锁 或者 没有关闭文件句柄 也没事,php脚本执行完毕也会自动释放资源的,操作系统会回收的,所以不用担心没有关闭和释放会一直锁着独占,除非此php脚本一直没有结束
fclose($fp);
// This overrides the exception as if it were never thrown
// return "\nException erased";
}
}
```
sql 数据库 乐观锁也是可以实现这样无阻塞不等待的锁的。
伪代码:
```php
// 阻塞版就是用行锁/悲观锁,非阻塞版用乐观锁
public function lock(k, ok, gone) {
res = select id from lock where k = k and lock = 0
if (res == 0) {
// 获取锁失败,该k当前已被上锁占用
gone();
return false;
}
res = update from lock where k = k and lock = 0 set lock = 1
if (res == 1) {
// 获得锁后的操作
ok();
// 释放锁
update from lock where k = k set lock = 0
return true;
} else {
// 获取锁失败
gone();
return false;
}
}
```
### 用数据库来做锁合适吗?
其实很多时候我们只是想锁过程而已,现在都利用数据库实现这个锁了,这样做不合适,这并不是数据库应该解决的问题,应该改用文件锁/redis锁,来锁过程
我们明明是需要锁临界过程,却用数据库来做锁,简单的还好,尤其是很复杂的sql,还将面临很多引发死锁的潜在问题,这显然不合适,这不止是隔靴搔痒了,而是手痒挠背了。
*****
### 还有一个细节要注意:锁的颁发者
使用文件锁时,锁的颁发者就是操作系统,只有一个操作系统,所以颁发唯一是可靠的。
使用数据库锁时,向数据库申请锁,锁是数据库颁发的,所以此时锁都要以向这个数据库申请为准,锁的 申请/获取 和 验证/检查 都要以这个数据库为目标。(软件都是封装,数据库底层也可能会向操作系统申请锁。)
>[tip] php有个非线程安全的版本 [php版本nts和ts的区别。 - gltv587的博客 - CSDN博客](https://blog.csdn.net/gltv587/article/details/79054571)
*****
[Linux文件锁flock - fengbohello - 博客园](https://www.cnblogs.com/fengbohello/p/4262949.html)
> flock锁的释放非常具有特色,即可调用LOCK\_UN参数来释放文件锁,也可以通过关闭fd的方式来释放文件锁(flock的第一个参数是fd),**意味着flock会随着进程的关闭而被自动释放掉。**
[fcntl 文件锁和struct flock - 小 楼 一 夜 听 春 雨 - 博客园](https://www.cnblogs.com/kex1n/p/13055518.html)
> fcntl是一个非常强大的函数,在这里我们可以使用它来**给文件的某一个部分上锁**。
[PgSQL · 源码分析 · PG中的无锁算法和原子操作应用一则 · 数据库内核月报 · 看云](https://www.kancloud.cn/taobaomysql/monthly/213786)
> 这里的CPU的原子操作是不可中断的一个或者一系列操作, **也就是不会被线程调度机制打断的操作, 运行期间不会有任何的上下文切换。**
[分布式领域最重要的一篇论文,到底讲了什么?](https://mp.weixin.qq.com/s/FZnJLPeTh-bV0amLO5CnoQ)
> 每个系统是一个小宇宙,宇宙之外还有更大的宇宙,个体独立自治,个体之外还有更大的个体...
[基于Redis的分布式锁到底安全吗(上)?](https://mp.weixin.qq.com/s/JTsJCDuasgIJ0j95K8Ay8w)
[基于Redis的分布式锁到底安全吗(下)?](https://mp.weixin.qq.com/s/4CUe7OpM6y1kQRK8TOC_qQ)
[万字长文说透分布式锁](https://mp.weixin.qq.com/s/35aCS_5GqLyzZS3VobL6fg)
[5个步骤,教你瞬间明白线程和线程安全_CSDN资讯-CSDN博客](5个步骤,教你瞬间明白线程和线程安全_CSDN资讯-CSDN博客)
> 当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
[Chubby分布式锁服务总结 - 知乎](https://zhuanlan.zhihu.com/p/64554506)
[如何正确使用redis分布式锁](https://zhuanlan.zhihu.com/p/93947224)
> 由此, 我们可以看出单机redis锁是不安全的, 当然, 这种不安全性的来源是分布式系统的内在特性--异步性.
[死锁案例五](https://mp.weixin.qq.com/s/882oEbq2SyYfB5dEPBRvRQ)
[因 Redis 分布式锁造成的 P0 级重大事故,整个项目组被扣了绩效。。。](https://mp.weixin.qq.com/s/GaQ_jemfkUrF-PiPfpimtw)
[Google的锁,才是分布式锁?](https://mp.weixin.qq.com/s/R2FCLknar6bCvykJ1m7rJQ)
[面试难题:Redis 分布式锁,真的完美无缺吗?](https://mp.weixin.qq.com/s/aNJ_lqHF38WJ8ItHd8uUSw)
[就为了一个原子操作,其他CPU核心罢工了](https://mp.weixin.qq.com/s?__biz=MzAwNjkxNzgxNg==&mid=2247485772&idx=1&sn=0f89aa0d44ee2c1d0753e43588cca32d&source=41#wechat_redirect)
[还不懂分布式锁?看看 Redisson 是如何实现分布式锁的!](https://mp.weixin.qq.com/s/tTu7ho9By-JeWBWQKeQcKQ)
[从LONGADDER看更高效的无锁实现](https://coolshell.cn/articles/11454.html)
> 可以看到,LongAdder确实用了很多心思减少并发量,并且,每一步都是在”没有更好的办法“的时候才会选择更大开销的操作,从而尽可能的用最最简单的办法去完成操作。追求简单,但是绝对不粗暴。
[比AtomicLong更优秀的LongAdder确定不来了解一下吗?](https://mp.weixin.qq.com/s/le-BfM6-mDftU2nGI1DmuA)
[解决分布式系统事务一致性的几种方案对比,你有更好的吗?](https://mp.weixin.qq.com/s/kzmTKKH-t6tpJ97fa6TYPg)
[一分钟实现分布式锁](https://mp.weixin.qq.com/s/d3cFp0DRw0JXkgk5ZLF7Hg)
[基于Redis的分布式锁到底安全吗(下)?](https://mp.weixin.qq.com/s/4CUe7OpM6y1kQRK8TOC_qQ)
[漫画:什么是分布式锁?](https://mp.weixin.qq.com/s/8fdBKAyHZrfHmSajXT_dnA)
[漫画:如何用Zookeeper实现分布式锁?](https://mp.weixin.qq.com/s/u8QDlrDj3Rl1YjY4TyKMCA)
[高并发环境下服务器该如何优化](https://mp.weixin.qq.com/s/VYf6vFzDcIzzCp8jHkqTSA)
> 这里说下无锁编程,就是由内核完成这个锁机制,主要是使用原子操作替代锁来实现对共享资源的访问保护,使用原子操作时,在进行实际的写操作时,使用了lock指令,这样就可以阻止其他任务写这块内存,避免出现数据竞争现象。原子操作速度比锁快,一般要快一倍以上。
[JVM和Python解释器的硬盘夜话](https://mp.weixin.qq.com/s/s9IykGHC-QY_smNprZgTmw)
> 找这篇文章,,提高锁效率,原子锁,系统内部fopen就是这样的锁
[锁的升级打怪:通俗易懂讲解偏向锁、轻量级锁和重量级锁](https://mp.weixin.qq.com/s/ohloH-32u6iJeSsU8cdjxA)
[从共享单车学临界区](https://mp.weixin.qq.com/s/qSiy9NewG1GzOQ1XriIofQ)
[Redisson是如何实现分布式锁的?](https://mp.weixin.qq.com/s/XO9uJpAdo45kjJgJjIrx5w)
[还在为不懂AQS苦恼吗?](https://mp.weixin.qq.com/s/chSjy7H4vW9X4G7oXyUYng)
[HotSpot虚拟机中具体的锁实现](https://mp.weixin.qq.com/s/-lTjCC7J5z1YM1KwVsT8cQ)
> 我们可以用等红绿灯作为例子。Java 线程的阻塞相当于熄火停车,而自旋状态相当于怠速停车。如果红灯的等待时间非常长,那么熄火停车相对省油一些;如果红灯的等待时间非常短,比如说我们在 synchronized 代码块里只做了一个整型加法,那么在短时间内锁肯定会被释放出来,因此怠速停车更加合适。
[没用过分布式锁?那你该好好看看这篇文章。](https://mp.weixin.qq.com/s/TuAQHj2_VzXFGCvUfvTtiw)
乐观锁成立的 读和写是不可分割的,这在是在同一条sql语句中
[并发编程与锁的底层原理](https://mp.weixin.qq.com/s/DX_GsBq31-cemADrqQKfqw)
[编程世界的那把锁](https://mp.weixin.qq.com/s/hGlBSagmjv6B9jdIl6uY4w)
> 我就在这里无限循环,拼命的抢, 除非我的时间片到了,被迫让出CPU, 但是我不会阻塞, 还是就绪状态,等待下一次的调度, 进入CPU继续抢。
> 无限循环,也要听从cpu分配时间片,只是不断的去申请时间片而已,具体执行还是由操作系统的调度程序来决定
[synchronized与Lock 擂台之战](https://mp.weixin.qq.com/s/Ulcs16IuPtQtAVyv2oaFlw)
[从共享单车学临界区](https://mp.weixin.qq.com/s/qSiy9NewG1GzOQ1XriIofQ)
[锁的升级打怪:通俗易懂讲解偏向锁、轻量级锁和重量级锁](https://mp.weixin.qq.com/s/ohloH-32u6iJeSsU8cdjxA)
[没用过分布式锁?那你该好好看看这篇文章。](https://mp.weixin.qq.com/s/TuAQHj2_VzXFGCvUfvTtiw)
[如何实现分布式锁?](https://mp.weixin.qq.com/s/_C6Dx1BNQxhJk4vT2ee62g)
[锁的基本概念到 Redis 分布式锁实现](https://mp.weixin.qq.com/s/9SJ7rbw1MW9Qod2OS_Kf2Q)
[共享资源那么多,如何用一把锁保护多个资源?](https://mp.weixin.qq.com/s/ObaffbZe5fnbCku2Uq3EKw)
[锁住余额,为何还会更新异常?](https://mp.weixin.qq.com/s/1-nttEL57F-h5XGE73zfYg)
[SpringBoot + Redisson实现分布式锁](https://mp.weixin.qq.com/s/TXiT733pJr7TOPDRuimuvg)
[如何避免死锁,我们有套路可循](https://mp.weixin.qq.com/s/MqVhyGcxVdIp8B_AkzzQMg)
[php多进程读写同一个文件锁的问题及flock详解 - CSDN博客](https://blog.csdn.net/zhang197093/article/details/52216081)
[php中并发读写文件冲突的解决方案(文件锁应用示例) - coder狼 - 博客园](https://www.cnblogs.com/wellsoho/p/5166467.html)
[PHP文件锁同步实例 - CSDN博客](https://blog.csdn.net/u014175572/article/details/53381049)
[PHP对文件进行加锁、解锁实例_php技巧_脚本之家](https://www.jb51.net/article/60280.htm)(LOCK_EX | LOCK_NB)
[PHP: flock - Manual](http://www.php.net/manual/zh/function.flock.php)
[搞懂“分布式锁”,看这篇文章就对了](https://www.toutiao.com/a6611354991913337347/?tt_from=weixin&utm_campaign=client_share&wxshare_count=1&api_ab_group=pyq_1×tamp=1539874485&app=news_article_lite&utm_source=weixin&iid=46408437753&utm_medium=toutiao_android&group_id=6611354991913337347)
> **互斥性:** 和我们本地锁一样互斥性是最基本,但是分布式锁需要保证在不同节点的不同线程的互斥。
**可重入性:** 同一个节点上的同一个线程如果获取了锁之后那么也可以再次获取这个锁。
**锁超时:** 和本地锁一样支持锁超时,防止死锁。
**高效,高可用:** 加锁和解锁需要高效,同时也需要保证高可用防止分布式锁失效,可以增加降级。
**支持阻塞和非阻塞:** 和 ReentrantLock 一样支持 lock 和 trylock 以及 tryLock(long timeOut)。
**支持公平锁和非公平锁(可选):** 公平锁的意思是按照请求加锁的顺序获得锁,非公平锁就相反是无序的。这个一般来说实现的比较少。
~~~
既然锁的本质就是保证过程的顺序性(锁过程,而不是静态资源,其实初衷是要锁静态资源,但是由于一个操作里面往往需要锁多个静态资源,而这样的操作又有很多种,这就导致锁的情况很复杂,不可控,极易出现死锁,并且数据库性能也有问题,所以我们才另辟蹊径转而去锁过程的,因为我们发现,如果我们给所有的过程排队,资源竞争的问题就不存在了),而过程可以分类的,所以可以做一个宽锁 窄锁的设计 key jy:order jy:order:goods:3 宽锁覆盖域下面的所有窄锁,获取窄锁时依次获取所有的宽锁(为了提高性能,先判断锁是否注册,而不是直接获取)
比如后台批量生成会员卡号就可以用一个宽锁(保证同一批次的卡卡id是连续的)
而购买商品就可以用一个窄锁
不知道这个思想业界是不是已经有了
~~~
* * * * *
**参考资料:**
- [php 锁_百度搜索](https://www.baidu.com/s?wd=php+锁&ie=UTF-8)
- [php并发加锁示例_php实例_脚本之家](http://www.jb51.net/article/94878.htm)
- [PHP并发操作下的加锁 -- 简明现代魔法](http://www.nowamagic.net/php/php_LockInConcurrent.php)
- [PHP程序中的文件锁、互斥锁、读写锁使用技巧解析_php技巧_脚本之家](http://www.jb51.net/article/81246.htm)
- [php并发加锁 - CleverCode的博客 - 博客频道 - CSDN.NET](http://blog.csdn.net/clevercode/article/details/52493568)
- [php网站的锁机制 - 航天飞哥的博客 - 博客频道 - CSDN.NET](http://blog.csdn.net/liu857279611/article/details/51587973)
- [并发下常见的加锁及锁的PHP具体实现 - xinqiyang - 博客园](http://www.cnblogs.com/scotoma/archive/2010/09/26/1836312.html)
- [非阻塞同步算法与CAS(Compare and Swap)无锁算法 - Mainz - 博客园](http://www.cnblogs.com/Mainz/p/3546347.html)
- [Memcache的并发问题和利用CAS的解决方案 | 云博](http://cloudate.net/?p=306)
- [同步、异步 与 串行、并行的区别](http://blog.csdn.net/u010231453/article/details/53542672)
- [异常处理try-catch-finally](http://www.th7.cn/Program/php/201405/201159.shtml)
- [PHP的新特性finally | 风雪之隅](http://www.laruence.com/2012/08/16/2709.html)
- [同步锁的三种实现与案例解析 - CSDN博客](http://blog.csdn.net/u013630349/article/details/78826305)
- [如何优雅地用Redis实现分布式锁](http://mp.weixin.qq.com/s/EcrVp0dOpnc4FniaBlezcA)
- [分布式锁之Redis实现(最终版)](https://www.toutiao.com/a6534781248928219651/?tt_from=weixin&utm_campaign=client_share×tamp=1523257620&app=news_article_lite&utm_source=weixin&iid=25315997380&utm_medium=toutiao_android&wxshare_count=1)
- [golang并发编程之互斥锁、读写锁详解](https://mp.weixin.qq.com/s/RZ_YuGXe_R8S9Tvo2JT4TQ)
> 锁是传统的并发程序对共享资源进行访问控制的主要手段。(锁是对并发程序访问共享资源进行访问控制的主要手段)
* * * * *
**上github找找有没有成熟的方案**
在github上找到几个很棒的库,看来遇到问题时,上github找一找总会有意想不到的收获哈^_^(你遇到的问题总会有前辈早就遇到了,并且已经有了成熟的、经得起考验的解决方案了)
- [Search · lock](https://github.com/search?l=PHP&o=desc&q=lock&s=stars&type=Repositories&utf8=✓)
- [malkusch/lock](https://github.com/malkusch/lock) \=\> [php-lock/lock](https://github.com/php-lock/lock)(Lock library to provide serialized execution of PHP code.)
- [BeatSwitch/lock](https://github.com/BeatSwitch/lock)
- [sunli1223/phplock](https://github.com/sunli1223/phplock)
* * * * *
**还找到一个任务管理的工具:**
- [jobbyphp/jobby](https://github.com/jobbyphp/jobby)
* * * * *
### 同步/异步、阻塞/非阻塞、并行/并发:
设定A为调用方,B为被调用方。
**同步:** A调用B,在B返回结果前,A被挂起阻塞,不能继续向下执行,直到B执行完毕并返回结果,A才继续向下执行。此为同步调用。
**异步:** A调用B,在B返回结果前,A不会被挂起阻塞,而是可以继续向下执行,在B执行完毕后会发送系统通知给A。此为异步调用。(至于这个异步的实现方式以及消息通知,不同系统有自己的实现)
**阻塞非阻塞:** 我们知道 进程/线程 在三态模型中有三种状态(阻塞态、就绪态、运行态),阻塞/非阻塞就是阻塞态的一种形式了。
**并行:** 多核CPU下,多个进程在各自单独的核心上运行,同一时刻可以有多个进程在时间上同时运行。
**并发:** 单核CPU下,多个进程运行,CPU一个时间片(同一时刻)只能运行其中的一个进程,每个进程快速进行上下文切换(抢占CPU的时间片),由于太快,对于我们看起来就像是同时运行多个进程一样。
#### 串行/并行
**同步(sync),是串行,阻塞的**
**异步(Asynchronous ),是并行,非阻塞的**
> 这两组词往往都是成组出现的
>[tip] **不同场景下,“同步”这个词所表示的意思可能是不同的,比如生活中“同步”往往表示;两个事物的运行频率,如:数据与XX保持同步;同步开展工作;步调保持同步一致。**
**注意:这里的理解已不再准确,请参阅:**[IO模型 · php笔记 · 看云](https://www.kancloud.cn/xiak/php-node/786007)、[同步、异步 · php笔记 · 看云](https://www.kancloud.cn/xiak/php-node/785187)
* * * * *
### 代码演练
```php
public function test()
{
$mutex = new CASMutex(4);
$amount = 0;
// 这是怎么做到保证同步串行的啊(其实理解错了,这种做不到同步锁)
$mutex->synchronized(function () use ($mutex, $amount) {
$i = 3;
while ($i > 0) {
echo $i . ' - ' . date('Y-m-d H:i:s') . '
'; ob_flush(); flush(); $i--; sleep(1); } $mutex->notify(); }); echo "我出来了
"; ob_flush(); flush(); } ``` 理解错了,这个 `CASMutex` 并不能直接帮我们做实现锁,官方实例是使用`$memcached->cas()`做锁的,所以上面的代码并没什么用,是我们自己理解错了。 接下来我们使用文件锁的实现: ```php public function test2() { echo "go~
"; ob_flush(); flush(); echo "获取或等待锁……
"; ob_flush(); flush(); $mutex = new FlockMutex(fopen(__FILE__, "r")); // 可以自己去看一下实现,当闭包执行完就会自动释放锁 $mutex->synchronized(function () { $i = 3; while ($i > 0) { echo $i . ' - ' . date('Y-m-d H:i:s') . '
'; ob_flush(); flush(); $i--; sleep(1); } }); echo "我出来了
"; ob_flush(); flush(); } ``` 这样就实现了我们想要的锁定一个程序的执行过程,对执行过程加锁,只允许一个进程执行,不能允许多个进行并行。以保证我们的业务逻辑正确。(生成二维码ID必须连续,那就意味这,这个操作需要被锁定独占才能满足要求) * * * * * 补充:忘了说我这个需求是解决什么问题,是解决一个,多行插入二维码表(有可能我一次要插入几十万条数据),但是需要保证ID是连续的(自增ID),所以就需要保证同一时刻只有一个进程能进行生成操作,类似于进程锁一样,要保证同时只有一个进程执行,这样才能保证我的业务逻辑正确,程序完全按照我预期的结果执行,在并发的时候不出现BUG。 * * * * * ### 扩展 [swoole 中的锁及其应用](https://mp.weixin.qq.com/s/BSGpBKxC_1vGttoHYlpwUg) - [Python3之多进程](http://www.toutiao.com/a6439858194382962945/?tt_from=weixin&utm_campaign=client_share&app=news_article&utm_source=weixin&iid=11683717705&utm_medium=toutiao_android&wxshare_count=1) [进程与线程的一个简单解释 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html) [Linux 守护进程的启动方法 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2016/02/linux-daemon.html) [死磕 Java 并发 - 深入分析 synchronized 的实现原理](https://juejin.im/entry/589981fc1b69e60059a2156a) [内存锁与内存事务 - 刘小兵2014](https://my.oschina.net/digerl/blog/34001#tt_daymode=1) > 锁有更复杂的实现如更新锁,意向锁,乐观锁,悲观锁,轻量级锁,偏向锁,自旋锁等等,都是针对不同的需求特征或事务等级定制的锁。事实上,这里面的很多只是使用了锁的名字,所以其实并不是锁,因为它们没有起到排它的作用如自旋锁,轻量级锁,偏向锁等等其实都不是锁。特别是乐观锁,却是通过不锁来达到锁的效果。 > 另一方面,根据被封锁的对象的大小,又可以被分为行锁,表锁,页锁,或其它范围锁,特别是对于内存数据,根据内存数据的结构与层次,锁的种类其实无限多。**但是在真正的开发工作中只要找到合适粒度的对象就可以了,不必过于较真。** > 这意味着如果要在项目中启用OO的范式进行系统开发,则对程序员的要求其实非常高。**因为对上面所述这些锁机制的深刻理解,不是一个初入者可以很容易搞明白的东西。大部分的程序员都必须在碰了很多钉子以后才可能真正学会一些东西。** > 我其实认为程序员的知识重点是应该摆在建模上面。但是世界并不完美,我们却要工作。这就要求我们不得不拥有一些应付这个不完美世界的能力。换一句话,如果世界已经是完美的,为什么我们还需要工作呢?我们还要继续努力,因为世界还不完美(这个有点象悖论,,,因为它看上去好象是我们工作的目的是为了让世界更完美,但是事实却是世界从没有变得“更”完美或永远也不可能完美。。。归根结底,我们并不是世界。我们也不存在。真正存在的从头到尾只不过是一些雄激素而已。我们以为我们存在,只不过雄激素让我们以为我们存在然后我们便可以成为我们)。 * * * * * [2017上半年的精华文章全在这里了](http://mp.weixin.qq.com/s/FplDkMl5Z0nSk9Id_SjS7Q) [编程世界的那把锁](http://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513653&idx=1&sn=e30c18c0c1780fb3ef0cdb858ee5201e&chksm=80d67af6b7a1f3e059466302c2c04c14d097c1a5de01cf986df84d4677299542f12b974dfde3&scene=21#wechat_redirect) >[danger] ……你看,单线程的逻辑正确并不表示多线程并发运行时的逻辑也能正确。 [加锁还是不加锁,这是一个问题](http://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513692&idx=1&sn=ef2416a4bb96d64db77e32d5b4c7967e&chksm=80d67a9fb7a1f3898e513cc1d9e96841610bb84aed2dc24cab2d403e74e317e3c447e45e7611&scene=21#wechat_redirect) >[info] 你们之前的synchronized 叫做悲观锁, 元老院太悲观了,总是怕你们把事情搞砸,你看现在乐观一点儿, 不也玩的挺好嘛! 每个线程都不用阻塞,不用在那个无聊的锁池里待着。 要知道,阻塞,激活是一笔不小的开销啊。 * * * * * 锁的本质是什么啊,核心是怎么做的,这个应该需要了解系统底层吧。 较真就是和自己过不去,人生苦短,最怕一生碌碌无为,还说平凡难能可贵。 * * * * * ### php真的是最好的编程语言吗? 虽然说php是最好的编程语言,但是其实php相比java来说,确实在有些方面不是很成熟,比如php本身没有提供锁机制,也没有很好的对多进程的支持和管理的方案,这些都是php的弱点。 正是php本身还存在一些问题,所以才会有一些如swoole之类的扩展的生存空间了。 我们来看一下php对自己的描述就知道了: > PHP is a popular general-purpose scripting language that is especially suited to web development > by: https://github.com/php > Java was originally developed as an alternative to the C/C++ programming languages. It is now mainly used for building desktop, Android, and web server applications. Java is owned and licensed through Oracle. > by: https://github.com/topics/java 所以任何一个东西有它的优点,就会有缺点,似乎没有最好的,一揽子的完美方案,只能根据具体场景来看怎么选择最合适的,最合适的就是最好的。 * * * * * [\[求助\] PHP有没有高效的锁同步方案?](http://m.newsmth.net/article/PHP/94666) [PHP的多进程互斥和锁定机制讨论_miceCMS开发团队](http://blog.sina.cn/dpool/blog/s/blog_5ed1dcc70100d3qk.html?vt=4) [请问抢购的时候为什么选择用PHP文件锁而不是MySQL锁机制,PHP文件锁与其对比有什么特殊优点吗? - SegmentFault](https://segmentfault.com/q/1010000006782974) [高并发下常见的缓存锁机制的PHP实现 - PHPERZ中文资讯站](http://www.phperz.com/article/14/1112/8344.html) [php 根据url自动生成缩略图并处理高并发的问题解决方法 - PHPERZ中文资讯站](http://www.phperz.com/article/15/0123/5186.html) > 任何时候都要考虑并发,只有考虑周到,才能设计出来高性能,稳定的系统。 [用memcached实现的php锁机制 - 为程序员服务](http://ju.outofmemory.cn/entry/48924) [网页搜索_php锁机制](https://so.m.sm.cn/s?q=php%E9%94%81%E6%9C%BA%E5%88%B6&by=relative&uc_param_str=dnntnwvepffrgibijbprsvdsme&dn=8747331402-4c93fc31&nt=99&nw=4G&ve=11.0.8.858&pf=145&fr=android&gi=bTkwBOl9HXrDhv3l6Di3lsqk6blEtdR9eWcYChOe27u8rio%3D&bi=800&jb=0&pr=UCMobile&sv=ucrelease1&ds=bTkwBBipMwOIa6gfb2LWSAGEXNqcc6h8lJ5chd5kXXjq7Q%3D%3D&me=AAQCzU1n1Bku3%2FKOD%2BdXI%2BXs&from=ucframe&uc_sm=1) [网页搜索_php没有java的锁](https://so.m.sm.cn/s?q=php%E6%B2%A1%E6%9C%89java%E7%9A%84%E9%94%81&uc_param_str=dnntnwvepffrgibijbprsvdsme&from=ucframe&uc_sm=1) [网页搜索_php监听数据变更](https://so.m.sm.cn/s?uc_param_str=dnntnwvepffrgibijbprsvdsme&dn=8747331402-4c93fc31&nt=99&nw=4G&ve=11.0.8.858&pf=145&fr=android&gi=bTkwBOl9HXrDhv3l6Di3lsqk6blEtdR9eWcYChOe27u8rio%3D&bi=800&jb=0&pr=UCMobile&sv=ucrelease1&ds=bTkwBBipMwOIa6gfb2LWSAGEXNqcc6h8lJ5chd5kXXjq7Q%3D%3D&me=AAQCzU1n1Bku3%2FKOD%2BdXI%2BXs&from=ucframe&by=tuijian&by2=weini&sc=&q=php%E7%9B%91%E5%90%AC%E6%95%B0%E6%8D%AE%E5%8F%98%E6%9B%B4&uc_sm=1) [php + mysql 这个下单流程怎么确保数据的完整性-云栖社区](https://m.aliyun.com/yunqi/ask/4094) > 盗我在SF的问题啊,https://segmentfault.com/q/1010000002600161 [php中有没有什么方法可以实现实时监听数据库中的某张..._慕课问答](http://www.imooc.com/qadetail/71289) > 实时只有两种,你要实时得到你要监听的事物的状态,要么事物发生变化时自己通知你,要么你定时轮询查看它。 [我是一个线程(修订版)](http://mp.weixin.qq.com/s/-BMCUuIWYE3O_oC3ZNNJRg) [JAVA CAS原理深度分析 - CSDN博客](http://blog.csdn.net/hsuxu/article/details/9467651) > 确保对内存的读-改-写操作原子执行。在Pentium及Pentium之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其他处理器暂时无法通过总线访问内存。 >[danger] 可见,内部还是用的锁,那怕是到cpu层面,也是用的锁(CPU的锁),不然对共享变量读写时怎么保证数据的正确性,虽然我们平时写代码不用考虑这个问题,编程语言对于我们来说总是安全的,但是计算机内部其实也做了安全考量和优化,以保证我们认为的那些再正常和普通的操作得以正确的执行,只不过我们忽略掉了操作系统/编程语言内部为此所做的努力而已。 [一篇文章看懂Java并发和线程安全](https://www.toutiao.com/a6513707798830776846/?tt_from=weixin&utm_campaign=client_share×tamp=1516737598&app=news_article&utm_source=weixin&iid=22069500288&utm_medium=toutiao_android&wxshare_count=1) [基于Redis的分布式锁到底安全吗(下)?](https://mp.weixin.qq.com/s?__biz=MzA4NTg1MjM0Mg==&mid=2657261521&idx=1&sn=7bbb80c8fe4f9dff7cd6a8883cc8fc0a&chksm=84479e08b330171e89732ec1460258a85afe73299c263fcc7df3c77cbeac0573ad7211902649&scene=21#wechat_redirect) > 一个进程持有锁L,发起了请求R,但是请求失败了。另一个进程获得了锁L并在请求R到达目的方之前执行了一些动作。如果后来请求R到达了,它就有可能在没有锁L保护的情况下进行操作,带来数据不一致的潜在风险。 * * * * * 锁的本质其实是对那些需要使用竞态资源的操作进行排队处理。 锁的本质作用就是要**控制对竞态资源的访问(读和写)**,数据库的行锁,表锁,文件锁,内存锁,总线锁,任何锁,只要能达到**控制对竞态资源的访问的目的**,那么本质就没有什么不同。 比如开发中,需要锁住用户,如果数据库不支持,那么用文件锁也是可以的(用户ID生成文件名的KEY),**只要所有实例都遵循相同的数据访问规则,那么锁的工作就是正常的。** 不过如果数据库支持,那么直接锁数据库就更好了。 **所有实例都遵循相同的数据访问规则:** > 只有将所有线程都设计成遵守相同的数据访问规则,互斥机制才能正常工作。 **操作系统并不会为我们做数据访问的串行化。** 如果允许其中的某个线程在没有得到锁的情况下也可以访问共享资源,那么即使其他的线程在使用共享资源前都申请锁,也还是会出现数据不一致的问题。(UNIX环境高级编程第三版 P-380) * * * * * [伯克利推出世界最快的KVS数据库Anna:秒杀Redis和Cassandra](http://mp.weixin.qq.com/s/3WmGpZkEuSz-ox_2CPCsqg) > 针对同一个键值的并行更新操作会被串行化,它们需要同步机制来防止多个线程同时更新同一个键值。 [编程中的14种锁,你知道几个?](https://www.toutiao.com/a6534966157194035719/?tt_from=weixin&utm_campaign=client_share×tamp=1521551602&app=news_article_lite&utm_source=weixin&iid=25315997380&utm_medium=toutiao_android&wxshare_count=1) [支付平台的架构设计](https://mp.weixin.qq.com/s/QDqtnj02gfxdlyfTKOOnGQ) > **锁的本质其实是排队,不同的锁排队的空间和时间不同而已。** > **其实都是将并行强制变为串行的解决方案。** [Java中如何提升锁性能](https://mp.weixin.qq.com/s/xQnV9PC6vzMjal-ED-XNjw) [Java并发编程之阻塞队列](https://mp.weixin.qq.com/s/6XXpPykDtFfnAgnEXrZLKA) [如何优雅地用Redis实现分布式锁](https://mp.weixin.qq.com/s/EcrVp0dOpnc4FniaBlezcA) >[danger] **锁其实并不是要锁数据本身,锁的本质是要锁操作的(对操作进行排队),即控制操作的顺序**,比如,针对同一条数据的多路操作,1. 消息发送操作;2. 库存扣减操作,这两路(类)操作要改的数据虽然在同一行,但其实它们并不是竞争关系,它们各自的目标数据是相互独立的不同字段,所以并不算共享数据(竞态资源),如果把锁放在数据行上,那就太影响性能了,**其实这两路操作互不影响,只是各自要防止同类操作的多进程并发而已,而不是防对方**,所以只要用针对各路操作的锁就可以了,这才是锁的本质意义。(通常说的状态其实也是这个意思,可理解为操作的状态,如:操作进行中,操作已完成/当前无操作) ~~~ 然而这样就能保证锁一定会被释放吗?考虑这样一种情况:代码执行到doSomething()方法的时候,服务器宕机了,这个时候finally代码块就没法被执行了,因此在这种情况下,该锁不会被正常释放,在上述案例中,可能会导致任务漏算。因此,这种方案的第一个问题是会出现锁无法正常释放的风险,解决这个问题的方法也很简单,Redis设置key的时候可以指定一个过期时间,只要获取锁的时候设置一个合理的过期时间,那么即使服务器宕机了,也能保证锁被正确释放。 要考虑任何的极端情况,做好容错,留后路,未雨绸缪,保证万无一失。 仔细分析一下getLock方法,该方法并不是原子性的,当一个客户端检查到某个锁不存在,并在执行setKey方法之前,别的客户端可能也会检查到该锁不存在,并也会执行setKey方法,这样一来,同一把锁就有可能被不同的客户端获取到了。获取锁必须是原子操作 那么这种方案就没有问题了吗?很遗憾地说,这种方案也是有问题的。原因在于上述释放锁的操作不是原子性的 只要不是原子操作,就不得不仔细考虑其并发问题! ~~~ [漫画:什么是分布式锁?](https://mp.weixin.qq.com/s/8fdBKAyHZrfHmSajXT_dnA) >[danger] **原子性的重要性,任何极端情况都要考虑**,无论什么极端情况都要保证数据可靠性,这是最重要的,否则其它的谈都不用谈。试问连数据可靠性都不能保证的系统谁敢用,过家家吗,所以要重视这点,必须严谨。 > > **原子性其实本质就是代码,指令的原子性。** 一个操作可能有多行代码,多个指令组成,这些代码,指令的执行细节,都成功,都失败,结果一致性,就显得重要了,这和事务内的多条sql一样,**严谨的软件必须考虑原子性。时刻记住,你必须对你写的每一条代码负责**,你写下它时有没有考虑过它会不会执行失败,和会不会被执行,**如果没有被执行或者执行失败你有应对措施没有,还是不暇\[假\]思索,心安理得,一厢情愿的认为它一定会执行,一定会执行成功**,而不作任何考虑。**你的良心不会痛吗?** 是什么给了你勇气让你如此自信,如果真是这样,可以说你写的软件是弱鸡,已经千疮百孔了,毫无安全性可言。 > > **这并不是钻牛角尖,自找麻烦,也不是杞人忧天,俗话说小心使得万年船,更何况代码即科学,而科学必须严谨,必须一丝不苟不能怕麻烦,否则就不是科学了。** 这并不是说我们平常写代码/做事时要经常 [瞻前顾后、谨小慎微](https://www.zybang.com/question/7b973c6578ef148dd67e79126098b9d2.html),**而是对待科学要有对待科学应该有的严谨、务实的态度。** > > 代码即科学,科学工作必须要**求真务实**,模棱两可和不求甚解是不行的。 [漫画:如何用Zookeeper实现分布式锁?](https://mp.weixin.qq.com/s/u8QDlrDj3Rl1YjY4TyKMCA) > php的运行模式和java不同,不是在一个JVM中运行的,每个进程彼此独立,没有统一管理的,所以php中要实现锁,就相当于这个分布式环境中锁的关系了。那个文件锁,是php发出的系统调用,如果php进程挂了,操作系统应该会检测到并自动释放文件锁吧。 > > 系统并没有直接提供锁,锁其实是一种业务的产物,都是通过某种约定,规则,或方式来实现锁的,哪怕系统底层直接提供锁,其实也是通过一些方式实现的,而我们无需关系怎么实现,只要知道可以达到锁的目的就可以了。所以什么是锁,锁并不是指某个东西,门上一把锁,组织你不能进,这个铁东西就是锁,指示灯显示厕所里面有人不能进,这个有人不能进的规则就是锁,约束了你不能进。所以锁是宽泛的,它的意义就是指定某种规则,让其它人遵守,以达到某种目的。 [我是一个线程(修订版)](https://mp.weixin.qq.com/s/-BMCUuIWYE3O_oC3ZNNJRg) > 锁顺序要保持一致才不会出现死锁,也就是要遵循相同的规则。 [为什么分布式一定要有redis?](https://mp.weixin.qq.com/s/gEU8HtsQNPXY8bzkK-Qllg) > 一刹那者为一念,二十念为一瞬,二十瞬为一弹指,二十弹指为一罗预,二十罗预为一须臾,一日一夜有三十须臾。 > 那么,经过周密的计算,一瞬间为0.36 秒,一刹那有 0.018 秒.一弹指长达 7.2 秒。 >[danger] **任何东西都要去深入思考,做到面面俱到,考虑每一种可能性,应对每一种突发情况,这样才能保证是严谨的。** 避重就轻,拈轻怕重,避实就虚,怕麻烦,不肯深入问题的本质,这样永远都只会停留在表面,无法解决核心问题。 [【原创】分布式之延时任务方案解析 - 孤独烟 - 博客园](https://www.cnblogs.com/rjzheng/p/8972725.html) [【原创】分布式之消息队列复习精讲 - 孤独烟 - 博客园](http://www.cnblogs.com/rjzheng/p/8994962.html) [重入锁是什么意思,锁降级有什么好处-CSDN论坛](https://bbs.csdn.net/topics/390276124) [[MySQL不为人知的主键与唯一索引约束](https://mp.weixin.qq.com/s/IE31GSDP0Ndjzc8kFj4Ukw)]() [从单一架构到分布式交易架构,网易严选的成功实践](https://mp.weixin.qq.com/s/nv3Ht7OqTYQw31QFDX3gNg) >[danger] **没有完美的架构设计,世上也没有绝对的事情,没有谁能保证绝对可靠、安全和高可用,但我们有补偿和容错(类似还有重试,确认等机制),也是能做到万无一失的。** * * * * * [关于Java锁机制面试官会怎么问](https://mp.weixin.qq.com/s/no6S3PUYoCjKLyzuYtOqSQ) * * * * * ### 锁的本质 **锁是一种规定共享资源的访问规则的强制手段。** 系统并没有直接提供锁,锁其实是一种业务的产物,都是通过某种约定,规则,或方式来实现锁的,哪怕系统底层直接提供锁,其实也是通过一些方式实现的,而我们无需关系怎么实现,只要知道可以达到锁的目的就可以了。所以什么是锁,锁并不是指某个东西,门上一把锁,组织你不能进,这个铁东西就是锁,指示灯显示厕所里面有人不能进,这个有人不能进的规则就是锁,约束了你不能进。所以锁是宽泛的,它的意义就是指定某种规则,让其它人遵守,以达到某种目的。 [七大进程间通信和线程同步](https://mp.weixin.qq.com/s/VV_mTpuOYFIZRb94mwhr2Q) * * * * * ### 从业务代码来看锁的应用案例 **锁key命名:** 操作类型 [+ 竞态资源ID(竞态资源唯一标识)] >[tip] (竞态资源ID不是必须的,有时也会有直接所某项操作的情况,比如code项目发货操作QrCode:add()和Order:delivery()操作) >[danger] **确保:** 同一时刻,只能有一个进程 **“对此数据执行此种操作”** ,保证 **“对此数据执行的此种操作”** 是串行的,从而不会出现并发问题。 **(请仔细体会这句话的每一个字)** ```php // 准备锁文件 // lock:锁这路操作,而不是直接锁数据行! // 锁key命名:操作类型 [+ 竞态资源ID(竞态资源唯一标识)](竞态资源ID不是必须的,有时也会有直接所某项操作的情况,比如code项目发货操作QrCode:add()和Order:delivery()操作) // **确保:同一时刻,只能有一个进程“对此数据执行此种操作”,保证“对此数据执行的此种操作”是串行的,从而不会出现并发问题。(仔细体会这句话的每一个字)** $lock_key = 'intention_notice_' . $id; $lockFile = config('lock_path') . $lock_key; file_put_contents($lockFile, date('Y-m-d H:i:s'), LOCK_EX); $mutex = new \malkusch\lock\mutex\FlockMutex(fopen($lockFile, "r")); // 锁住 return $mutex->synchronized(function () use ($bankAccount, $amount) { // 同步操作 // 这部分加锁了的,为同步操作,能保证这部分代码同一时刻只有一个进程在执行 $balance = $bankAccount->getBalance(); $balance -= $amount; if ($balance < 0) { throw new \DomainException("You have no credit."); } $bankAccount->setBalance($balance); }); // 释放锁(PHP脚本执行完毕后结束进程,就会自动交出锁,申请文件锁其实是一次系统调用) ``` >[tip] 思考:执行系统调用的程序突然被 kill 了,那么系统会怎么回收资源呢,比如当执行的系统调用为一个文件锁时,系统应该会自己回收这个锁吧。在系统层面来说,文件锁其实是一个被控制独占访问的系统资源,而独占能力是由操作系统提供的。 #### 更多案例 ```php https://github.com/php-lock/lock Mutex::synchronized() executes code exclusively. This method guarantees that the code is only executed by one process at once. Other processes have to wait until the mutex is available. The critical code may throw an exception, which would release the lock as well. `Mutex::synchronized()` 会同步执行代码。这将保证代码同一时刻只能被一个进程所执行,而其他进程在此过程中则必须等待直到得到互斥锁。关键代码可能抛出异常,这将释放锁。 ----------- https://coding.net/u/xiasf/p/code/git/blob/master/application/admin/controller/QrCode.php // 生成二维码 public function add(Request $request) { // 使用文件锁,使生产二维码的操作不能够并发执行,以保证起止连续,程序的逻辑才能正确 $mutex = new FlockMutex(fopen(__FILE__, "r")); return $mutex->synchronized(function () use ($request) { // 生成逻辑…… }); } ``` * * * * * ### 形象展示为什么会有并发问题 ![](http://cdn.aipin100.cn/18-7-6/49426434.jpg) 这就是并发问题,3操作对于进程2是不可见的,所以4这一刻还以为状态还是2时的,但其实不是的,也就是持有了旧数据,既然持有旧数据,那么程序可能就会和预期不一致,这就是并发问题。 小明 可用 小红 可用 小明 改为不可用 小红 刚看了是可用的 **用锁的话:** 小明 只能我一个人看,可用 小红 有人再看,那我等他看完了我再看(什么都不做,就守在这等着他) 小明 改为不可用,走了 小红 轮到我看了,也只准我一个人看,不可用 小红 不可用啊,那我走了 #### 有锁后的特性 数据不会被两个人同时共享,每个人,每一刻持有的都是最新数据,不可能持有到旧数据。 将并行问题变成串行来解决,这也体现了锁其实就是一种排队,或者说是控制并发程序访问共享资源的顺序和规则。 * * * * * ### php与Java java是老大哥,对很多东西都提供一揽子解决方案,并且是官方的。而php就不是这样,很多时候并没有官方的解决方案,比如没有锁的实现,每个进程相互独立。这么来比喻,java五脏俱全,php灵活自由。如果你要用php写出高级功能,需要具备深厚的计算机知识,而java很多都是开箱即用的。java是一套上古神兵(注意是一套哦),php是一把绝世好剑。 * * * * * ### 带有“候补”功能的分布式锁编排服务 > 锁的本质是为了保证业务的并发安全性,即锁是服务于业务的,所以我们并不是单纯的 key 为真正的锁,这也是为什么 goods:1 要等待 goods:1-user:2 释放才能获得锁的原因,因为他们存在业务的竞态关系,而不只是单纯的看 key,这也正是锁编排的**编排**意义的体现。 1. 请求锁,没请求到锁就支持重试(支持重试次数和超时时间参数) 2. 请求锁,没有请求到锁时,尝试提交候补(候补也有限额的) 3. 锁释放时,优先给到候补列表中的等待者 4. 提前定义好编排规则,使用时一次性提前获取业务过程中的全部所需锁(如 goods:1-user:2),以避免出现死锁 5. 锁分析(超时锁统计,耗时锁分析,锁使用情况统计等等,方便定位程序分析优化业务代码) 6. 锁告警(超时锁,耗时锁 发生告警通知) ---- ### 锁的公平性 轮询争夺的锁表面上看起来是公平的,每个请求都有机会获取到锁,谁能获取到锁,往往看运气(网络到达、进程调度等因素),看起来很公平是吗,但其实并不公平。 试想一下大家去窗口买票时都拼命往里面挤,谁力气大,谁拳头硬,谁就能能挤到前面去先买到票,仔细想一下这公平吗,这算哪门子公平,弱小的人挤哭了都买不到票,挤不过别人,就算来得早也没用,这是弱肉强食的丛林法则,不是文明社会的秩序公平。 所以我们日常使用的并不是公平的锁,那么什么是公平的锁?你应该问下那个弱小挤不进去买不到票的人:“文明排队,先来后到!” 是啊,排队,先来后到,这样才公平嘛。 要获取锁,得先登记,等轮到你了自然会把锁给你,不用争,不用抢,不用打架,人人都能买到票。想要先买到票就别睡懒觉早点来排队,早起的鸟儿有虫吃,没有比这更公平的了。 公平锁除了比非公平锁在道德上更公平外,还有性能优势,由于公平锁采用排队方式,所以每个人只用关注自己前面一个人有没有排到了就行,大家不用再像一群恶狼一样盯着可怜的窗口,而那些在挤啊挤中摩擦产生的能量都被浪费掉了,最终只有一个人抢到票,其他人都精疲力竭,败兴而归,这是极大的浪费,不符合勤俭节约的精神。 > 但非公平锁也并不是一无是处,因为公平锁的缺点也很明显,它实现起来要比非公平锁繁琐不少,反而非公平锁实现起来简单粗暴,使用方便,更适合在某些简单的场景下使用。 但肯定又有较真的人会跳出来说:“大家在登记时不也存在争抢的情况吗,不还是不公平”,这么说吧,取票叫号的窗口绝对没有自助打饭的窗口热闹!想想那些打自助餐时在你前面磨磨唧唧选择困难症的人们吧,可不急死你了是吗,哈哈哈。—— 我们不能完全做到公平,但我们可以去努力改变,努力让世界变得更加公平! https://github.com/HelloGitHub-Team/HelloZooKeeper/blob/main/content/2/content.md [图解:为什么非公平锁的性能更高?](https://juejin.cn/post/6998317686836953124) > 和我们的观点相反 ---- ### 写锁的读 查询的操作会加读锁,读锁只与写锁冲突,所以多个请求并发同时来读是可以的,但是如果某个时刻正在写数据,其他的读操作就会被阻塞,直到写操作完成,写锁释放后,读锁才能获取到。 不让读“快照副本”是因为,这样会导致理论上的争议:某个时刻数据正在写,写操作还没有完成,也就不确定数据最终的内容,此时有人来读数据你期望读到什么? 有人可能会说:“还没写完那你就把之前的数据先给我吧,我不在乎你正在写的所谓的最新数据,你不要让我等就行”。但较真的人会说:“我要读的就是此时此刻的数据,而此时的数据还在写,那我肯定要等一下的,等你写完了再给我不就行了,你拿旧数据给我有什么意义!不要害朕!!!” 有的时候,对于一些重要的读操作,也有给读操作加写锁的情况,加了写锁的读操作就不能和其他的请求同时读/写了,这么做是为了保证,某个时刻 只有这一个 读操作 能接触到数据(读和写)。这样做虽然会降低系统吞吐,但通常只是在重要的时刻使用,并且是把数据读到内存后就立马释放锁了,后续对数据的操作在内存中进行操作,所以这个 写锁的读 只有一瞬间而已,不会有性能问题。(至于到底什么要的重要操作,非这样不可,暂时还没想到这样有什么特别的意义) 读锁底层是什么样的,真的存在所谓的 同时 “读”吗? https://github.com/HelloGitHub-Team/HelloZooKeeper/blob/main/content/4/content.md ---- ### 现实世界的锁 锁并不是只有开发时需要用到,它并不是编程世界的专有,相反软件开发中的一些概念其实都源于生活,是对生活中某些场景的描述。现实世界中也有很多锁的例子,我们每天都在和它打交道,只是它已融入我们的生活,以至于忘记关注到它们的存在了。 锁对应现实的例子: 1. 交通红绿灯🚥 2. 门锁 3. 取票叫号 如果没有锁会怎么样,上厕所不锁门你会很尴尬;红绿灯罢工,交通会瘫痪,会堵车,会发生更多的车祸;...。 **锁无处不在,因为资源是有限的,总是会发生负载**,马路上总不止一辆车,饭店不止一个人要吃饭,...。所以 锁即排队,即秩序的守护者。 ---- [Python Twisted介绍_技术笔记本-CSDN博客_twisted](https://blog.csdn.net/hanhuili/article/details/9389433) > 在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。这种**明确的执行顺序和串行化处理的行为**是很容易推断得出的。**如果任务之间并没有互相依赖的关系,但仍然需要互相等待的话这就使得程序不必要的降低了运行速度。** > 通常我们写的代码,都是自上而下按顺序执行的,即都是 “明确的执行顺序和串行化处理的行为”。 ---- last update:2018-8-6 17:18:37
';
'; ob_flush(); flush(); $i--; sleep(1); } $mutex->notify(); }); echo "我出来了
"; ob_flush(); flush(); } ``` 理解错了,这个 `CASMutex` 并不能直接帮我们做实现锁,官方实例是使用`$memcached->cas()`做锁的,所以上面的代码并没什么用,是我们自己理解错了。 接下来我们使用文件锁的实现: ```php public function test2() { echo "go~
"; ob_flush(); flush(); echo "获取或等待锁……
"; ob_flush(); flush(); $mutex = new FlockMutex(fopen(__FILE__, "r")); // 可以自己去看一下实现,当闭包执行完就会自动释放锁 $mutex->synchronized(function () { $i = 3; while ($i > 0) { echo $i . ' - ' . date('Y-m-d H:i:s') . '
'; ob_flush(); flush(); $i--; sleep(1); } }); echo "我出来了
"; ob_flush(); flush(); } ``` 这样就实现了我们想要的锁定一个程序的执行过程,对执行过程加锁,只允许一个进程执行,不能允许多个进行并行。以保证我们的业务逻辑正确。(生成二维码ID必须连续,那就意味这,这个操作需要被锁定独占才能满足要求) * * * * * 补充:忘了说我这个需求是解决什么问题,是解决一个,多行插入二维码表(有可能我一次要插入几十万条数据),但是需要保证ID是连续的(自增ID),所以就需要保证同一时刻只有一个进程能进行生成操作,类似于进程锁一样,要保证同时只有一个进程执行,这样才能保证我的业务逻辑正确,程序完全按照我预期的结果执行,在并发的时候不出现BUG。 * * * * * ### 扩展 [swoole 中的锁及其应用](https://mp.weixin.qq.com/s/BSGpBKxC_1vGttoHYlpwUg) - [Python3之多进程](http://www.toutiao.com/a6439858194382962945/?tt_from=weixin&utm_campaign=client_share&app=news_article&utm_source=weixin&iid=11683717705&utm_medium=toutiao_android&wxshare_count=1) [进程与线程的一个简单解释 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html) [Linux 守护进程的启动方法 - 阮一峰的网络日志](http://www.ruanyifeng.com/blog/2016/02/linux-daemon.html) [死磕 Java 并发 - 深入分析 synchronized 的实现原理](https://juejin.im/entry/589981fc1b69e60059a2156a) [内存锁与内存事务 - 刘小兵2014](https://my.oschina.net/digerl/blog/34001#tt_daymode=1) > 锁有更复杂的实现如更新锁,意向锁,乐观锁,悲观锁,轻量级锁,偏向锁,自旋锁等等,都是针对不同的需求特征或事务等级定制的锁。事实上,这里面的很多只是使用了锁的名字,所以其实并不是锁,因为它们没有起到排它的作用如自旋锁,轻量级锁,偏向锁等等其实都不是锁。特别是乐观锁,却是通过不锁来达到锁的效果。 > 另一方面,根据被封锁的对象的大小,又可以被分为行锁,表锁,页锁,或其它范围锁,特别是对于内存数据,根据内存数据的结构与层次,锁的种类其实无限多。**但是在真正的开发工作中只要找到合适粒度的对象就可以了,不必过于较真。** > 这意味着如果要在项目中启用OO的范式进行系统开发,则对程序员的要求其实非常高。**因为对上面所述这些锁机制的深刻理解,不是一个初入者可以很容易搞明白的东西。大部分的程序员都必须在碰了很多钉子以后才可能真正学会一些东西。** > 我其实认为程序员的知识重点是应该摆在建模上面。但是世界并不完美,我们却要工作。这就要求我们不得不拥有一些应付这个不完美世界的能力。换一句话,如果世界已经是完美的,为什么我们还需要工作呢?我们还要继续努力,因为世界还不完美(这个有点象悖论,,,因为它看上去好象是我们工作的目的是为了让世界更完美,但是事实却是世界从没有变得“更”完美或永远也不可能完美。。。归根结底,我们并不是世界。我们也不存在。真正存在的从头到尾只不过是一些雄激素而已。我们以为我们存在,只不过雄激素让我们以为我们存在然后我们便可以成为我们)。 * * * * * [2017上半年的精华文章全在这里了](http://mp.weixin.qq.com/s/FplDkMl5Z0nSk9Id_SjS7Q) [编程世界的那把锁](http://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513653&idx=1&sn=e30c18c0c1780fb3ef0cdb858ee5201e&chksm=80d67af6b7a1f3e059466302c2c04c14d097c1a5de01cf986df84d4677299542f12b974dfde3&scene=21#wechat_redirect) >[danger] ……你看,单线程的逻辑正确并不表示多线程并发运行时的逻辑也能正确。 [加锁还是不加锁,这是一个问题](http://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513692&idx=1&sn=ef2416a4bb96d64db77e32d5b4c7967e&chksm=80d67a9fb7a1f3898e513cc1d9e96841610bb84aed2dc24cab2d403e74e317e3c447e45e7611&scene=21#wechat_redirect) >[info] 你们之前的synchronized 叫做悲观锁, 元老院太悲观了,总是怕你们把事情搞砸,你看现在乐观一点儿, 不也玩的挺好嘛! 每个线程都不用阻塞,不用在那个无聊的锁池里待着。 要知道,阻塞,激活是一笔不小的开销啊。 * * * * * 锁的本质是什么啊,核心是怎么做的,这个应该需要了解系统底层吧。 较真就是和自己过不去,人生苦短,最怕一生碌碌无为,还说平凡难能可贵。 * * * * * ### php真的是最好的编程语言吗? 虽然说php是最好的编程语言,但是其实php相比java来说,确实在有些方面不是很成熟,比如php本身没有提供锁机制,也没有很好的对多进程的支持和管理的方案,这些都是php的弱点。 正是php本身还存在一些问题,所以才会有一些如swoole之类的扩展的生存空间了。 我们来看一下php对自己的描述就知道了: > PHP is a popular general-purpose scripting language that is especially suited to web development > by: https://github.com/php > Java was originally developed as an alternative to the C/C++ programming languages. It is now mainly used for building desktop, Android, and web server applications. Java is owned and licensed through Oracle. > by: https://github.com/topics/java 所以任何一个东西有它的优点,就会有缺点,似乎没有最好的,一揽子的完美方案,只能根据具体场景来看怎么选择最合适的,最合适的就是最好的。 * * * * * [\[求助\] PHP有没有高效的锁同步方案?](http://m.newsmth.net/article/PHP/94666) [PHP的多进程互斥和锁定机制讨论_miceCMS开发团队](http://blog.sina.cn/dpool/blog/s/blog_5ed1dcc70100d3qk.html?vt=4) [请问抢购的时候为什么选择用PHP文件锁而不是MySQL锁机制,PHP文件锁与其对比有什么特殊优点吗? - SegmentFault](https://segmentfault.com/q/1010000006782974) [高并发下常见的缓存锁机制的PHP实现 - PHPERZ中文资讯站](http://www.phperz.com/article/14/1112/8344.html) [php 根据url自动生成缩略图并处理高并发的问题解决方法 - PHPERZ中文资讯站](http://www.phperz.com/article/15/0123/5186.html) > 任何时候都要考虑并发,只有考虑周到,才能设计出来高性能,稳定的系统。 [用memcached实现的php锁机制 - 为程序员服务](http://ju.outofmemory.cn/entry/48924) [网页搜索_php锁机制](https://so.m.sm.cn/s?q=php%E9%94%81%E6%9C%BA%E5%88%B6&by=relative&uc_param_str=dnntnwvepffrgibijbprsvdsme&dn=8747331402-4c93fc31&nt=99&nw=4G&ve=11.0.8.858&pf=145&fr=android&gi=bTkwBOl9HXrDhv3l6Di3lsqk6blEtdR9eWcYChOe27u8rio%3D&bi=800&jb=0&pr=UCMobile&sv=ucrelease1&ds=bTkwBBipMwOIa6gfb2LWSAGEXNqcc6h8lJ5chd5kXXjq7Q%3D%3D&me=AAQCzU1n1Bku3%2FKOD%2BdXI%2BXs&from=ucframe&uc_sm=1) [网页搜索_php没有java的锁](https://so.m.sm.cn/s?q=php%E6%B2%A1%E6%9C%89java%E7%9A%84%E9%94%81&uc_param_str=dnntnwvepffrgibijbprsvdsme&from=ucframe&uc_sm=1) [网页搜索_php监听数据变更](https://so.m.sm.cn/s?uc_param_str=dnntnwvepffrgibijbprsvdsme&dn=8747331402-4c93fc31&nt=99&nw=4G&ve=11.0.8.858&pf=145&fr=android&gi=bTkwBOl9HXrDhv3l6Di3lsqk6blEtdR9eWcYChOe27u8rio%3D&bi=800&jb=0&pr=UCMobile&sv=ucrelease1&ds=bTkwBBipMwOIa6gfb2LWSAGEXNqcc6h8lJ5chd5kXXjq7Q%3D%3D&me=AAQCzU1n1Bku3%2FKOD%2BdXI%2BXs&from=ucframe&by=tuijian&by2=weini&sc=&q=php%E7%9B%91%E5%90%AC%E6%95%B0%E6%8D%AE%E5%8F%98%E6%9B%B4&uc_sm=1) [php + mysql 这个下单流程怎么确保数据的完整性-云栖社区](https://m.aliyun.com/yunqi/ask/4094) > 盗我在SF的问题啊,https://segmentfault.com/q/1010000002600161 [php中有没有什么方法可以实现实时监听数据库中的某张..._慕课问答](http://www.imooc.com/qadetail/71289) > 实时只有两种,你要实时得到你要监听的事物的状态,要么事物发生变化时自己通知你,要么你定时轮询查看它。 [我是一个线程(修订版)](http://mp.weixin.qq.com/s/-BMCUuIWYE3O_oC3ZNNJRg) [JAVA CAS原理深度分析 - CSDN博客](http://blog.csdn.net/hsuxu/article/details/9467651) > 确保对内存的读-改-写操作原子执行。在Pentium及Pentium之前的处理器中,带有lock前缀的指令在执行期间会锁住总线,使得其他处理器暂时无法通过总线访问内存。 >[danger] 可见,内部还是用的锁,那怕是到cpu层面,也是用的锁(CPU的锁),不然对共享变量读写时怎么保证数据的正确性,虽然我们平时写代码不用考虑这个问题,编程语言对于我们来说总是安全的,但是计算机内部其实也做了安全考量和优化,以保证我们认为的那些再正常和普通的操作得以正确的执行,只不过我们忽略掉了操作系统/编程语言内部为此所做的努力而已。 [一篇文章看懂Java并发和线程安全](https://www.toutiao.com/a6513707798830776846/?tt_from=weixin&utm_campaign=client_share×tamp=1516737598&app=news_article&utm_source=weixin&iid=22069500288&utm_medium=toutiao_android&wxshare_count=1) [基于Redis的分布式锁到底安全吗(下)?](https://mp.weixin.qq.com/s?__biz=MzA4NTg1MjM0Mg==&mid=2657261521&idx=1&sn=7bbb80c8fe4f9dff7cd6a8883cc8fc0a&chksm=84479e08b330171e89732ec1460258a85afe73299c263fcc7df3c77cbeac0573ad7211902649&scene=21#wechat_redirect) > 一个进程持有锁L,发起了请求R,但是请求失败了。另一个进程获得了锁L并在请求R到达目的方之前执行了一些动作。如果后来请求R到达了,它就有可能在没有锁L保护的情况下进行操作,带来数据不一致的潜在风险。 * * * * * 锁的本质其实是对那些需要使用竞态资源的操作进行排队处理。 锁的本质作用就是要**控制对竞态资源的访问(读和写)**,数据库的行锁,表锁,文件锁,内存锁,总线锁,任何锁,只要能达到**控制对竞态资源的访问的目的**,那么本质就没有什么不同。 比如开发中,需要锁住用户,如果数据库不支持,那么用文件锁也是可以的(用户ID生成文件名的KEY),**只要所有实例都遵循相同的数据访问规则,那么锁的工作就是正常的。** 不过如果数据库支持,那么直接锁数据库就更好了。 **所有实例都遵循相同的数据访问规则:** > 只有将所有线程都设计成遵守相同的数据访问规则,互斥机制才能正常工作。 **操作系统并不会为我们做数据访问的串行化。** 如果允许其中的某个线程在没有得到锁的情况下也可以访问共享资源,那么即使其他的线程在使用共享资源前都申请锁,也还是会出现数据不一致的问题。(UNIX环境高级编程第三版 P-380) * * * * * [伯克利推出世界最快的KVS数据库Anna:秒杀Redis和Cassandra](http://mp.weixin.qq.com/s/3WmGpZkEuSz-ox_2CPCsqg) > 针对同一个键值的并行更新操作会被串行化,它们需要同步机制来防止多个线程同时更新同一个键值。 [编程中的14种锁,你知道几个?](https://www.toutiao.com/a6534966157194035719/?tt_from=weixin&utm_campaign=client_share×tamp=1521551602&app=news_article_lite&utm_source=weixin&iid=25315997380&utm_medium=toutiao_android&wxshare_count=1) [支付平台的架构设计](https://mp.weixin.qq.com/s/QDqtnj02gfxdlyfTKOOnGQ) > **锁的本质其实是排队,不同的锁排队的空间和时间不同而已。** > **其实都是将并行强制变为串行的解决方案。** [Java中如何提升锁性能](https://mp.weixin.qq.com/s/xQnV9PC6vzMjal-ED-XNjw) [Java并发编程之阻塞队列](https://mp.weixin.qq.com/s/6XXpPykDtFfnAgnEXrZLKA) [如何优雅地用Redis实现分布式锁](https://mp.weixin.qq.com/s/EcrVp0dOpnc4FniaBlezcA) >[danger] **锁其实并不是要锁数据本身,锁的本质是要锁操作的(对操作进行排队),即控制操作的顺序**,比如,针对同一条数据的多路操作,1. 消息发送操作;2. 库存扣减操作,这两路(类)操作要改的数据虽然在同一行,但其实它们并不是竞争关系,它们各自的目标数据是相互独立的不同字段,所以并不算共享数据(竞态资源),如果把锁放在数据行上,那就太影响性能了,**其实这两路操作互不影响,只是各自要防止同类操作的多进程并发而已,而不是防对方**,所以只要用针对各路操作的锁就可以了,这才是锁的本质意义。(通常说的状态其实也是这个意思,可理解为操作的状态,如:操作进行中,操作已完成/当前无操作) ~~~ 然而这样就能保证锁一定会被释放吗?考虑这样一种情况:代码执行到doSomething()方法的时候,服务器宕机了,这个时候finally代码块就没法被执行了,因此在这种情况下,该锁不会被正常释放,在上述案例中,可能会导致任务漏算。因此,这种方案的第一个问题是会出现锁无法正常释放的风险,解决这个问题的方法也很简单,Redis设置key的时候可以指定一个过期时间,只要获取锁的时候设置一个合理的过期时间,那么即使服务器宕机了,也能保证锁被正确释放。 要考虑任何的极端情况,做好容错,留后路,未雨绸缪,保证万无一失。 仔细分析一下getLock方法,该方法并不是原子性的,当一个客户端检查到某个锁不存在,并在执行setKey方法之前,别的客户端可能也会检查到该锁不存在,并也会执行setKey方法,这样一来,同一把锁就有可能被不同的客户端获取到了。获取锁必须是原子操作 那么这种方案就没有问题了吗?很遗憾地说,这种方案也是有问题的。原因在于上述释放锁的操作不是原子性的 只要不是原子操作,就不得不仔细考虑其并发问题! ~~~ [漫画:什么是分布式锁?](https://mp.weixin.qq.com/s/8fdBKAyHZrfHmSajXT_dnA) >[danger] **原子性的重要性,任何极端情况都要考虑**,无论什么极端情况都要保证数据可靠性,这是最重要的,否则其它的谈都不用谈。试问连数据可靠性都不能保证的系统谁敢用,过家家吗,所以要重视这点,必须严谨。 > > **原子性其实本质就是代码,指令的原子性。** 一个操作可能有多行代码,多个指令组成,这些代码,指令的执行细节,都成功,都失败,结果一致性,就显得重要了,这和事务内的多条sql一样,**严谨的软件必须考虑原子性。时刻记住,你必须对你写的每一条代码负责**,你写下它时有没有考虑过它会不会执行失败,和会不会被执行,**如果没有被执行或者执行失败你有应对措施没有,还是不暇\[假\]思索,心安理得,一厢情愿的认为它一定会执行,一定会执行成功**,而不作任何考虑。**你的良心不会痛吗?** 是什么给了你勇气让你如此自信,如果真是这样,可以说你写的软件是弱鸡,已经千疮百孔了,毫无安全性可言。 > > **这并不是钻牛角尖,自找麻烦,也不是杞人忧天,俗话说小心使得万年船,更何况代码即科学,而科学必须严谨,必须一丝不苟不能怕麻烦,否则就不是科学了。** 这并不是说我们平常写代码/做事时要经常 [瞻前顾后、谨小慎微](https://www.zybang.com/question/7b973c6578ef148dd67e79126098b9d2.html),**而是对待科学要有对待科学应该有的严谨、务实的态度。** > > 代码即科学,科学工作必须要**求真务实**,模棱两可和不求甚解是不行的。 [漫画:如何用Zookeeper实现分布式锁?](https://mp.weixin.qq.com/s/u8QDlrDj3Rl1YjY4TyKMCA) > php的运行模式和java不同,不是在一个JVM中运行的,每个进程彼此独立,没有统一管理的,所以php中要实现锁,就相当于这个分布式环境中锁的关系了。那个文件锁,是php发出的系统调用,如果php进程挂了,操作系统应该会检测到并自动释放文件锁吧。 > > 系统并没有直接提供锁,锁其实是一种业务的产物,都是通过某种约定,规则,或方式来实现锁的,哪怕系统底层直接提供锁,其实也是通过一些方式实现的,而我们无需关系怎么实现,只要知道可以达到锁的目的就可以了。所以什么是锁,锁并不是指某个东西,门上一把锁,组织你不能进,这个铁东西就是锁,指示灯显示厕所里面有人不能进,这个有人不能进的规则就是锁,约束了你不能进。所以锁是宽泛的,它的意义就是指定某种规则,让其它人遵守,以达到某种目的。 [我是一个线程(修订版)](https://mp.weixin.qq.com/s/-BMCUuIWYE3O_oC3ZNNJRg) > 锁顺序要保持一致才不会出现死锁,也就是要遵循相同的规则。 [为什么分布式一定要有redis?](https://mp.weixin.qq.com/s/gEU8HtsQNPXY8bzkK-Qllg) > 一刹那者为一念,二十念为一瞬,二十瞬为一弹指,二十弹指为一罗预,二十罗预为一须臾,一日一夜有三十须臾。 > 那么,经过周密的计算,一瞬间为0.36 秒,一刹那有 0.018 秒.一弹指长达 7.2 秒。 >[danger] **任何东西都要去深入思考,做到面面俱到,考虑每一种可能性,应对每一种突发情况,这样才能保证是严谨的。** 避重就轻,拈轻怕重,避实就虚,怕麻烦,不肯深入问题的本质,这样永远都只会停留在表面,无法解决核心问题。 [【原创】分布式之延时任务方案解析 - 孤独烟 - 博客园](https://www.cnblogs.com/rjzheng/p/8972725.html) [【原创】分布式之消息队列复习精讲 - 孤独烟 - 博客园](http://www.cnblogs.com/rjzheng/p/8994962.html) [重入锁是什么意思,锁降级有什么好处-CSDN论坛](https://bbs.csdn.net/topics/390276124) [[MySQL不为人知的主键与唯一索引约束](https://mp.weixin.qq.com/s/IE31GSDP0Ndjzc8kFj4Ukw)]() [从单一架构到分布式交易架构,网易严选的成功实践](https://mp.weixin.qq.com/s/nv3Ht7OqTYQw31QFDX3gNg) >[danger] **没有完美的架构设计,世上也没有绝对的事情,没有谁能保证绝对可靠、安全和高可用,但我们有补偿和容错(类似还有重试,确认等机制),也是能做到万无一失的。** * * * * * [关于Java锁机制面试官会怎么问](https://mp.weixin.qq.com/s/no6S3PUYoCjKLyzuYtOqSQ) * * * * * ### 锁的本质 **锁是一种规定共享资源的访问规则的强制手段。** 系统并没有直接提供锁,锁其实是一种业务的产物,都是通过某种约定,规则,或方式来实现锁的,哪怕系统底层直接提供锁,其实也是通过一些方式实现的,而我们无需关系怎么实现,只要知道可以达到锁的目的就可以了。所以什么是锁,锁并不是指某个东西,门上一把锁,组织你不能进,这个铁东西就是锁,指示灯显示厕所里面有人不能进,这个有人不能进的规则就是锁,约束了你不能进。所以锁是宽泛的,它的意义就是指定某种规则,让其它人遵守,以达到某种目的。 [七大进程间通信和线程同步](https://mp.weixin.qq.com/s/VV_mTpuOYFIZRb94mwhr2Q) * * * * * ### 从业务代码来看锁的应用案例 **锁key命名:** 操作类型 [+ 竞态资源ID(竞态资源唯一标识)] >[tip] (竞态资源ID不是必须的,有时也会有直接所某项操作的情况,比如code项目发货操作QrCode:add()和Order:delivery()操作) >[danger] **确保:** 同一时刻,只能有一个进程 **“对此数据执行此种操作”** ,保证 **“对此数据执行的此种操作”** 是串行的,从而不会出现并发问题。 **(请仔细体会这句话的每一个字)** ```php // 准备锁文件 // lock:锁这路操作,而不是直接锁数据行! // 锁key命名:操作类型 [+ 竞态资源ID(竞态资源唯一标识)](竞态资源ID不是必须的,有时也会有直接所某项操作的情况,比如code项目发货操作QrCode:add()和Order:delivery()操作) // **确保:同一时刻,只能有一个进程“对此数据执行此种操作”,保证“对此数据执行的此种操作”是串行的,从而不会出现并发问题。(仔细体会这句话的每一个字)** $lock_key = 'intention_notice_' . $id; $lockFile = config('lock_path') . $lock_key; file_put_contents($lockFile, date('Y-m-d H:i:s'), LOCK_EX); $mutex = new \malkusch\lock\mutex\FlockMutex(fopen($lockFile, "r")); // 锁住 return $mutex->synchronized(function () use ($bankAccount, $amount) { // 同步操作 // 这部分加锁了的,为同步操作,能保证这部分代码同一时刻只有一个进程在执行 $balance = $bankAccount->getBalance(); $balance -= $amount; if ($balance < 0) { throw new \DomainException("You have no credit."); } $bankAccount->setBalance($balance); }); // 释放锁(PHP脚本执行完毕后结束进程,就会自动交出锁,申请文件锁其实是一次系统调用) ``` >[tip] 思考:执行系统调用的程序突然被 kill 了,那么系统会怎么回收资源呢,比如当执行的系统调用为一个文件锁时,系统应该会自己回收这个锁吧。在系统层面来说,文件锁其实是一个被控制独占访问的系统资源,而独占能力是由操作系统提供的。 #### 更多案例 ```php https://github.com/php-lock/lock Mutex::synchronized() executes code exclusively. This method guarantees that the code is only executed by one process at once. Other processes have to wait until the mutex is available. The critical code may throw an exception, which would release the lock as well. `Mutex::synchronized()` 会同步执行代码。这将保证代码同一时刻只能被一个进程所执行,而其他进程在此过程中则必须等待直到得到互斥锁。关键代码可能抛出异常,这将释放锁。 ----------- https://coding.net/u/xiasf/p/code/git/blob/master/application/admin/controller/QrCode.php // 生成二维码 public function add(Request $request) { // 使用文件锁,使生产二维码的操作不能够并发执行,以保证起止连续,程序的逻辑才能正确 $mutex = new FlockMutex(fopen(__FILE__, "r")); return $mutex->synchronized(function () use ($request) { // 生成逻辑…… }); } ``` * * * * * ### 形象展示为什么会有并发问题 ![](http://cdn.aipin100.cn/18-7-6/49426434.jpg) 这就是并发问题,3操作对于进程2是不可见的,所以4这一刻还以为状态还是2时的,但其实不是的,也就是持有了旧数据,既然持有旧数据,那么程序可能就会和预期不一致,这就是并发问题。 小明 可用 小红 可用 小明 改为不可用 小红 刚看了是可用的 **用锁的话:** 小明 只能我一个人看,可用 小红 有人再看,那我等他看完了我再看(什么都不做,就守在这等着他) 小明 改为不可用,走了 小红 轮到我看了,也只准我一个人看,不可用 小红 不可用啊,那我走了 #### 有锁后的特性 数据不会被两个人同时共享,每个人,每一刻持有的都是最新数据,不可能持有到旧数据。 将并行问题变成串行来解决,这也体现了锁其实就是一种排队,或者说是控制并发程序访问共享资源的顺序和规则。 * * * * * ### php与Java java是老大哥,对很多东西都提供一揽子解决方案,并且是官方的。而php就不是这样,很多时候并没有官方的解决方案,比如没有锁的实现,每个进程相互独立。这么来比喻,java五脏俱全,php灵活自由。如果你要用php写出高级功能,需要具备深厚的计算机知识,而java很多都是开箱即用的。java是一套上古神兵(注意是一套哦),php是一把绝世好剑。 * * * * * ### 带有“候补”功能的分布式锁编排服务 > 锁的本质是为了保证业务的并发安全性,即锁是服务于业务的,所以我们并不是单纯的 key 为真正的锁,这也是为什么 goods:1 要等待 goods:1-user:2 释放才能获得锁的原因,因为他们存在业务的竞态关系,而不只是单纯的看 key,这也正是锁编排的**编排**意义的体现。 1. 请求锁,没请求到锁就支持重试(支持重试次数和超时时间参数) 2. 请求锁,没有请求到锁时,尝试提交候补(候补也有限额的) 3. 锁释放时,优先给到候补列表中的等待者 4. 提前定义好编排规则,使用时一次性提前获取业务过程中的全部所需锁(如 goods:1-user:2),以避免出现死锁 5. 锁分析(超时锁统计,耗时锁分析,锁使用情况统计等等,方便定位程序分析优化业务代码) 6. 锁告警(超时锁,耗时锁 发生告警通知) ---- ### 锁的公平性 轮询争夺的锁表面上看起来是公平的,每个请求都有机会获取到锁,谁能获取到锁,往往看运气(网络到达、进程调度等因素),看起来很公平是吗,但其实并不公平。 试想一下大家去窗口买票时都拼命往里面挤,谁力气大,谁拳头硬,谁就能能挤到前面去先买到票,仔细想一下这公平吗,这算哪门子公平,弱小的人挤哭了都买不到票,挤不过别人,就算来得早也没用,这是弱肉强食的丛林法则,不是文明社会的秩序公平。 所以我们日常使用的并不是公平的锁,那么什么是公平的锁?你应该问下那个弱小挤不进去买不到票的人:“文明排队,先来后到!” 是啊,排队,先来后到,这样才公平嘛。 要获取锁,得先登记,等轮到你了自然会把锁给你,不用争,不用抢,不用打架,人人都能买到票。想要先买到票就别睡懒觉早点来排队,早起的鸟儿有虫吃,没有比这更公平的了。 公平锁除了比非公平锁在道德上更公平外,还有性能优势,由于公平锁采用排队方式,所以每个人只用关注自己前面一个人有没有排到了就行,大家不用再像一群恶狼一样盯着可怜的窗口,而那些在挤啊挤中摩擦产生的能量都被浪费掉了,最终只有一个人抢到票,其他人都精疲力竭,败兴而归,这是极大的浪费,不符合勤俭节约的精神。 > 但非公平锁也并不是一无是处,因为公平锁的缺点也很明显,它实现起来要比非公平锁繁琐不少,反而非公平锁实现起来简单粗暴,使用方便,更适合在某些简单的场景下使用。 但肯定又有较真的人会跳出来说:“大家在登记时不也存在争抢的情况吗,不还是不公平”,这么说吧,取票叫号的窗口绝对没有自助打饭的窗口热闹!想想那些打自助餐时在你前面磨磨唧唧选择困难症的人们吧,可不急死你了是吗,哈哈哈。—— 我们不能完全做到公平,但我们可以去努力改变,努力让世界变得更加公平! https://github.com/HelloGitHub-Team/HelloZooKeeper/blob/main/content/2/content.md [图解:为什么非公平锁的性能更高?](https://juejin.cn/post/6998317686836953124) > 和我们的观点相反 ---- ### 写锁的读 查询的操作会加读锁,读锁只与写锁冲突,所以多个请求并发同时来读是可以的,但是如果某个时刻正在写数据,其他的读操作就会被阻塞,直到写操作完成,写锁释放后,读锁才能获取到。 不让读“快照副本”是因为,这样会导致理论上的争议:某个时刻数据正在写,写操作还没有完成,也就不确定数据最终的内容,此时有人来读数据你期望读到什么? 有人可能会说:“还没写完那你就把之前的数据先给我吧,我不在乎你正在写的所谓的最新数据,你不要让我等就行”。但较真的人会说:“我要读的就是此时此刻的数据,而此时的数据还在写,那我肯定要等一下的,等你写完了再给我不就行了,你拿旧数据给我有什么意义!不要害朕!!!” 有的时候,对于一些重要的读操作,也有给读操作加写锁的情况,加了写锁的读操作就不能和其他的请求同时读/写了,这么做是为了保证,某个时刻 只有这一个 读操作 能接触到数据(读和写)。这样做虽然会降低系统吞吐,但通常只是在重要的时刻使用,并且是把数据读到内存后就立马释放锁了,后续对数据的操作在内存中进行操作,所以这个 写锁的读 只有一瞬间而已,不会有性能问题。(至于到底什么要的重要操作,非这样不可,暂时还没想到这样有什么特别的意义) 读锁底层是什么样的,真的存在所谓的 同时 “读”吗? https://github.com/HelloGitHub-Team/HelloZooKeeper/blob/main/content/4/content.md ---- ### 现实世界的锁 锁并不是只有开发时需要用到,它并不是编程世界的专有,相反软件开发中的一些概念其实都源于生活,是对生活中某些场景的描述。现实世界中也有很多锁的例子,我们每天都在和它打交道,只是它已融入我们的生活,以至于忘记关注到它们的存在了。 锁对应现实的例子: 1. 交通红绿灯🚥 2. 门锁 3. 取票叫号 如果没有锁会怎么样,上厕所不锁门你会很尴尬;红绿灯罢工,交通会瘫痪,会堵车,会发生更多的车祸;...。 **锁无处不在,因为资源是有限的,总是会发生负载**,马路上总不止一辆车,饭店不止一个人要吃饭,...。所以 锁即排队,即秩序的守护者。 ---- [Python Twisted介绍_技术笔记本-CSDN博客_twisted](https://blog.csdn.net/hanhuili/article/details/9389433) > 在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后它们才能依次执行。这种**明确的执行顺序和串行化处理的行为**是很容易推断得出的。**如果任务之间并没有互相依赖的关系,但仍然需要互相等待的话这就使得程序不必要的降低了运行速度。** > 通常我们写的代码,都是自上而下按顺序执行的,即都是 “明确的执行顺序和串行化处理的行为”。 ---- last update:2018-8-6 17:18:37