• java
  • go
  • 数据库
  • linux
  • 中间件
  • 书
  • 源码
  • 夕拾

  • java
  • go
  • 数据库
  • linux
  • 中间件
  • 书
  • 源码
  • 夕拾

【补充】查漏补缺

目录

  • 目录
  • 信号量与管程
    • 概念
    • 信号量
    • 管程
      • mesa模型
      • 互斥
      • 同步
    • synchronized
      • wait()的正确姿势
      • notify()何时使用

信号量与管程

概念

在os引入多线程后,程序的多任务并发功能得到了良好的支持,但是也带来了并发导致的共享资源竞争的问题(如:对共享数据区的数据进行操作),在计算机中,对操作共享资源的代码块称为临界区.为了解决这种竞争冲突,引入了互斥与同步

互斥: 任何时候,只能有一个对象访问某个资源,绝不允许多个对象同事操作,即任何时刻,只能有一个进程执行临界区代码.

同步: 指的是事件执行的依赖关系,如: b只有在a执行完毕后才能执行.在os的多线程中,同步的引入是为了协调对共享数据的并发访问.

为了确保同步的正确执行,基本来说有两种形式:

  1. 通过底层硬件支持(cpu指令有test-and-set,即原子操作,一个操作要么执行,要么不执行)
  2. 高层次的软件编程抽象(编程难度较大)

层级关系

并发编程 ——– 临界区
高层抽象 —-信号量——锁
硬件支持 –禁用中断—原子操作(ts指令)—-原子(load/store)

信号量

os来协调共享资源访问的一种重要依赖信息,确保线程之间的同步.简单理解,信号量就是描述系统资源数量的一个变量.
信号量的操作p()操作(Prolaag,荷兰语,尝试减少),v()操作(Verhoog缩写,荷兰语,尝试增加)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

Class Semaphore {
//二进制信号量中 资源数目为0或1
    int sem;  

//获取不到锁,线程放进等待队列
    WaitQueue q;

}

// 原子操作加锁
Semaphore :: P() {

    sem --;

    if (sem<0) {

      //把当前线程放到等待队列中,线程阻塞状态,放弃cpu执行

        block(P);

    }

}

//原子操作解锁
Semaphore :: V() {

    sem ++;

    if (sem<=0) {

       //从等待队列中移除一个线程,并唤醒线程,使其变成可运行状态

        wakeup(t);

    }

}

管程

所谓管程即指:管理共享变量以及对共享变量的操作过程.让其支持并发.就是管理类的成员变量和成员方法,让这个类是线程安全的.

Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简。MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量。

mesa模型

在管程的发展史中,出现过三种不同的管程模型:hasen模型,hoare模型,mesa模型.其中,广泛应用的是mesa模型,java管程的实现也是参考mesa模型.

管程对互斥与`同步的解决方案:

互斥

互斥: 管程解决互斥问题的思路很简单,就是将共享变量及其对共享变量的操作统一封装起来。当多个线程同时试图进入管程内部时,只允许一个线程进入,其他线程则在入口等待队列中等待。

同步

在管程模型里,共享变量和对共享变量的操作是被封装起来的,

管程里还引入了条件变量的概念,而且每个条件变量都对应有一个等待队列,条件变量和等待队列可以解决线程同步问题.

管程是一种用于多线程互斥访问共享资源的程序结构采用面向对象方法,简化了线程的同步机制。由一个锁和多个条件变量组成。锁控制对共享对象的互斥访问,条件变量用于线程间的同步合作。
锁控制对共享对象的互斥访问,条件变量用于线程间的同步合作。

synchronized

synchronized是一个jdk提供的可重入的,非公平的内置锁,可以分为同步块,普通同步方法,静态同步方法。

java monitor同样有入口队列,等待队列,但等待队列的线程可以直接去获取锁而不需要先进入入口队列,所以synchronized是一个非公平锁实现。

任意时刻,只有一个线程处于活动状态(持有monitor),所以获取到monitor就是获取到了锁,此时,锁对象对象头mark word锁标志变成10,并指向了该monitor对象,释放了monitor就释放了锁。线程的同步(wait/notify/notifyAll)通过monitor来实现,这也是为什么wait/notify/notifyAll要放在synchronized作用范围内。

wait()的正确姿势

对于 MESA 管程来说,有一个编程范式,就是需要在一个 while 循环里面调用 wait()。这个是MESA管程特有的。
mesa管程里,

1
2
3
while(条件不满足){
wait();
}

notify()何时使用

除非经过深思熟虑,否则尽量使用 notifyAll().

使用条件:

  1. 所有等待线程拥有相同的等待条件
  2. 所有等待线程被唤醒后,执行相同的操作.
  3. 只需要唤醒一个线程.
【第一部分】并发基础
程序中的异常处理
  1. 1. 目录
  2. 2. 信号量与管程
    1. 2.1. 概念
    2. 2.2. 信号量
    3. 2.3. 管程
      1. 2.3.1. mesa模型
      2. 2.3.2. 互斥
      3. 2.3.3. 同步
    4. 2.4. synchronized
      1. 2.4.1. wait()的正确姿势
      2. 2.4.2. notify()何时使用
© 2023 haoxp
Hexo theme