使用原子整数 AtomicInteger ,可无锁解决线程安全问题。 其中的关键是 compareAndSet(比较并设置值),它的简称就是 CAS(也有 Compare And Swap 的说法),它必须是原子操作

  1. class AccountCas implements Account {
  2. //使用原子整数
  3. private AtomicInteger balance;
  4. public AccountCas(int balance) {
  5. this.balance = new AtomicInteger(balance);
  6. }
  7. @Override
  8. public Integer getBalance() {
  9. // 得到原子整数的值
  10. return balance.get();
  11. }
  12. @Override
  13. public void withdraw(Integer amount) {
  14. while(true) {
  15. // 获得修改前的值
  16. int prev = balance.get();
  17. // 获得修改后的值
  18. int next = prev-amount;
  19. // 比较并设值
  20. if(balance.compareAndSet(prev, next)) {
  21. break;
  22. }
  23. }
  24. }
  25. }

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 为例

  1. AtomicInteger i = new AtomicInteger(0);
  2. // 获取并自增(i = 0, 结果 i = 1, 返回 0),类似于 i++
  3. i.getAndIncrement();
  4. // 自增并获取(i = 1, 结果 i = 2, 返回 2),类似于 ++i
  5. i.incrementAndGet();
  6. // 自减并获取
  7. i.decrementAndGet();
  8. // 获取并自减
  9. i.getAndDecrement();
  10. // 获取并加值(i = 0, 结果 i = 5, 返回 0)
  11. i.getAndAdd(5);
  12. // 加值并获取
  13. i.addAndGet(-5);
  14. // 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
  15. // 其中函数中的操作能保证原子,但函数需要无副作用
  16. // 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
  17. i.getAndUpdate(p -> p - 2);
  18. // 更新并获取
  19. i.updateAndGet(p -> p + 2);
  20. // 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
  21. // 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final 的
  22. i.getAndAccumulate(10, (p, x) -> p + x);
  23. // 计算并获取
  24. i.accumulateAndGet(-10, (p, x) -> p + x);

原子引用

  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference

    1. BigDecimal initialValue = new BigDecimal(1);
    2. AtomicReference<BigDecimal> balance = new AtomicReference<>(initialValue);

    ABA问题
    主线程仅能判断出共享变量的值与初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况。
    如果主线程希望:只要有其它线程动过共享变量,那么自己的 CAS 就算失败,这时,仅比较值是不够的,需要再加一个版本号。
    AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用的整个变化过程,通过 AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。
    但有时,并不关心引用变量更改了几次,只关心是否更改过,所以就有了 AtomicMarkableReference
    AtomicStampedReference & AtomicMarkableReference 的区别

  • AtomicStampedReference 需要我们传入整型变量作为版本号,来判定是否被更改过

  • AtomicMarkableReference 需要我们传入布尔变量作为标记,来判断是否被更改过

    原子数组

  • AtomicIntegerArray

  • AtomicLongArray
  • AtomicReferenceArray

    字段更新器

  • AtomicReferenceFieldUpdater // 域 字段

  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater

利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常
Exception in thread "main" java.lang.IllegalArgumentException: Must be volatile type

  1. AtomicIntegerFieldUpdater fieldUpdater =
  2. AtomicIntegerFieldUpdater.newUpdater(Test5.class, "field");
  3. private volatile int field;

原子累加器

LongAdder

Unsafe

Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射获得

  1. public class UnsafeAccessor {
  2. static Unsafe unsafe;
  3. static {
  4. try {
  5. Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
  6. theUnsafe.setAccessible(true);
  7. unsafe = (Unsafe) theUnsafe.get(null);
  8. } catch (NoSuchFieldException | IllegalAccessException e) {
  9. throw new Error(e);
  10. }
  11. }
  12. static Unsafe getUnsafe() {
  13. return unsafe;
  14. }
  15. }
  16. public static void main(String[] args) {
  17. Unsafe unsafe = UnsafeAccessor.getUnsafe();
  18. Field id = Student.class.getDeclaredField("id");
  19. Field name = Student.class.getDeclaredField("name");
  20. // 获得成员变量的偏移量
  21. long idOffset = UnsafeAccessor.unsafe.objectFieldOffset(id);
  22. long nameOffset = UnsafeAccessor.unsafe.objectFieldOffset(name);
  23. Student student = new Student();
  24. // 使用 cas 方法替换成员变量的值,返回 boolean 类型的值
  25. UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0, 20);
  26. UnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null, "张三");
  27. }