并发理论基础-互斥锁:原子性
01 | 导读
1、原子性
1)定义:一个或者多个操作在 CPU 执行的过程中不被中断的特性,称为“原子性”
2)容易误解:
- 原子性问题的源头是线程切换,
- 操作系统做线程切换是依赖 CPU 中断的,所以禁止 CPU 发生中断就能够禁止线程切换
- 单独的1和2都没问题,如果认为禁用CPU 中断就能解决原子性问题,这就有待商酌了。
3)1个例子:32 位 CPU 上执行 long 型变量的写操,long 型变量是 64 位,在 32 位 CPU 上执行写操作会被拆分成两次写操作(写高 32 位和写低 32 位)
- 单核,同一时刻只有一个线程执行,禁止 CPU 中断,也就是禁止了线程切换,获得 CPU 使用权的线程就可以不间断地执行,所以两次写操作一定是:要么都被执行,要么都没有被执行,具有原子性。
- 多核,同一时刻,有可能有两个线程同时在执行,一个线程执行在 CPU-1 上,一个线程执行在 CPU-2 上,此时禁止 CPU 中断,只能保证 CPU 上的线程连续执行,并不能保证同一时刻只有一个线程执行,如果这两个线程同时写 long 型变量高 32 位的话,那就有可能出现写入和读取的数值不一致的bug问题。
2、互斥
1)定义:“同一时刻只有一个线程执行”
2)如果我们能够保证对共享变量的修改是互斥的,那么,无论是单核 CPU 还是多核 CPU,就都能保证原子性了
02 | 锁模型
1、简易锁模型
1)临界区:指一段需要互斥执行的代码
2)执行过程:
- 进入临界区前->尝试加锁->加锁成功->执行临界区代码->解锁
- 进入临界区前->尝试加锁->锁被占用->等待
3)不足之处:没有明确锁和要保护的资源
2、改进后的锁模型
1)改进的地方:标注临界区受保护资源R和保护资源的锁LR,将锁LR和受保护资源R建立关联关系
2)注意:锁和受保护资源间的对应关系很重要,忽略他,就会出现类似锁自家门来保护他家资产的事情
3、Java 语言的锁技术:synchronized
1)锁是一种通用的技术方案,Java 语言提供的 synchronized 关键字,就是锁的一种实现,synchronized 关键字可以用来修饰方法,也可以用来修饰代码块。
2)Java 编译器会在 synchronized 修饰的方法或代码块前后自动加上加锁 lock() 和解锁 unlock()
3)锁修饰方法的隐含规则:
- 当修饰静态方法的时候,锁定的是当前类的 Class 对象,在上面的例子中就是 Class X;
- 当修饰非静态方法的时候,锁定的是当前实例对象 this
03 | 锁和受保护资源的关系
1)一个合理的关系是:受保护资源和锁之间的关联关系是 N:1 的关系
2)并发领域可以用同一把锁来保护多个资源(对应现实世界的“包场”),不能用多个锁保护同一个资源。
3)锁不仅能保证原子性,还能保证可见性
04 | 小结
1)加锁能够保证执行临界区代码的互斥性,但不是随便一把锁都能有效
2)必须深入分析锁定的对象和受保护资源的关系,综合考虑受保护资源的访问路径,多方面考量才能用好互斥锁
3)synchronized 是 Java 在语言层面提供的互斥原语,其实 Java 里面还有很多其他类型的锁,但作为互斥锁,原理都是相通的。
05 | 思维导图
1、并发理论基础-互斥锁思维导图
参考文献:
[1]王宝令. Java并发编程实战[M]. 极客时间, 2019.