java基础巩固-宇宙第一AiYWM:为了维持生计,多高(多线程与高并发)_Part6~整起(打手的自我安全修养之生产者消费者模型、实现先打印2再打印1这种控制多线程运行顺序的几种方法、交替输出)

PART1:异步模式之生产者与消费者
在这里插入图片描述

  • 生产者与消费者问题,到底是个啥问题嘛?其实就是:
    在这里插入图片描述
    • 生产者在生成数据后,放在一个缓冲区中,消费者从缓冲区取出数据处理,任何时刻,只能有一个生产者或消费者可以访问缓冲区,由此产生的问题就是:
      • 任何时刻只能有一个线程操作缓冲区,说明操作缓冲区是临界代码,需要互斥,你不保护人家生产者还没有生产出来消费者拿啥消费。
      • 缓冲区空时,消费者必须等待生产者生成数据;缓冲区满时,生产者必须等待消费者取出数据。说明生产者和消费者需要同步
    • 其实就是打手去买包子吃,要是打手们和包子铺不协商好,包子做多了或者做少了不够吃就相当于坏事了:
      • 打手就是消费者,用钱去包子铺买包子吃
      • 包子铺就是生产者
    • 知道了咱们需要通过互斥保护一个人操作的原子性,需要通过同步来保护多个人操作之间的顺序性:那咱们怎样实现这俩条件呢?我们需要三个信号量
      • 互斥信号量 mutex:用于互斥访问缓冲区,初始化值为 1
      • 资源信号量 fullBuffers:用于消费者询问缓冲区是否有数据,有数据则读取数据,初始化值为 0(表明缓冲区一开始为空)【消费者问缓冲区大管家,你那里有包子了没,有包子我就拿包子吃呀】
      • 资源信号量 emptyBuffers:用于生产者询问缓冲区是否有空位,有空位则生成数据,初始化值为 n (缓冲区大小)【生产者问大管家缓冲区,你那里还有空的放包子的笼屉没,有我就不歇了,要起来干活了】
        • 与下面的保护性暂停中的GuardObject不同,不需要产生结果和消费结果的线程一一对应
        • 消费队列可以用来平衡生产和消费的线程资源
        • 生产者仅负责产生结果数据,不关心数据该如何处理,而消费者专心处理结果数据
        • 消息队列是FIFO,并且消息队列是有容量限制的,满时不会再加入数据,空时不会再消耗数据
        • JDK中各种阻塞队列,采用的就是这种模式
        • 线程之间的通信问题约等于生产者消费者问题,面试这样问。相当于A线程和B线程同时访问变量nums,然后A实现nums+1,B实现nums-1,你如果A线程和B线程不相互通信的话是不可能规规矩矩实现nums+1然后nums-1的,指不定哪一步就出错了
    • 或者说的具体一点:怎样解决生产者消费者中容易出现的问题呢
      在这里插入图片描述
      • 解决方法一:找个中介,替包子铺和打手双方跑腿
        在这里插入图片描述
      • 解决方法二:信号灯法,设置一个标志位解决
      • 解决方法三:用synchronized实现多线程同步,加个锁呗(咱们可以用(wait()方法+notify()方法),输出一些打印结果做个小实验)
        在这里插入图片描述
        会有个问题,在这里插入图片描述
        在这里插入图片描述
        在这里插入图片描述
      • 解决方法四:“用Lock模板圈实现多线程同步(await()方法+signal()方法),输出正常打印结果,不管两个线程,四个线程,八个线程…这里依旧正确,并且可以控制线程有序执行”
        • 啥叫啥叫Lock模板圈呢,下面就是:先回顾一下Lock:
          在这里插入图片描述
          在这里插入图片描述
          在这里插入图片描述
          然后下面是举的例子,节选,了解一下Lock模板圈的用法
          在这里插入图片描述
          在这里插入图片描述
          用condition的目的是:
          在这里插入图片描述
          在这里插入图片描述
          在这里插入图片描述
  • 生产者与消费者问题延伸之哲学家就餐问题
    在这里插入图片描述
    • 这个问题又到底是个啥问题呢?我觉得就是因为服务员没礼貌不懂规矩,你要是放五双筷子而不是五只筷子不就没这事了嘛
      • 5 个老大哥哲学家,闲着没事做,围绕着一张圆桌吃面;巧就巧在,这个桌子只有 5 支筷子,每两个哲学家之间放一支筷子;哲学家围在一起先思考,思考中途饿了就会想进餐;这些哲学家需要拿到左右两边的筷子才能进餐;吃完后,会把两支筷子分别放回左右的原处,继续思考;所以要是不控制就会出现有人永远拿不到筷子也就吃不到饭
    • 那问题出现了如何解决问题呢?
      • 方法一:用信号量的方式,也就是 PV 操作来尝试解决它,拿起筷子就相当于用 P 操作,代表有筷子那就直接用,没有筷子时就等待其他哲学家放回筷子。方案一的问题在于,会出现所有哲学家同时拿左边筷子的可能性
        • 这种解法存在一个极端的问题:假设五位哲学家同时拿起左边的筷子,桌面上就没有筷子了, 这样就没有人能够拿到他们右边的筷子,也就说每一位哲学家都会阻塞了,很明显这发生了死锁的现象。
      • 方法二:方案一会发生同时竞争左边筷子导致死锁的现象,那么我们就在拿筷子前,加个互斥信号量。互斥信号量的作用就在于,只要有一个哲学家进入了临界区,也就是准备要拿筷子时,其他哲学家都不能动,只有这位哲学家用完筷子了,才能轮到下一个哲学家进餐。但是这种方法二只能有一位哲学家,按道理是能可以有两个哲学家同时进餐的,所以从效率角度上,这不是最好的解决方案。
      • 方法三:我们可以避免哲学家可以同时拿左边的筷子,采用分支结构,根据哲学家的编号的不同,而采取不同的动作。即让偶数编号的哲学家先拿左边的筷子后拿右边的筷子,奇数编号的哲学家先拿右边的筷子后拿左边的筷子。方案三即不会出现死锁,但是也可以两人同时进餐。
      • 方案四:我们用一个数组 state 来记录每一位哲学家的三个状态,分别是在进餐状态、思考状态、饥饿状态(正在试图拿筷子)。那么,一个哲学家只有在两个邻居都没有进餐时,才可以进入进餐状态。方案四同样不会出现死锁,也可以两人同时进餐
        在这里插入图片描述
    • 那哲学家进餐问题对我们有啥用呢,知道这个东西能干啥:哲学家进餐问题对于互斥访问有限的竞争问题(如 I/O 设备)一类的建模过程十分有用
  • 生产者与消费者问题延伸之读者写者问题:
    • 首先说明,学了这货有啥用:著名的问题是读者-写者【读者只会读取数据,不会修改数据,而写者即可以读也可以修改数据】,它为数据库访问建立了一个模型
    • 读者写者,问题是啥呀:数据库哪里,肯定就是脏读、幻读、不可重复读呗
      • 读-读允许:同一时刻,允许多个读者同时读
      • 读-写互斥:没有写者时读者才能读,没有读者时写者才能写
      • 写-写互斥:没有其他写者时,写者才能写
    • 咋解决这个问题呢:
      • 方法一:使用信号量的方法解决:读者优先策略【读者优先中只要后续有读者到达,读者就可以进入读者队列, 而写者必须等待,直到没有读者到达。没有读者到达会导致读者队列为空,即 rCount==0,此时写者才可以进入临界区执行写操作。】
        • 信号量 wMutex:控制写操作的互斥信号量,初始值为 1
        • 读者计数 rCount:正在进行读操作的读者个数,初始化为 0
        • 信号量 rCountMutex:控制对 rCount 读者计数器的互斥修改,初始值为 1
      • 方法二:只要有写者准备要写入,写者应尽快执行写操作,后来的读者就必须阻塞。如果有写者持续不断写入,则读者就处于饥饿:写者优先策略
        • 信号量 rMutex:控制读者进入的互斥信号量,初始值为 1
          • 这里 rMutex 的作用,开始有多个读者读数据,它们全部进入读者队列,此时来了一个写者,执行了 P(rMutex) 之后,后续的读者由于阻塞在 rMutex 上,都不能再进入读者队列,而写者到来,则可以全部进入写者队列,因此保证了写者优先。同时,第一个写者执行了 P(rMutex) 之后,也不能马上开始写,必须等到所有进入读者队列的读者都执行完读操作,通过 V(wDataMutex) 唤醒写者的写操作。
        • 信号量 wDataMutex:控制写者写操作的互斥信号量,初始值为 1
        • 写者计数 wCount:记录写者数量,初始值为 0
        • 信号量 wCountMutex:控制 wCount 互斥修改,初始值为 1
      • 方法三:公平策略。【开始来了一些读者读数据,它们全部进入读者队列,此时来了一个写者,执行 P(falg) 操作,使得后续到来的读者都阻塞在 flag 上,不能进入读者队列,这会使得读者队列逐渐为空,即 rCount 减为 0。这个写者也不能立马开始写(因为此时读者队列不为空),会阻塞在信号量 wDataMutex 上,读者队列中的读者全部读取结束后,最后一个读者进程执行 V(wDataMutex),唤醒刚才的写者,写者则继续开始进行写操作】
        • 优先级相同
        • 写者、读者互斥访问
        • 只能一个写者访问临界区
        • 可以有多个读者同时访问临界资源

