一文看懂竞态条件、竞态资源、临界区、互斥锁、同步锁、临界区、互斥量、信号量、自旋锁等专有名词!

JAVA herman 598浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

关于线程安全的专有名词有一大堆。你们突然之间问我这个名词是什么意思,那个名词是什么意思我还真不一定能给你准确的回答。这还别说一门语言一堆名词。其实有些名词叫法不同,实际上就是一个意思。

A 语言有这个名词,B 语言就起另外一个名词。不能大胆的雷同,所以就改变一个叫法,其本质还是一样的。

为了减少大家在私信我,那我今天就来扯一扯,竞态条件,竞态资源,轮询忙等,锁变量,原子性,TSL,阻塞,睡眠,唤醒,管程,互斥锁,同步锁,临界区,互斥量,信号量,自旋锁等各个专业名词的实际所代表的含义。

线程安全必懂的专业名词

  • 竞态条件:多线程的核心矛盾是“竞态条件”,即多个线程同时读写某个字段。
  • 竞态资源:竞态条件下多线程争抢的是“竞态资源”。
  • 临界区:涉及读写竟态资源的代码片段叫“临界区”。
  • 互斥:保证竟态资源安全的最朴素的一个思路就是让临界区代码“互斥”,即同一时刻最多只能有一个线程进入临界区。
  • 最朴素的互斥手段:在进入临界区之前,用if检查一个bool值,条件不满足就“忙等”。这叫“锁变量”。但锁变量不是线程安全的。因为“检查-占锁”这个动作不具备“原子性”。
  • TSL:“TSL指令”就是原子性地完成“检查-占锁”的动作。
  • 自旋锁:就算不用TSL指令,也可以设计出线程安全的代码,有一种既巧妙又简洁的结构叫“自旋锁”。当然还有其他更复杂的锁比如“Peterson锁”。
  • 忙等待:但自旋锁的缺点是条件不满足时会“忙等待”,需要后台调度器重新分配时间片,效率低。
  • 解决忙等待问题的是:“sleep”和“wakeup”两个原语。sleep阻塞当前线程的同时会让出它占用的锁。wakeup可以唤醒在目标锁上睡眠的线程。
  • 互斥量:使用sleep和wakeup原语,保证同一时刻只有一个线程进入临界区代码片段的锁叫“互斥量”。
  • 信号量:把互斥锁推广到"N"的空间,同时允许有N个线程进入临界区的锁叫“信号量”。互斥量和信号量的实现都依赖 TSL 指令保证“检查-占锁”动作的原子性。
  • 管程:把互斥量交给程序员使用太危险,有些编程语言实现了“管程”的特性,从编译器的层面保证了临界区的互斥,比如 Java 的 synchronized 关键字。
  • 互斥锁、独占锁、内置锁:并没有“同步锁”这个名词,Java 的 synchronized 正确的叫法应该是“互斥锁”,“独占锁”或者“内置锁”。但有的人“顾名思义”叫它同步锁。

下面我们简单的来扩展一下。比如下面的程序:

public class Counter {
    protected long count = 0;
    public void add(long value){
        this.count = this.count + value;   
    }
}

多线程同时执行这段代码可能就会出错。当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。上例中 add() 方法就是一个临界区,它会产生竞态条件。在临界区中使用适当的同步就可以避免竞态条件。

并发与并行

if (!occupied) {   // 检查
    occupied = true; // 占锁
    critical_rigion(); // 临界区
    occupied = false; // 释放锁
}

上面代码中 occupied 就是锁变量。

自旋锁的关键就是用一个 while 轮询,代替 if 检查状态,这样就算线程切出去,另一个线程也因为条件不满足循环忙等,不会进入临界区。这是一个非常常用的结构,不光用在自旋锁,基本是使用条件变量 wait(),notifyAll() 时候的一种惯用法。

// 线程A
while (true) { 
    while (turn != 0) {} // 锁被占,循环忙等。
    critical_rigion(); 
    turn = 1; // 释放锁
    noncritical_rigion(); 
} 
// 线程B
while (true) { 
    while (turn != 1) {} // 锁被占,循环忙等
    critical_rigion(); 
    turn = 0; // 释放锁
    noncritical_rigion(); 
}

自旋锁的缺点是循环忙等。如果并发的线程不像进程调度那样在时间片用完以后会自动切换上下文,就会形成死锁。所以最好在条件不满足的时候,让出线程的控制权,让其他线程有机会执行来使条件满足。

互斥锁、同步锁、自旋锁

线程安全不管搞出多少名词,都逃不出 3 大核心:原子性、可见性、有序性。推荐阅读《Java 线程安全的3大核心:原子性、可见性、有序性》,其他的相关锁知识,请查看我公众号里的相关文章。

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » 一文看懂竞态条件、竞态资源、临界区、互斥锁、同步锁、临界区、互斥量、信号量、自旋锁等专有名词!