第一部分:并发理论基础03 | 互斥锁(上):解决原子性问题文章目录第一部分:并发理论基础03 | 互斥锁(上):解决原子性问题那原子性问题到底该如何解决呢?简易锁模型改进后的锁模型Java 语言提供的锁技术:synchronized用 synchronized 解决 count+=1 问题锁和受保护资源的关系总结课后思考一个或者多个操作在 CPU 执行的过程中不被中断的特性,称为“原子性”。理解这个特性有助于你分析并发编程 Bug 出现的原因,例如利用它可以分析出 long 型变量在 32 位机器上读写可能出现的诡异 Bug,明明已经把变量成功写入内存,重新读出来却不是自己写入的。那原子性问题到底该如何解决呢?你已经知道,原子性问题的源头是线程切换,如果能够禁用线程切换那不就能解决这个问题了吗?而操作系统做线程切换是依赖 CPU 中断的,所以禁止 CPU 发生中断就能够禁止线程切换。在早期单核 CPU 时代,这个方案的确是可行的,而且也有很多应用案例,但是并不适合多核场景。这里我们以 32 位 CPU 上执行 long 型变量的写操作为例来说明这个问题,long 型变量是 64 位,在 32 位 CPU 上执行写操作会被拆分成两次写操作(写高 32 位和写低 32 位,如下图所示)。在单核 CPU 场景下,同一时刻只有一个线程执行,禁止 CPU 中断,意味着操作系统不会重新调度线程,也就是禁止了线程切换,获得 CPU 使用权的线程就可以不间断地执行,所以两次写操作一定是:要么都被执行,要么都没有被执行,具有原子性。但是在多核场景下,同一时刻,有可能有两个线程同时在执行,一个线程执行在 CPU-1 上,一个线程执行在 CPU-2 上,此时禁止 CPU 中断,只能保证 CPU 上的线程连续执行,并不能保证同一时刻只有一个线程执行,如果这两个线程同时写 long 型变量高 32 位的话,那就有可能出现我们开头提及的诡异 Bug 了。“同一时刻只有一个线程执行”这个条件非常重要,我们称之为互斥。如果我们能够保证对共享变量的修改是互斥的,那么,无论是单核 CPU 还是多核 CPU,就都能保证原子性了。简易锁模型当谈到互斥,相信聪明的你一定想到了那个杀手级解决方案:锁。同时大脑中还会出现以下模型:我们把一段需要互斥执行的代码称为临界区。线程在进入临界区之前,首先尝试加锁 lock(),如果成功,则进入临界区,此时我们称这个线程持有锁;否则呢就等待,直到持有锁的线程解锁;持有锁的线程执行完临界区的代码后,执行解锁 unlock()。改进后的锁模型我们知道在现实世界里,锁和锁要保护的资源是有对应关系的,比如你用你家的锁保护你家的东西,我用我家的锁保护我家的东西。在并发编程世界里,锁和资源也应该有这个关系,但这个关系在我们上面的模型中是没有体现的,所以我们需要完善一下我们的模型。
03 | 互斥锁(上):解决原子性问题
第一部分:并发理论基础03 | 互斥锁(上):解决原子性问题文章目录第一部分:并发理论基础03 | 互斥锁(上):解决原子性问题那原子性问题到底该如何解决呢?简易锁模型改进后的锁模型Java 语言提供的锁技术:synchronized用 synchronized 解决 count+=1 问题锁和受保护资源的关系总结课后思考一个或者多个操作在 CPU 执行的过程中不被中断的特性,称为“原子性”。理解这个特性有助于你分析并发编程 Bug 出现的原因,例如利用它可以分析出 long 型变量在 32 位机器上读写可能出现的诡异 Bug,明明已经把变量成功写入内存,重新读出来却不是自己写入的。那原子性问题到底该如何解决呢?你已经知道,原子性问题的源头是线程切换,如果能够禁用线程切换那不就能解决这个问题了吗?而操作系统做线程切换是依赖 CPU 中断的,所以禁止 CPU 发生中断就能够禁止线程切换。在早期单核 CPU 时代,这个方案的确是可行的,而且也有很多应用案例,但是并不适合多核场景。这里我们以 32 位 CPU 上执行 long 型变量的写操作为例来说明这个问题,long 型变量是 64 位,在 32 位 CPU 上执行写操作会被拆分成两次写操作(写高 32 位和写低 32 位,如下图所示)。在单核 CPU 场景下,同一时刻只有一个线程执行,禁止 CPU 中断,意味着操作系统不会重新调度线程,也就是禁止了线程切换,获得 CPU 使用权的线程就可以不间断地执行,所以两次写操作一定是:要么都被执行,要么都没有被执行,具有原子性。但是在多核场景下,同一时刻,有可能有两个线程同时在执行,一个线程执行在 CPU-1 上,一个线程执行在 CPU-2 上,此时禁止 CPU 中断,只能保证 CPU 上的线程连续执行,并不能保证同一时刻只有一个线程执行,如果这两个线程同时写 long 型变量高 32 位的话,那就有可能出现我们开头提及的诡异 Bug 了。“同一时刻只有一个线程执行”这个条件非常重要,我们称之为互斥。如果我们能够保证对共享变量的修改是互斥的,那么,无论是单核 CPU 还是多核 CPU,就都能保证原子性了。简易锁模型当谈到互斥,相信聪明的你一定想到了那个杀手级解决方案:锁。同时大脑中还会出现以下模型:我们把一段需要互斥执行的代码称为临界区。线程在进入临界区之前,首先尝试加锁 lock(),如果成功,则进入临界区,此时我们称这个线程持有锁;否则呢就等待,直到持有锁的线程解锁;持有锁的线程执行完临界区的代码后,执行解锁 unlock()。改进后的锁模型我们知道在现实世界里,锁和锁要保护的资源是有对应关系的,比如你用你家的锁保护你家的东西,我用我家的锁保护我家的东西。在并发编程世界里,锁和资源也应该有这个关系,但这个关系在我们上面的模型中是没有体现的,所以我们需要完善一下我们的模型。