Java并发06之原子类

发布于:2024-12-07 ⋅ 阅读:(237) ⋅ 点赞:(0)

1 CAS算法

1.1 CAS算法

CAS算法包含三个操作数:内存位置V、预期值A以及更新值B。有且仅当内存位置V的值与预期值A相同时,才将旧值更新为新值B。否则不做任何处理。

CAS算法在Java中的具体实现就是Unsafe类。由于Java方法无法直接访问底层系统,需要通过native方法来访问,基于Unsafe类可以直接操作特定内存的数据。Unsafe内部方法操作可以像C指针一样直接操作内存,因此Java中CAS操作的执行依赖于Unsafe类的方法。Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源。

1.2 CAS存在的问题

  • 空自旋问题:CAS多与自旋相结合。自旋是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,如果CAS自旋长时间不成功,会占用大量的CPU资源。
  • ABA问题:比如说线程1从内存位置V中取出A,然后线程2也从内存中取出A修改为B,此时又有线程3又将B修改回A。这时候线程1进行CAS操作发现内存中仍然是A,然后执行修改操作成功。解决方案:使用带有戳记(版本号)的原子类。

2 原子类

JUC提供的原子类可以分为五大类:

  • 基本类型原子类:AtomicInteger、AtomicBoolean、AtomicLong
  • 数组类型原子类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
  • 引用类型原子类:AtomicReference、AtomicStampedReference、AtomicMarkableReference
  • 对象属性修改原子类:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
  • 累积性原子类:LongAdder、LongAccumulator

2.1 基本类型原子类

public class TestAtomicInteger {
    public static final int SIZE = 50;
    public static final AtomicInteger NUMBER  = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(SIZE);
        for (int i = 1; i <= SIZE; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 1000; j++) {
                        NUMBER.getAndIncrement();
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }
        // 等待上面50个线程全部计算完成后,再去获得最终值
        countDownLatch.await();
        System.out.println("result: " + NUMBER.get());
    }
}

2.2 数组类型原子类

public class TestAtomicIntegerArray {
    public static void main(String[] args) {
        AtomicIntegerArray array = new AtomicIntegerArray(new int[5]);
        for (int i = 0; i < array.length(); i++) {
            System.out.print(array.get(i) + " ");
        }
        System.out.println();

        System.out.println(array.getAndSet(0, 1122));
        System.out.println(array.getAndIncrement(0));
        System.out.println(array.get(0));
    }
}

2.3 引用类型原子类

2.3.1 AtomicReference

public class TestAtomicReference {
    public static void main(String[] args) {
        AtomicReference<User> atomicReference = new AtomicReference<>();
        User z3 = new User("张三", 18);
        User li4 = new User("李四", 20);
        // 设置为z3
        atomicReference.set(z3);
        // 如果是z3,则修改为li4
        System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
        // 如果是z3,则修改为li4
        System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());
    }

    @Data
    @ToString
    @AllArgsConstructor
    static class User {
        String userName;
        int age;
    }
}

2.3.2 AtomicStampedReference

AtomicStampedReference:带版本号的引用类型原子类

public class TestAtomicStampedReference {

    public static void main(String[] args) {
        // 带有戳记的引用类型原子类
        Book javaBook = new Book(1, "javaBook");
        AtomicStampedReference<Book> stampedReference = new AtomicStampedReference<>(javaBook, 1);
        System.out.println(stampedReference.getReference() + "\t" + stampedReference.getStamp());
        // 戳记+1
        Book mysqlBook = new Book(2, "mysqlBook");
        boolean b = stampedReference.compareAndSet(javaBook, mysqlBook, 1, 2);
        System.out.println(b + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp());
        // 戳记+1
        b = stampedReference.compareAndSet(mysqlBook, javaBook, 2, 3);
        System.out.println(b + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp());
    }

    @NoArgsConstructor
    @AllArgsConstructor
    @Data
    static class Book {
        private int id;
        private String bookName;
    }
}

2.3.3 AtomicMarkableReference

AtomicMarkableReference:不关心引用变量被修改了几次,只是单纯的关心是否更改过,因此不建议用来解决ABA问题

public class TestAtomicMarkableReference {
   private static final AtomicMarkableReference<Integer> MARKABLE_REFERENCE = new AtomicMarkableReference<>(100, false);

    public static void main(String[] args) {
        new Thread(() -> {
            boolean marked = MARKABLE_REFERENCE.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            MARKABLE_REFERENCE.compareAndSet(100, 1000, marked, !marked);
        }, "t1").start();

        new Thread(() -> {
            boolean marked = MARKABLE_REFERENCE.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);

            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = MARKABLE_REFERENCE.compareAndSet(100, 2000, marked, !marked);
            System.out.println(Thread.currentThread().getName() + "\t" + "t2线程CASresult: " + b);
            System.out.println(Thread.currentThread().getName() + "\t" + MARKABLE_REFERENCE.isMarked());
            System.out.println(Thread.currentThread().getName() + "\t" + MARKABLE_REFERENCE.getReference());
        }, "t2").start();
    }
}

2.4 对象属性修改原子类

以一种线程安全的方式操作非线程安全对象内的某些字段,减少锁定的范围,只关注长期、敏感性变化的某一个字段,而不是整个对象,以达到精确加锁+节约内存的目的。

public class TestAtomicIntegerFieldUpdater {
    public static void main(String[] args) throws InterruptedException {
        BankAccount bankAccount = new BankAccount();
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 1; i <= 10; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 1000; j++) {
                        bankAccount.transMoney(bankAccount);
                    }
                } finally {
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t" + "result: " + bankAccount.money);
    }

    static class BankAccount {
        String bankName = "CCB";

        // 更新的对象属性必须使用 public volatile 修饰符。
        public volatile int money = 0;

        public void add() {
            money++;
        }

        // 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须
        // 使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
        static final AtomicIntegerFieldUpdater<BankAccount> fieldUpdater =
                AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");

        // 不加synchronized,保证高性能原子性,局部微创小手术
        public void transMoney(BankAccount bankAccount) {
            fieldUpdater.getAndIncrement(bankAccount);
        }
    }
}

2.5 累积性原子类

public class TestAccumulator {
    public static final int _1W = 10000;
    public static final int threadNumber = 50;

    public static void main(String[] args) throws InterruptedException {
        ClickNumber clickNumber = new ClickNumber();
        long startTime;
        long endTime;

        CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber);

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickBySynchronized();
                    }
                } finally {
                    countDownLatch1.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch1.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickBySynchronized: " + clickNumber.number);

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickByAtomicLong();
                    }
                } finally {
                    countDownLatch2.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByAtomicLong: " + clickNumber.atomicLong.get());

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickByLongAdder();
                    }
                } finally {
                    countDownLatch3.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAdder: " + clickNumber.longAdder.sum());

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= 100 * _1W; j++) {
                        clickNumber.clickByLongAccumulator();
                    }
                } finally {
                    countDownLatch4.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch4.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAccumulator: " + clickNumber.longAccumulator.get());
    }

    static class ClickNumber {
        int number = 0;

        // 同步锁
        public synchronized void clickBySynchronized() {
            number++;
        }

        // 基本数据类型原子类
        AtomicLong atomicLong = new AtomicLong(0);

        public void clickByAtomicLong() {
            atomicLong.getAndIncrement();
        }

        // LongAdder
        LongAdder longAdder = new LongAdder();

        public void clickByLongAdder() {
            longAdder.increment();
        }

        // LongAccumulator
        LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);

        public void clickByLongAccumulator() {
            longAccumulator.accumulate(1);
        }
    }
}