PART2-1:同步模式之保护性暂停

  • 同步模式之保护性暂停:Guarded Suspension,用在一个线程等待另一个线程的执行结果的情境下
    在这里插入图片描述
    • 有一个结果需要从一个线程传递到另一个线程, 让他们关联同一个GuardedObject
      在这里插入图片描述
    • 如果有结果不断从一个线程到另一个线程那么可以使用消息队列(见上面生产者消费者)
    • JDK中,join 的实现、Future 的实现,采用的就是此同步模式
      • 同步模式之保护性暂停的实现
        在这里插入图片描述
        • 增加延时:get方法加一个timeout参数表示要等待多久,public object get(long timeout)
    • 因为要等待另一方的结果,因此归类到同步模式

PART2-2:同步模式之Balking

  • Balking (犹豫) 模式用在一个线程发现另一个线程或本线程已经做了某-件相同的事,那么本线程就无需再做了,直接结束返回
    在这里插入图片描述
    在这里插入图片描述

PART3-1:学了半天多线程,如果我要实现先打印2再打印1这种多线程运行顺序,有几种方法:

  • 方法一:
    在这里插入图片描述
  • 方法二:ReentrantLock中的await()和signal()
  • 方法三:LockSupport的park()和unpark()方法
    在这里插入图片描述

PART3-2:实现多线程交替输出:

  • 方法一:用整数标记,帮我们一把
    在这里插入图片描述
  • 方法二:用Reentrant
    在这里插入图片描述
    在这里插入图片描述
  • park和unpark
    在这里插入图片描述

巨人的肩膀:
B战各位老师
java并发编程实战
java并发线程之美

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值