在讲解 CAS 和乐观锁之前,我们首先需要了解一些基本概念:Java 线程模型以及为什么需要线程模型

Java 线程模型

我们常说线程是进程的子集,一个进程可以有多个线程。但是对于 Linux 系统而言,并没有线程这个概念。我们可以说,在 Linux 中线程等于轻量级的进程。区别在于:

进程拥有独立的内存地址 ,但是线程没有独立的内存地址,多个线程只能共享一个内存的地址。

linux 创建的线程和java创建的线程对比 java线程原理和linux原生线程_Java


如图所示,Java 是运行在操作系统中的,那么其线程也对应着操作系统中的每一个线程。对应的,Java线程模型和内核必然存在着某种关系,一般我们认为有三种:一对一模型、多对一模型、多对多模型。

一对一模型

linux 创建的线程和java创建的线程对比 java线程原理和linux原生线程_一对一_02

最简单的内存模型。好处是当某个线程阻塞时,另一个线程不受影响,可以继续执行其任务。缺点是一个用户线程就要开辟一个对应的内核线程,如果线程太多,会导致 CPU 使用增大。

多对一模型

linux 创建的线程和java创建的线程对比 java线程原理和linux原生线程_后端_03

和一对一模型相比,多对一将一部分的用户线程进行管理,然后开辟出一个内核线程。线程管理放在用户线程上。这样的好处是降低了CPU 的使用,提高了使用效率。但是缺点在于,对应组线程中的某个线程一旦阻塞,那么整组的其他线程也会被同时阻塞,直到该线程恢复正常。

多对多模型

linux 创建的线程和java创建的线程对比 java线程原理和linux原生线程_java_04

多路复用多个用户级线程到同样数量或更少数量的内核线程。这是线程模型的效率提升的“终极模式”,它大幅度降低了系统资源的占用。

目前 Java 线程模型 为 一对一模型

无锁编程

在之前的第一期中我们讲过,无锁能够大幅度降低线程的开销,减少操作系统内核态和用户态的切换,极大提升多线程并发的性能。但是最大的难处在于:无锁编程难度相对较高,控制更加复杂,更容易出错。

悲观锁

linux 创建的线程和java创建的线程对比 java线程原理和linux原生线程_线程模型_05

如图所示,当多个线程访问目标资源时,如果我们使用互斥锁,那么当某个线程获取到目标资源后,就会对资源进行加锁,防止其他线程使用。只有当该线程使用结束,并释放资源后,其他线程才能继续去竞争获取资源。我们将这种情况称之为悲观锁

linux 创建的线程和java创建的线程对比 java线程原理和linux原生线程_线程模型_06

CAS

linux 创建的线程和java创建的线程对比 java线程原理和linux原生线程_线程模型_07

CAS,全称是compare and swap。和互斥锁不同,CAS 是一种乐观锁。当多个线程访问目标资源时,它首先会访问目标资源是否是之前的值(Compare 阶段),如果是的,那么就将其替换为自己的值;否则,就自旋等待,直到下次能够再次获取(Swap 阶段)。
一个简单的 CAS demo 如下图所示:

linux 创建的线程和java创建的线程对比 java线程原理和linux原生线程_java_08


但是 这又引起一个问题:

CAS 分为 compare 和 swap 两步操作,多线程下难以保证一致性?

其实现有的操作系统已经为我们创建了对应的原子操作。例如 x86 系统使用的指令是cmpxchg,arm 使用的指令是LL/SC。通过不同操作系统提供的原子指令,我们无须担心CAS 操作的原子性。