# CAS

核心观念就是比较并替换

# 问题产生

多线程下修改金额,最终金额不是预期结果


import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class Demo {
    public static void main(String[] args) {
        Account account = new AccountUnsafe(10000);
        Account.demo(account);
    }

}

class AccountUnsafe implements Account {

    private Integer balance;

    public AccountUnsafe(Integer balance) {
        this.balance = balance;
    }

    @Override
    public Integer getBalance() {
        synchronized (this) {
            return this.balance;
        }
    }

    @Override
    public void withdraw(Integer amount) {
        this.balance -= amount;
    }
}

interface Account {
    // 获取余额
    Integer getBalance();

    // 取款
    void withdraw(Integer amount);

    /**
     * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
     * 如果初始余额为 10000 那么正确的结果应当是 0
     */
    static void demo(Account account) {
        List<Thread> ts = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            ts.add(new Thread(() -> {
                account.withdraw(10);
            }));
        }
        long start = System.nanoTime();
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long end = System.nanoTime();
        System.out.println(account.getBalance()
                + " cost: " + (end-start)/1000_000 + " ms");
    }
}

在多线程下最总的金额并不是0,在使用无锁的情况下使用 AtomicInteger 得以解决

class AccountSafe implements Account {

    private AtomicInteger balance;

    public AccountSafe(int account) {
        this.balance = new AtomicInteger(account);
    }

    @Override
    public Integer getBalance() {
        return balance.get();
    }

    @Override
    public void withdraw(Integer amount) {
        while (true) {
            int pre = balance.get();
            int next = pre - amount;
            //核心操作,期望值是 pre,改变值是next
            if (balance.compareAndSet(pre, next)) {
                break;
            }
        }
    }
}

# AtomicInteger

说明

三个类API都差不多,所以以AtomicInteger举例

  • AtomicInteger
  • AtomicBoolean
  • AtomicIong

# compareAndSet

@Slf4j
public class Demo {
    public static void main(String[] args) {
        //无参构造就是0
        AtomicInteger atomicInteger = new AtomicInteger(2);
        //比较和替换,但是这个需要和while 配合比较麻烦
        atomicInteger.compareAndSet(0, 1);
    }
}

# incrementAndGet & getAndIncrement

public class Demo {
    public static void main(String[] args) {
        //无参构造就是0
        AtomicInteger atomicInteger = new AtomicInteger(2);
        //自增并获取
        atomicInteger.incrementAndGet();
        //获取并自增
        atomicInteger.getAndIncrement();
    }
}

# addAndGet & getAndAdd

public class Demo {
    public static void main(String[] args) {
        //无参构造就是0
        AtomicInteger atomicInteger = new AtomicInteger(2);
        //自定义增长数,先增加再获取
        atomicInteger.addAndGet(5);
        //先获取再增加
        atomicInteger.getAndAdd(5);
        System.out.println(atomicInteger.get());
    }
}

# updateAndGet

public class Demo {
    public static void main(String[] args) {
        //无参构造就是0
        AtomicInteger atomicInteger = new AtomicInteger(2);
        /**
         * x 原始值
         * 和compareAndSet差不多,可以做比较复杂的运算
         * 假如x 是 10,在 x * 10 后就是100,但是在运算的过程中,被别的线程更改了,
         * 那么就修改失败了回去获取新的值再运算
         */
        atomicInteger.updateAndGet(x -> x * 10);

        System.out.println(atomicInteger.get());
    }
}

# 原子引用

  • AtomicReference:操作还是和上面一样
  • AtomicMarkableReference
  • AtomicStampedReference

# ABA

# 初形成对业务大致不会有影响,但是需要知道

public class ABA {

    static AtomicReference<String> content = new AtomicReference<>("init");
    public static void main(String[] args) {
        System.out.println("main -> " + content.get());
        other();
        content.compareAndSet("init", "main update");

        System.out.println("main —> " + content.get());
    }

    private static void other() {
        new Thread(() -> content.set("t1 update")).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        Thread init = new Thread(() -> content.set("init"));
        init.start();
        try {
            init.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

}

# 解决办法:AtomicStampedReference 增加版本号

public class ABA {

    //Stamped 本意是 时间戳得意思,在这里当作版本号
    static AtomicStampedReference<String> content = new AtomicStampedReference<>("A", 0);

    public static void main(String[] args) {
        int stamp = content.getStamp();

        System.out.println(" main -> " + content.getReference() + "  version ->" + stamp);
        other();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        boolean b = content.compareAndSet("A", "new B", stamp, stamp + 1);
        System.out.println("update A to new B state: " + b );
        
    }

    private static void other() {

        new Thread(() -> {
            int stamp = content.getStamp();
            content.compareAndSet("A", "B", stamp, stamp + 1);
        }).start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        new Thread(() -> {
            int stamp = content.getStamp();
            content.compareAndSet("B", "A", stamp, stamp + 1);
        }).start();

    }
}

# 原子数组

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

# AtomicIntegerArray

public class Demo {

    public static void main(String[] args) {
        demo(
                () -> new int[10],
                (array) -> array.length,
                (array, index) -> array[index]++,
                array -> System.out.println(Arrays.toString(array))
        );

        demo(
                () -> new AtomicIntegerArray(10),//线程安全
                (array) -> array.length(),
                (array, index) -> array.getAndIncrement(index),
                array -> System.out.println(array)
        );
    }

    private static <T> void demo(
            Supplier<T> arraySupplier,
            Function<T, Integer> lengthFun,
            BiConsumer<T, Integer> putConsumer,
            Consumer<T> printConsumer) {
        List<Thread> ts = new ArrayList<>();
        T array = arraySupplier.get();
        int length = lengthFun.apply(array);
        for (int i = 0; i < length; i++) {
            // 每个线程对数组作 10000 次操作
            ts.add(new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    putConsumer.accept(array, j % length);
                }
            }));
        }

        ts.forEach(t -> t.start()); // 启动所有线程
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });     // 等所有线程结束
        printConsumer.accept(array);
    }

}

# 原子更新器

  • AtomicReferenceFieldUpdater

import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
@Slf4j
public class Demo {

    public static void main(String[] args) {
        Student student = new Student();
        /**
         * @params1: 哪个对象
         * @params2: 属性类型
         * @params3: 属性名称
         */
        AtomicReferenceFieldUpdater<Student, String> name = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");
        boolean b = name.compareAndSet(student, null, "new name");
        log.info("修改状态:{}",b);

    }

}

class Student {

    //必须加上volatile 不然报错
    volatile String name;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}

# 原子累加器

性能比AtomicLong 效果要高几倍

  • LongAdder