推荐阅读:

  • 我总结了72份面试题,累计3170页,斩获了30+互联网公司offer(含BATJM)

  • 2020首战告捷,这份Java面试神技Plus版,让我成功拿到了阿里、京东、字节跳动等大厂offer

  • 膜拜!阿里内部都在强推的K8S(kubernetes)学习指南,不能再详细了

年轻人,醒醒吧!此时不搏何时搏!本文主要讲一下常见的CAS理论。再者就是说一下锁的分类,什么乐观锁啊,悲观锁、重入锁等等。这篇文章要一网打尽,都介绍一下。

把CAS按在地上摩擦

中文名:比较并交换

英文名:Compare And Swap

英文缩写:CAS

他是一种无锁化基于乐观锁思想实现的算法,目的是在不使用锁的情况下实现多线程之间的共享数据同步。在Java的java.util.concurrent包中的原子类(不是原子弹)就是基于CAS的实现的。在CAS的算法世界中,存在三大护法:value(要更新的变量)、expected(预期值)和new(要新写入的值)。下面画图说明CAS是如何实现不加锁的情况下协调多线程同步共享数据的:

解释一下:当A、B两个线程都操作value值时,线程A如果一切顺利,会在进行预期值与内存值做比较且相等,这个动作是原子化操作,这时候执行原子的修改value值的操作。修改完成后,B线程也来修改,发现有敌情,只好原地循环等待,直到条件符合时才进行内存值的操作。还有一点要注意的是,比对和修改两个动作都是原子的,但是原子操作 + 原子操作 != 原子操作。多线程高并发,搞的额头没头发。

理想与现实的差距就是这么大…

锁的分类

乐观锁与悲观锁

这二位其实并不是实际存在的锁,仅仅是对锁的抽象定义。乐观锁的目的就是不加锁,从而提升效率。这一思想在Java以及基于数据库实现的乐观锁中都有实践。

在乐观锁的概念里,认为所有的数据都是为我当前线程服务的,在我使用的过程中不会有别的线程修改我的数据(哼,想多了),但是为了保险起见,在更新目标数据的时候还是要做一次对比,即前面说的CAS过程。不过乐观锁是思想,CAS是算法。搞清楚这个就行了。

在悲观锁的概念里,跟乐观锁恰好相反,它的核心是“总有刁民想害朕”即所有线程都可能修改自己持有的数据。因此在读取数据的时候就赶紧上锁,其他人都别想动我的宝贝!大家都立正,一个一个按顺序来。比如前面写到的Synchronized和后面将要写的Lock接口,还有就是基于数据库的悲观锁:select xx from xx where xxx for update

自旋锁与非自旋锁

自旋锁其实就是前一篇中说的轻量级锁,还有兄弟是自适应自旋锁,目前自旋锁是被废了的太子,自适应自旋锁顶替了太子之位了,因为它可以自动的动态调整自旋次数,以达到最高效的运行状态,具体根据那些参数自动调整。而非自旋锁则是当目标资源被占用时,直接进入休眠状态了(遇到困难,睡大觉),等资源就绪后会被再次唤醒并尝试获取锁,这样就造成了反复的内核态与用户态的切换,浪费系统资源。一张图,展示一下自旋与非自旋的差异性:

画图是真的费眼睛,大家有什么比较好的画图方式吗?可以分享一下。这里再解释一下,自旋锁并不完美,有很多缺点,比如自旋时如果此时控制不当,会造成CPU资源的浪费,JDK也在不断的优化这些锁的性能。

再往深了说,其实在自旋锁中还分为三种:TicketLock、CLHlock和MCSlock。

TicketLock

看名字就知道:票据锁,即想要获取锁,你要出示对应的凭证,对上号了,才能把锁给你。跟你去银行取钱似的,拿对卡,输对密码才能给你取钱。

/*** FileName: TicketLock* Author:   RollerRunning* Date:     2020/12/3 9:34 PM* Description:*/
public class TicketLock {//保证可见性volatile int flag = 0;AtomicInteger ticket = new AtomicInteger(0);void lock() {int getTicket = ticket.getAndIncrement();while (getTicket != flag) {}}void unlock() {flag++;}
}

