多线程基础
多线程基础
一、基本概念
1.1 程序、进程和线程
程序:一个可执行文件,如windows
中exe
文件
进程:程序的一次动态执行过程,是操作系统分配资源的基本单位
线程:线程是进程中的一个单一的执行路径,是CPU
调度的基本单位
1.2 启动线程的4种方法
- 继承
Thread
类,实现run
方法 - 实现
Runnable
接口,实现run
方法 - 实现
Callable
接口,实现call
方法 - 使用线程池启动
1.3 sleep、yield和join
sleep
:Thread
类静态本地方法,可以让当前线程暂停一段时间让其他线程执行,注意进入睡眠状态并不会释放线程已经占用的锁。
yield
:Thread
类的静态本地方法,可以让当前线程停止下来进入就绪态,和其他线程一起等待CPU
调度。
join
:Thread
类的方法,可以让当前线程等待,开始执行调用join
方法的线程,等它执行完了,当前线程再继续执行。
二、并发编程三大特性
2.1 可见性
定义:线程运行时,会将内存中数据拷贝一份到自己的线程空间,当对其修改时,不会立刻写回到内存中,而是先放入缓冲区。因此当一个线程修改了内存中的数据,其他线程在短时间内是看不到的,就会出现数据安全问题。
可见性就是指当有一个线程修改了某个成员变量的值,其他变量可以立马看到修改过的值。
可见性的保障
对于Intel CPU
,通过缓存一致性协议(MESI
)保障可见性。在Java
语言中,通过volatile
关键字,保证变量的可见性。
2.2 有序性
定义:程序在运行时,由于CPU
的执行优化,不保证执行顺序和代码一样,只保证最终结果一致性。
有序性的保障
在JVM
中,可以通过添加内存屏障实现变量读写指令的执行顺序,保障有序性
2.3 原子性
定义:一个或多个操作在执行时是不可分割,只能由一个线程执行完了,其他线程才能执行。
原子性的保障
可以通过Synchronized
关键字加锁或者使用CAS
操作保证原子性
三、CAS机制
CAS
是Compare and Swap
的缩写,即比较和交换,也被称为自旋锁,它在用户层面实现对代码的加锁,避免了通过系统调用向操作系统内核申请锁。
什么是CAS操作
修改一个数的时候,会先从内存中读出来一个数放到线程空间。线程会记录这个数的初始值,然后修改这个数。当程序将这个数写回时,比较内存中的这个数和程序中保存的这个数的初始值是否一致。如果一致则说明在当前线程修改这个数并写回的过程中没有其他线程修改过这个数,那么此次操作是成功的。如果不一致,则说明在当前线程修改这个数的过程中,已经有其他线程修改了这个数了,此次操作失败。带有写回时检查的操作就是CAS
操作。
ABA问题
如果一个线程拿到了数据A
,将其变成B
,再将其变成A
然后写回内存,这个过程其他线程是不知道的,其他线程会以为在自己操作A的时间内没有其他线程操作A
,就会放心写回,此时就有可能产生ABA
问题。
四、AQS
AQS
是AbstractQueuedSynchronizer
的缩写,即抽象的队列同步器。分开解释为:
-
Abstract
: 因为它并不知道怎么上锁。使用模板方法设计模式即可,暴露出上锁逻辑 -
Queue
:线程阻塞队列 -
Synchronizer
:同步 -
CAS+(int)state
: 完成多线程抢锁逻辑 -
Queue
: 完成抢不到锁的线程排队,也叫同步器
所以AQS
简单理解就是用一个int
类型变量表示当前线程的状态,再用一个阻塞队列用于存放没有抢到锁的线程。