使用原子整数 AtomicInteger ,可无锁解决线程安全问题。 其中的关键是 compareAndSet(比较并设置值),它的简称就是 CAS(也有 Compare And Swap 的说法),它必须是原子操作
class AccountCas implements Account {//使用原子整数private AtomicInteger balance;public AccountCas(int balance) {this.balance = new AtomicInteger(balance);}@Overridepublic Integer getBalance() {// 得到原子整数的值return balance.get();}@Overridepublic void withdraw(Integer amount) {while(true) {// 获得修改前的值int prev = balance.get();// 获得修改后的值int next = prev-amount;// 比较并设值if(balance.compareAndSet(prev, next)) {break;}}}}
CAS
CAS 的工作流程
当一个线程要去修改 Account 对象中的值时,先用 get() 获取值 prev,然后再将其设置为新的值 next(调用 CAS 方法)。在调用 CAS 方法时,会将 prev 与 balance 进行比较
- 如果两者相等,就说明该值还未被其他线程修改,可以进行修改操作
- 如果两者不相等,就不设置值,重新调用 get() 获取值 prev,然后再将其设置为新的值 next(调用 CAS 方法),直到修改成功为止
CAS 的特点
- 结合 CAS 和 volatile 可以实现无锁并发,适用于线程数少、多核 CPU 的场景下
- CAS 必须借助 volatile 才能读取到共享变量的新值,来实现比较并交换的效果
- CAS 体现的是无锁并发、无阻塞并发
- 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一
- 一般情况下,使用无锁比使用加锁的效率更高
- 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响
- CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证 比较 - 交换的原子性
在多核状态下,某个核执行到带 lock 的指令时,CPU 会让总线锁住,当这个核把此指令执行完毕,再开启总线。这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的
原子整数
JUC 并发包提供了
AtomicBoolean
- AtomicInteger
- AtomicLong
以 AtomicInteger 为例
AtomicInteger i = new AtomicInteger(0);// 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++i.getAndIncrement();// 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++ii.incrementAndGet();// 自减并获取i.decrementAndGet();// 获取并自减i.getAndDecrement();// 获取并加值(i = 0, 结果 i = 5, 返回 0)i.getAndAdd(5);// 加值并获取i.addAndGet(-5);// 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)// 其中函数中的操作能保证原子,但函数需要无副作用// 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的i.getAndUpdate(p -> p - 2);// 更新并获取i.updateAndGet(p -> p + 2);// 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)// 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final 的i.getAndAccumulate(10, (p, x) -> p + x);// 计算并获取i.accumulateAndGet(-10, (p, x) -> p + x);
原子引用
- AtomicReference
- AtomicMarkableReference
AtomicStampedReference
BigDecimal initialValue = new BigDecimal(1);AtomicReference<BigDecimal> balance = new AtomicReference<>(initialValue);
ABA问题
主线程仅能判断出共享变量的值与初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况。
如果主线程希望:只要有其它线程动过共享变量,那么自己的 CAS 就算失败,这时,仅比较值是不够的,需要再加一个版本号。
AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用的整个变化过程,通过 AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。
但有时,并不关心引用变量更改了几次,只关心是否更改过,所以就有了 AtomicMarkableReference
AtomicStampedReference & AtomicMarkableReference 的区别AtomicStampedReference 需要我们传入整型变量作为版本号,来判定是否被更改过
AtomicMarkableReference 需要我们传入布尔变量作为标记,来判断是否被更改过
原子数组
AtomicIntegerArray
- AtomicLongArray
-
字段更新器
AtomicReferenceFieldUpdater // 域 字段
- AtomicIntegerFieldUpdater
- AtomicLongFieldUpdater
利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type
AtomicIntegerFieldUpdater fieldUpdater =AtomicIntegerFieldUpdater.newUpdater(Test5.class, "field");private volatile int field;
原子累加器
LongAdder
Unsafe
Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得
public class UnsafeAccessor {static Unsafe unsafe;static {try {Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");theUnsafe.setAccessible(true);unsafe = (Unsafe) theUnsafe.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {throw new Error(e);}}static Unsafe getUnsafe() {return unsafe;}}public static void main(String[] args) {Unsafe unsafe = UnsafeAccessor.getUnsafe();Field id = Student.class.getDeclaredField("id");Field name = Student.class.getDeclaredField("name");// 获得成员变量的偏移量long idOffset = UnsafeAccessor.unsafe.objectFieldOffset(id);long nameOffset = UnsafeAccessor.unsafe.objectFieldOffset(name);Student student = new Student();// 使用 cas 方法替换成员变量的值,返回 boolean 类型的值UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0, 20);UnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null, "张三");}
