volatile关键字
确保对一个变量更新对其他线程马上可见(写入变量不把值缓存在寄存器/其他地方,直接刷新回主内存),其他线程读取该值时,直接从主内存读取。
一般在以下情况使用:
1.写入变量值不依赖变量的当前值时。(因为若依赖,将是获取-计算-写入三步,这三步不是原子性的,volatile不保证原子性)
2.读写变量值时没有加锁。(加锁本身已保证内存可见性)
CAS(Compare And Swap)操作
非阻塞原子性操作。对于内存中的某一个值V,提供一个旧值A和一个新值B。如果提供的旧值V和A相等就把B写入V。这个过程是原子性的。
ABA问题:如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。
通过时间戳避免ABA问题。
Unsafe类
硬件级别的原子级操作,都是native方法。
Java指令重排序
Java内存模型允许编译器和处理器对指令重排序以提高运行性能,且只会对没有数据依赖性的指令重排序。重排序在多线程下会导致非预期程序执行结果,使用volatile修饰变量就可以保证:写时,该变量之前的操作不会被重排序到该变量之后;读时,该变量之后的操作不会额比重排序到该变量之前。
伪共享
由于存放到cache行的是内存块而不是单个变量,所以可能会多个变量在同一行,当多个线程同时修改一行的多个变量时,由于只能有一个线程操作缓存行,所以相比将每个变量放在一个缓存行,性能有所下降,这就是伪共享。
避免伪共享:
JDK8之前,字节填充(创建变量时使用填充字段填充该变量所在缓冲行)。
JDK8提供sun.misc.Contended注解。
锁的概述
乐观锁与悲观锁
悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改,所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。
悲观锁的实现往往依靠数据库提供的锁机制,即在数据库中,在对数据记录操作前给记录加排它锁。如果获取锁失败,则说明数据正在被其他线程修改,当前线程则等待或者抛出异常。如果获取锁成功,则对记录进行操作,然后提交事务后释放排它锁。
乐观锁并不使用数据库提供的锁机制,一般在表中添加version字段或用业务状态实现。乐观锁直到提交时才锁定,所以不会产生死锁。
悲观锁适合多写场景,乐观锁适合多读场景。
公平锁与非公平锁
公平锁:线程获取锁的顺序是请求锁的早晚决定。
非公平锁:运行时闯入,先来不一定先得。
独占锁与共享锁
独占锁:任何时候只能一个线程得到锁。
共享锁:可由多个线程持有。
可重入锁
某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
synchronized内部锁就是可重入锁。
自旋锁
当前线程获取锁时,若锁已被其他线程占有,它不马上阻塞自己(若线程被阻塞,需要从用户态到内核态,开销大,影响并发性能),而是在不放弃CPU使用权情况下,多次尝试获取(默认10次)。
自旋锁是使用 CPU 时间换取线程阻塞、调度开销。