还记得前面讲的Volatile是基于总线监听实现的可见性吗?这里如果线程特别多,大家都在监听flag,这对于带宽容量有限的主存来说,线程的不断增加,压力会越来越大,这也桑畅TicketLock的缺点。

CLHlock

CLHLock其实是三个人发明的:Craig, Landin和Hagersten所以叫CLH了,它的底层是基于链表的公平自旋锁。赫赫有名的AQS(AbstractQueuedSynchronizer)就是基于这种锁变种而来的。在CLH中,所有相互竞争的线程都被放到一个链表中排队,每一个线程被抽象成一个链表的节点,每一个节点在前趋结点的locked域上自旋等待。当前驱释放锁状态,则后续节点就可以进行获取锁的操作。在以后的文章里会手撕AQS的,今天主要介绍一下有啥,埋个种子。

MCSlock

CLHLock这么牛13了,还整个MCSLock干啥呢?原因竟然是为了兼容硬件系统,从架构上来看,分为三大怪物:SMP, MPP和NUMA,问题就出在了这个NUMA上了。它的中文名是:非一致存储访问结构。正是因为这种结构,导致了在使用CLHLock时,后节点在获取前节点中的locked域状态时内存过远。行了,当做八股文背住就行了,面试估计也没人问这个,写BUG更用不到。

公平锁与非公平锁

公平锁

是指多个线程按照申请锁的顺序来依次获取锁,线程直接进入队列中排队,当共享资源可用时,只有队列中的第一个线程才能获得锁。公平锁的优点是等待锁的线程不会饿死。但是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

非公平锁

是指多个线程加锁时直接尝试获取锁,加锁失败时,才会被放入队列中去。但如果此时锁刚好可用,那么这个线程可以直接获取到锁,所以非公平锁有可能出现后申请锁的线程先获取锁的场景。优点是可以减少唤起线程的开销,吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

重入锁和不可重入锁

可重入锁

以Java为例ReentrantLock和Synchronized都是可重入锁,是指在同一个线程在外层方法获取锁的时候,如果其内部调用的方法也有锁,则可以直接获取锁,不会因为之前的锁还没释放而阻塞,一定程度上避免了死锁。在下面的代码中,testRoller()和testRunning() 都是加了锁的两个方法,因为Synchronized是可重入锁,所以在testRoller()中调用testRunning()时,可以直接获取锁。

/*** FileName: TestLock* Author:   RollerRunning* Date:     2020/12/3 9:34 PM* Description:*/
public class TestLock {public synchronized void testRoller() {System.out.println("testRoller....");testRunning();}public synchronized void testRunning() {System.out.println("testRunning....");}
}

共享锁和独占锁

独享锁

是一种吃独食的锁,一次只能被一个线程持有。如果线程A对共享数据独占锁以后,那么其他线程就都没有机会再加锁了,获得排它锁的线程就拥有了对该数据的读写权限。JDK中的Synchronized以及Lock的实现类就是互斥锁。

共享锁

是指这类锁可被多个线程持有。如果线程A对共享数据加上共享锁后,则其他线程也只能对共享数据加共享锁,不能加排它锁。而获得共享锁的线程只能读数据,不能修改数据。

最后贴一张锁分类图:

