一、基础概念
1.1 乐观锁与悲观锁
1.1.1 乐观锁
-
乐观锁是
无锁
的实现,乐观是相对悲观锁而言的。 -
java中使用“自旋+CAS+volatile +unsafe类”实现乐观锁。
-
乐观锁适用于
竞争少
、多核 CPU 的场景下(读多写少)。
1、如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响;
2、自旋需要CPU参与
1.1.2 悲观锁
正如其名,具有强烈的独占和排他特性。它指的是对数据被外界修改持保守态度,为了避免同时被其他人修改,采取的是“
先取锁再访问
”的策略。synchronized、ReentrantLock都是悲观锁的典型例子。
1.2 自旋锁与阻塞锁
当一个线程拿不到锁的时候,有以下两种基本的等待策略:
- 策略1:放弃CPU,进入阻塞状态,等待后续被唤醒,再重新被操作系统调度。
- 策略2:不放弃CPU,空转,不断重试,也就是所谓的“自旋”。
- 很显然,如果是单核的CPU,只能用策略1。因为如果不放弃CPU,那么其他线程无法运行,也就无法释放锁。但对于多CPU或者多核,策略2就很有用了,因为没有线程切换的开销。
- 有一点要说明:这两种策略并不是互斥的,可以结合使用。如果拿不到锁,先自旋几圈;如果自旋还拿不到锁,再阻塞,synchronized关键字就是这样的实现策略。
二、原子类(Atomic类)基本介绍
java中,封装了“CAS+volatile +unsafe类 ”的操作,我们称为原子类
,原子类有很多,都以Atomic开头,原子类对应的变量叫原子变量,原子类包括:
- 原子整形类:AtomicInteger、AtomicLong、、AtomicBoolean;
- 原子引用类:AtomicReference、AtomicStampedReference、AtomicMarkableReference;
- AtomicReference:对引用类型做原子操作;
原子类的ABA问题:从 A 改为 B 又改回 A 的情况,叫做ABA问题。
原子整型类、AtomicReference不能感知ABA问题,需要使用AtomicStampedReference、AtomicMarkableReference解决ABA问题。
AtomicStampedReference:能知道被改了多少次,能彻底解决ABA问题。
AtomicMarkableReference:仅能感知到有没有被修改,能缓解ABA问题但不能彻底解决ABA问题。
- 原子数组:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray;
对数组中的一个元素进行原子操作。注意:不是对整个数组进行原子操作
- 字段更新器:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater;
- 利用字段更新器,可以针对对象的属性进行原子操作。
- 如果一个类是自己编写的,则可以在编写的时候把成员变量定义为Atomic类型。但如果是一个已经有的类,在不能更改其源代码的情况下,要想实现对其成员变量的原子操作,须用到字段更新器。
- 字段更新器使用的
限制条件
:被处理的字段须被volatile修饰,否则会报错。
2.1 原子整型类AtomicInteger使用举例
package com.fuping3.atomic;
import java.util.concurrent.atomic.AtomicInteger;
public class Demo {
private AtomicInteger atomic=new AtomicInteger(100);
public void atomicTest(){
while(true){ //自旋
int expect=atomic.get();
int update=expect-10;
boolean result = atomic.compareAndSet(expect, update);//CAS
if(result){
System.out.println("更新成功");
break;
}
}
}
}
(1)如上代码运行流程如下图:
(2)原子类的CAS函数源码
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
原子类的CAS函数,其实是封装的Unsafe类中的一个native函数compareAndSwapInt,该函数有4个参数:
- 第一个是对象(也就是AtomicInteger 对象);
- 第二个是对象的成员变量偏移量(通过对象+偏移量,可以找到是对象的哪个属性);
- 第三个参数expect是指变量的旧值(是读出来的值,写回去的时候,希望没有被其他线程修改,所以称为expect);
- 第四个参数update是指变量的新值(修改过的,希望写入的值)。
当expect等于变量当前的值时,说明在修改的期间,没有其他线程对此变量进行过修改,所以可以成功写入,变量被更新为update,返回true,否则返回false。
Unsafe类仅提供三个CAS函数:compareAndSwapInt、compareAndSwapLong、compareAndSwapObject(引用类型CAS);
—-》对boolean的支持是:将boolean转为int;
—-》对double的支持是:依赖double类型提供的一对double类型和long类型互转的函数DoubleAdder