并发理论基础-互斥锁:原子性

并发理论基础-互斥锁:原子性

01 | 导读

1、原子性

1)定义:一个或者多个操作在 CPU 执行的过程中不被中断的特性,称为“原子性”
2)容易误解:

  1. 原子性问题的源头是线程切换,
  2. 操作系统做线程切换是依赖 CPU 中断的,所以禁止 CPU 发生中断就能够禁止线程切换
  3. 单独的1和2都没问题,如果认为禁用CPU 中断就能解决原子性问题,这就有待商酌了。

3)1个例子:32 位 CPU 上执行 long 型变量的写操,long 型变量是 64 位,在 32 位 CPU 上执行写操作会被拆分成两次写操作(写高 32 位和写低 32 位)

  1. 单核,同一时刻只有一个线程执行,禁止 CPU 中断,也就是禁止了线程切换,获得 CPU 使用权的线程就可以不间断地执行,所以两次写操作一定是:要么都被执行,要么都没有被执行,具有原子性。
  2. 多核,同一时刻,有可能有两个线程同时在执行,一个线程执行在 CPU-1 上,一个线程执行在 CPU-2 上,此时禁止 CPU 中断,只能保证 CPU 上的线程连续执行,并不能保证同一时刻只有一个线程执行,如果这两个线程同时写 long 型变量高 32 位的话,那就有可能出现写入和读取的数值不一致的bug问题。

并发理论基础-互斥锁:原子性

2、互斥

1)定义:“同一时刻只有一个线程执行”
2)如果我们能够保证对共享变量的修改是互斥的,那么,无论是单核 CPU 还是多核 CPU,就都能保证原子性了

02 | 锁模型

1、简易锁模型

1)临界区:指一段需要互斥执行的代码
2)执行过程:

  1. 进入临界区前->尝试加锁->加锁成功->执行临界区代码->解锁
  2. 进入临界区前->尝试加锁->锁被占用->等待

3)不足之处:没有明确锁和要保护的资源

2、改进后的锁模型

1)改进的地方:标注临界区受保护资源R和保护资源的锁LR,将锁LR和受保护资源R建立关联关系
2)注意:锁和受保护资源间的对应关系很重要,忽略他,就会出现类似锁自家门来保护他家资产的事情

并发理论基础-互斥锁:原子性
3、Java 语言的锁技术:synchronized

1)锁是一种通用的技术方案,Java 语言提供的 synchronized 关键字,就是锁的一种实现,synchronized 关键字可以用来修饰方法,也可以用来修饰代码块。
2)Java 编译器会在 synchronized 修饰的方法或代码块前后自动加上加锁 lock() 和解锁 unlock()
3)锁修饰方法的隐含规则:

  1. 当修饰静态方法的时候,锁定的是当前类的 Class 对象,在上面的例子中就是 Class X;
  2. 当修饰非静态方法的时候,锁定的是当前实例对象 this

并发理论基础-互斥锁:原子性

03 | 锁和受保护资源的关系

1)一个合理的关系是:受保护资源和锁之间的关联关系是 N:1 的关系
2)并发领域可以用同一把锁来保护多个资源(对应现实世界的“包场”),不能用多个锁保护同一个资源。
3)锁不仅能保证原子性,还能保证可见性

并发理论基础-互斥锁:原子性

04 | 小结

1)加锁能够保证执行临界区代码的互斥性,但不是随便一把锁都能有效
2)必须深入分析锁定的对象和受保护资源的关系,综合考虑受保护资源的访问路径,多方面考量才能用好互斥锁
3)synchronized 是 Java 在语言层面提供的互斥原语,其实 Java 里面还有很多其他类型的锁,但作为互斥锁,原理都是相通的。

05 | 思维导图

1、并发理论基础-互斥锁思维导图
并发理论基础-互斥锁:原子性

参考文献:

[1]王宝令. Java并发编程实战[M]. 极客时间, 2019.