超屌的多线程锁分类,了解一下?相关推荐

  1. 【线程、锁】什么是AQS(锁分类:自旋锁、共享锁、独占锁、读写锁)

    文章目录 1. 什么是AQS 1.1 锁分类 1.2 具体实现 2. AQS底层使用了模板方法模式 3. AQS的简单应用 参考 1. 什么是AQS AQS:全称为AbstractQuenedSync ...

  2. 锁锁锁-多线程锁-多进程锁

    锁是什么 锁在现实生活意义在于通过加锁的方式达到隐私保护或者独占的意义. 锁在程序世界里,加锁是方法,目的在于①独占②同步. 多线程锁–锁的源起 1.为了尽可能压榨CPU资源,神奇的码农们发明了轻量级 ...

  3. Kraken:使用精确比对的超快速宏基因组序列分类软件

    文章目录 Kraken:使用精确比对的超快速宏基因组序列分类 热心肠日报 摘要 主要结果 图1. Kraken序列分类算法 图2. 基于三个模拟宏基因组的分类程序准确性和速度比较 图3. 基于三个模拟 ...

  4. 【JUC并发编程06】多线程锁 (公平锁和非公平锁,死锁,可重锁)

    文章目录 6 多线程锁 (公平锁和非公平锁,死锁,可重锁) 6.1 synchronized 锁的八种情况 6.2 对上述例子的总结 6.3 公平锁和非公平锁 6.4 可重入锁 6.5 死锁 6 多线 ...

  5. java判断线程是否死锁_c++多线程锁 Mutex  自动判断死锁

    c++多线程锁可以使用absl::Mutex  std::mutex这两种,下面是demo代码. 使用absl:Mutex的时候打印: [mutex.cc : 1338] RAW: Cycle: [m ...

  6. linux下java多线程_Linux系统下Java问题排查——cpu使用率过高或多线程锁问题

    原标题:Linux系统下Java问题排查--cpu使用率过高或多线程锁问题 一个系统.特别是多线程并发的后台系统,在某些特定场景下,可能触发系统中的bug:导致cpu一直居高不下.进程hang了或处理 ...

  7. 【JUC】第三章 多线程锁、CallableFuture 接口

    第三章 多线程锁.Callable&Future 接口 文章目录 第三章 多线程锁.Callable&Future 接口 一.多线程锁 1.synchronized 2.公平锁/非公平 ...

  8. CC00155.bigdatajava——|JavaMySQL.高级.V27|——|MySQL.v28|锁分类|

    一.MySQL的锁分类 ### --- mysql锁分类--> MySQL数据库由于其自身架构的特点,存在多种数据存储引擎, --> MySQL中不同的存储引擎支持不同的锁机制. --&g ...

  9. 多线程锁详解之【临界区】

    更多的锁介绍可以先看看这篇文章:多线程锁详解之[序章] 正文: 一般锁的类型可分为两种:用户态锁和内核态锁.用户态锁是指这个锁的不能够跨进程使用.而内核态锁就是指能够跨进程使用的锁.一般书中会说,wi ...

最新文章

  1. LeetCode 287. Find the Duplicate Number (时间复杂度O(n)) + 链表判断环
  2. 自定义View 进度条
  3. 具有代理设置的Spring Cloud AWS
  4. Java 集合系列目录(Category)
  5. java 压缩jar 仓库,java服务安装(一):使用java service wrapper及maven打zip包
  6. NB-IoT 的“前世今生”
  7. 中国碳酸锰(CAS+598-62-9)市场趋势报告、技术动态创新及市场预测
  8. Linux 内核协议栈 学习资料
  9. 递归与二叉树_leetcode235
  10. centos7下安装airflow
  11. 1、Debian-Pi-Aarch64 系统安装
  12. Web前端:HTML+CSS+JS实现美女照片3D立方体旋转
  13. ROS深度图转化为点云
  14. 思维导图ubuntu_XMind for Linux - 思维导图 | linux软件
  15. ggplot2添加散点图文字标记
  16. delphi导入oracle数据库,Oracle数据库自动备份工具(Delphi源码)
  17. “应用程序配置不正确,应用程序未能启动” 错误的解决
  18. Linux内核文件系统10
  19. Android TextView 如何判断是否已经省略
  20. java调tapd-api获取Bug并发企微通知

热门文章

  1. 什么专业python工程师_西安专业的Python工程师哪家专业
  2. 如何解决:Mac 进不去JetBrains官网 Mac 打不开 idea/webstorm/PyCharm官方网站
  3. 机器之心年度盘点:2017年人工智能领域度备受关注的科研成果
  4. pptp中的pac与pns
  5. vim下:wq和ZZ命令还是有区别的
  6. 古道西风,我是那绝望的断肠人
  7. python阴阳鱼绘制(使用turtle)
  8. 前端/后端 - 表单数据验证 - 个人实践
  9. 爷青回!用原生 Audio API 实现一个千千静听
  10. 【Gerrit使用教程详解】