我们再回顾一下,原子性问题的根源是CPU切换线程执行指令所导致的,当前一个对共享变量的操作没有完成之前,CPU又切换到另外一个线程来操作对应的共享变量,那么最终产生的结果就可能出现问题。

比如如果现在有两个线程都在执行number=number+1,他们最终的结果可能还是为1,因为PU执行流程可能会如下,:

如解决原子性问题

从上面的案例看,原子性问题的丢失完全是因为CPU切换线程执行指令导致的,那么是否意味着只要禁止CPU切换线程执行指令就可以呢,结果是行不通的,禁止CPU切换指令在单核CPU的确可以解决这个问题,但是多核CPU的场景下,CPU可以同时调度多个线程执行指令,那么该问题还是存在的。

所以我们必须另找出路,回过头来思考,我们会发现一个共性,就是不管是线程切换还是多核CPU同时执行指令,其实根本原因就是,对于共享变量在修改操作,在一个线程没有完成之前,另外一个线程是可以同时介入操作,所以才会导致一个线程的结果可能被另外一个线程覆盖。如果从这个角度来考虑的话,那么是不是只要达成一个线程在操作共享变量的过程中,另外一个线程是不能介入操作,只有等前面一个线程执行完之后,后面的线程才可以操作,也就是让两个线程对于共享变量的操作是互斥的,那么问题就可以解决,而让两个线程操作互斥我们常用的手段就是“加锁”。

互斥锁

能保证多个线程(进程、操作者)对于共享变量(共享资源)的操作是互斥的也就是我们常说的“互斥锁”,锁是一个通用的概念在很多领域都有锁的机制、使用锁的目的也很简单,就是“保证操作的原子性”。

锁这个名字虽然很形象,但是类比到我们现实世界往往容易造成困惑,比如现实世界的门锁,我们开门的必须是用钥匙,而不是需要获取锁,而且现实世界一个锁会有多个钥匙,这在编程领域是不允许的,所以我更愿意把锁的意思解释成“使用权”。每个操作者需要操作共享资源时,必须首先获得这个共享资源的使用权才可以进行操作,而当一个人拥有了共享资源的使用权之后,另外一个人是想要操作共享资源就之后就只能等待前者操作结束后释放共享资源的使用权。

当我们对某个共享资源加锁之后,如果线程想要访问共享资源,那么它首先要拿到这个对象的锁,当某一个线程获取到锁时,它便可以访问共享资源, 没有获取到锁的线程只能等待,直到上一个线程执行完毕之后释放锁再进行下轮锁的竞争,因为只有一把锁,所以永远只会有一个线程操作该资源。加了锁之后那么最后执行的流程就如下:

管程模型

使用互斥锁是为了线程杜宇共享资源的互斥性,对于共享资源的操作只允许有一个线程进行。但是在锁的获得与释放线程之间需要如何进行配合和协调又是一个问题,这也就是线程“同步”问题,所以解决共享变量的访问过程的原子性其实需要解决两个问题,一个是线程之间的互斥,二是线程之间的协调同步。对于这两个问题计算机领域有有一种成熟的方法论来解决,它就是管程。

管程是一个抽象的概念模型,为了解决多个进程或线程同时访问一个共享资源时能达到"互斥"和"同步"的效果,,它定义了管理共享资源的访问过程的模型,任何语言都可用通过都可以通过这套模型编写出安全的并发程序,管程实现必须达到下面几点要求

1、管程中的共享变量对于外部都是不可见的,只能通过管程才能访问对应的共享资源(意思是共享变量的操作必须通过管程,无法通过其他途径操作)。

2、管程是互斥的,某个时刻只能允许一个进程或线程访问共享资源(线程对于管程的访问是互斥的)。

3、管程中需要有线程等待队列和相应等待和唤醒操作(没获得锁的线程放入一个队列中等待,等前一个线程释放锁后可以通过某种机制唤醒等待队列中的线程)。

4、必须有一种办法使进程无法继续运行时被阻塞(在程序要求的逻辑条件不满足的时候,可以使其阻塞)。

我们来理解下上面几个条件:

首先第1点 和第2点我们都能理解,只能通过管程访问共享资源,并且每次只能有一个线程获得管程的执行权,这两个要求理解起来很简单,其实就是为了让线程之间达到互斥的效果。

然后看第3点要求,管程中要有等待队列和响应的等待和唤醒操作,这个也好理解,等待队列和唤醒可以使线程之间达到同步有序的执行。

第4点是比较让人费解的,什么时候线程会无法继续运行呢?为什么要在这个时候提供线程可以进入阻塞的方法。

咱们看一个案例:

场景:假如我们正在开发一个互联网项目;

角色:项目参与人员有产品经理、开发人员、测试人员参与;

限制:只有一个办公室可以使用,一个办公室一次只能容纳一个角色进入。

节点: 每个角色负责对应的节点,产品经理产品文档、开发人员产出项目代码、测试人员测试代码质量、产品进行验收。

条件:开发人员必须有了产品文档之后再产出项目代码、测试人员在开发人员开发完毕了之后进入测试、产品人员在测试完毕了之后进行验收。

在这个场景里面,多个角色就是系统的多个线程,办公室是一个共享资源同一时刻只能有一个角色进入,这个场景里面就有一个阻塞场景,就是当一个开发人员抢到了办公室钥匙之后,进入到办公室,结果发现产品的需求都没有出来,这个时候开发人员是没有办法进行工作的,所以只能一直等,等到有产品文档之后继续下一步,但是这个时候产品是没办法进入办公室工作的,因为锁在开发人员手里,所以开发人员一直等不到需求文档,而产品经理一直进入不了办公室,导致死锁。

那么这里就需要有一种方式,当开发人员发现条件不成立的时候,此时开发人员可以主动的放弃办公室的锁,然后告诉办公室门口的产品经理,让产品经理先进办公室完成工作,开发人员自己则进入一个等待队列,当产品经理完成了工作之后,产品经理通知开发人员,然后自己放弃房间钥匙,等待需求验收再开始下一轮的工作。

最后以这种条件阻塞的方式让获得锁的线程可以主动让出锁,并等待其他线程唤醒再来检测条件,避免了某一个线程因为条件不满足导致任务无法进行,而因为别的线程无法进入到管程里,导致这个条件永远也无法改变锁造成的死锁问题。

下面这张图虽然不严谨,但是有助于你理解整个管程模型:

JAVA中的管程

通过上面的管程我们再来看JAVA里面的管程,JAVA是通过Synchronized关键字,和wait()、notify、notifyAll() 方法实现了整个管程模型, 与上面标准的管程模型不同的是,JAVA的Monitor属于一种简单的管程模型,因为它并没有使用多个条件变量的队列,不管是竞争锁产生的阻塞,还是拿到锁因为某个条件不合格导致的阻塞,统一都放入一个队列了。

下面我们同样通过一张图来理解:

为什么多个线程不可能同时抢到一把锁_并发基础理论:原子性问题、锁、管程...相关推荐

  1. 为什么多个线程不可能同时抢到一把锁_HFL Redis_12_redis分布式锁的3种实现方式...

    HotFrameLearning(简称 HFL) Redis_12_redis分布式锁的3种实现方式 - 一.大致介绍 ``` 1.昨天介绍完redis的数据结构后,有小伙伴让本人讲讲redis的分布 ...

  2. java 变量锁_并发编程高频面试题:可重入锁+线程池+内存模型等(含答案)

    对于一个Java程序员而言,能否熟练掌握并发编程是判断他优秀与否的重要标准之一.因为并发编程是Java语言中最为晦涩的知识点,它涉及操作系统.内存.CPU.编程语言等多方面的基础能力,更为考验一个程序 ...

  3. 正在等待缓存锁:无法获得锁_一句话说清分布式锁,进程锁,线程锁

    推荐阅读 1. Java 性能优化:教你提高代码运行的效率 2. Java问题排查工具清单 3. 记住:永远不要在MySQL中使用UTF-8 4. Springboot启动原理解析 在分布式集群系统的 ...

  4. java runnable线程锁_多线程 java 同步 、锁 、 synchronized 、 Thread 、 Runnable

    线程 1 线程概述 1.1 什么是线程 v  线程是程序执行的一条路径, 一个进程中可以包含多条线程 v  一个应用程序可以理解成就是一个进程 v  多线程并发执行可以提高程序的效率, 可以同时完成多 ...

  5. 为什么多个线程不可能同时抢到一把锁_分布式为什么一定要有高可用的分布式锁?看完就知道了...

    分布式锁定义 分布式锁在分布式环境下,锁定全局唯一公共资源,表现为: 请求串行化 互斥性 第一步是上锁的资源目标,是锁定全局唯一公共资源,只有是全局唯一的资源才存在多个线程或服务竞争的情况. 互斥性表 ...

  6. 【Java 并发编程】线程锁机制 ( 锁的四种状态 | 无锁状态 | 偏向锁 | 轻量级锁 | 重量级锁 | 锁竞争 | 锁升级 )

    文章目录 一.悲观锁示例 ( ReentrantLock ) 二.重量级锁弊端 三.锁的四种状态 ( 无锁状态 | 偏向锁 | 轻量级锁 | 重量级锁 ) 四.锁的四种状态之间的转换 ( 无锁状态 - ...

  7. 8-26-GLI锁与普通互斥锁、死锁问题、递归锁、信号量、Event事件、并发的tcp通信、进程池线程池

    昨日回顾1 生产者消费者-在生产者和消费者之间,通过队列,增加缓冲,避免了生产者和消费者之间交互-Queue,redis,rabbitmq,kafka-解耦合,队列是微服务的基础2 线程理论,开启-进 ...

  8. 线程池,Volatile,原子性类AtomicInteger,乐观锁悲观锁,并发工具类Hashtable,ConcurrentHashMap类,Semaphore类

      目录 一.线程的状态 二.线程池 1.创建线程池的方式 1.1线程池-Executors默认线程池 1.2线程池-Executors创建指定上限的线程池 1.3线程池-ThreadPoolExec ...

  9. 锁与并发工具包与线程池与LockSupport与Fork/Join框架与并行流串行流与阻塞队列与JPS,jstack命令查看死锁查看线程状态与AQS个人笔记九

    朝闻道,夕死可矣 本文共计 86564字,估计阅读时长1小时 点击进入->Thread源码万字逐行解析 文章目录 本文共计 86564字,估计阅读时长1小时 一锁 二Java中13个原子操作类 ...

最新文章

  1. NR:UE初始搜网流程
  2. 为什么顶尖高手,都是长期主义者?
  3. 在刚刚结束的ACL 2019上,知识图谱领域都发生了哪些大事?
  4. Java读写二进制数据
  5. LeetCode 102. Binary Tree Level Order Traversal
  6. HTTP基本认证(Basic Authentication)的JAVA示例
  7. 剑指Offer - 面试题45. 把数组排成最小的数(字符串排序)
  8. 关于阿拉伯数字转化成为大写汉字
  9. 计算机老师任课教师寄语,任课老师寄语大全
  10. 实现网页布局的自适应 利用@media screen
  11. linux如何设置环境变量
  12. python怎么定义向量类_python的用户定义向量类
  13. 一文看懂机器人技术的发展史
  14. 各种水龙头拆卸图解_水龙头拆除和安装步骤图解
  15. Android 根据逗号分隔String
  16. 【天光学术】演讲稿:微笑面对生活
  17. neo4j图数据库Cypher语句
  18. Scikit learn:machine learning in Python之贝叶斯学习
  19. JDK8系列之Lambda表达式教程和示例
  20. 时间抖动(jitter)--学习笔记

热门文章

  1. 详解nohup和 区别
  2. 微信小程序在当前页面设置其他页面的数据
  3. Android远程调试的探索与实现
  4. iOS 覆盖率检测原理与增量代码测试覆盖率工具实现
  5. 论文浅尝 | 多内容实体和关系联合抽取的对抗训练
  6. TI-RTOS实时操作系统开发之功耗测试
  7. 从Airbnb的发展历程和网易云的大起大落看IT行业创新(第5周课后作业)
  8. 【译】Immutable.js : 操作 Set -8
  9. Elasticsearch5中安装Elasticsearch-head插件
  10. windows和linux中搭建python集成开发环境IDE——如何设置多个python环境