aoe1231

看得完吗

Java并发编程学习笔记5——共享模型之无锁

目录

1、问题的提出

2、CAS与volatile

2.1、CAS

2.2、volatile

2.3、为什么无锁效率高?

2.4、CAS的特点

3、原子整数

4、原子引用

4.1、ABA问题及解决

4.1.1、ABA问题

4.1.2、AtomicStampedReference

4.1.3、AtomicMarkableReference

5、原子数组

6、字段更新器

7、原子累加器

7.1、LongAdder原理分析

7.1.1、CAS锁

7.1.2、原理之伪共享

8、Unsafe

8.1、概述

8.2、Unsafe CAS操作


1、问题的提出

有如下需求,保证account.withdraw取款方法的线程安全。

public class TestAccount {
    public static void main(String[] args) {
        Account account1 = new LockAccount(10000);
        Account.demo(account1);

        Account account2 = new CasAccount(10000);
        Account.demo(account2);
    }
}

// 加锁实现
class LockAccount implements Account {
    private Integer balance;

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

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

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

// 无锁实现
class CasAccount implements Account {
    private AtomicInteger balance;

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

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

    @Override
    public void withdraw(Integer amount) {
        while(true) {
            // 获取余额的最新值
            int prev = balance.get();
            // 要修改的余额
            int next = prev - amount;
            // 真正修改
            boolean flag;
            if (flag = balance.compareAndSet(prev, next)) {
                break;
            }
        }
    }
}

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

    // 取款
    void withdraw(Integer amount);

    /**
     * 方法内部会启动1000个线程,每个线程做 -10元 的操作
     * 如果初始余额为10000那么正确的结果应当是0
     */
    static void demo(Account account) {
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            threads.add(new Thread(()->{
                account.withdraw(10);
            }));
        }
        long start = System.nanoTime();
        threads.forEach(Thread::start);
        threads.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 cost: 125ms
0 cost: 80ms

2、CAS与volatile

2.1、CAS

前面看到的AtomicInteger的解决方法,内部并没有用锁来保护共享变量的线程安全,那么它是如何实现的呢?

    public void withdraw(Integer amount) {
        while(true) {
            // 获取余额的最新值
            int prev = balance.get();
            // 要修改的余额
            int next = prev - amount;
            // 真正修改
            boolean flag;
            if (flag = balance.compareAndSet(prev, next)) {
                break;
            }
        }
    }

其中的关键是compareAndSet(),它的简称就是CAS(也有Compare And Swap的说法),它必须是原子操作。

注意:其实CAS的底层是lock cmpxchg指令(X86架构),在单核CPU和多核CPU下都能够保证【比较-交换】的原子性。

在多核状态下,某个核执行到带lock的指令时,CPU会让总线锁住,当这个核把此指令执行完毕,再开启总线,这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的。

2.2、volatile

获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。它可以用来修饰成员变量和静态成员变量,它可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存。即一个线程对volatile变量的修改,对另一个线程可见。

注意:volatile仅仅保证了共享变量的可见性,让其它线程能够看到最新值,但不能解决指令交错问题(不能保证原子性)。

CAS必须借助volatile才能读取到共享变量的最新值来实现【比较并交换】的效果。

2.3、为什么无锁效率高?

无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而synchronized会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。

打个比喻。线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速...恢复到高速运行,代价比较大。但无锁情况下,因为线程要保持运行,需要额外CPU的支持,CPU在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但是由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。

2.4、CAS的特点

结合CAS和volatile可以实现无锁并发,适用于线程数少,多核CPU的场景下。

  • CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗;
  • synchronized是基于悲观锁的思想:最悲观的估计,得防着其他线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
  • CAS体现的是无锁并发、无阻塞并发:① 因为没有使用synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一;② 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响。

3、原子整数

J.U.C并发包提供了:① AtomicBoolean;② AtomicInteger;③ AtomicLong。

以AtomicInteger为例:

public class Test2 {
    public static void main(String[] args) {
        AtomicInteger i = new AtomicInteger(0);

        // 获取并自增(i=0,结果i=1,返回0),类似于i++
        System.out.println(i.getAndIncrement());

        // 自增并获取(i=1,结果i=2,返回2),类似于++i
        System.out.println(i.incrementAndGet());

        // 自减并获取(i=2,结果i=1,返回1),类似于 --i
        System.out.println(i.decrementAndGet());

        // 获取并自减(i=1, 结果i=0, 返回1),类似于 i--
        System.out.println(i.getAndDecrement());

        // 获取并加值(i=0,结果i=5,返回0)
        System.out.println(i.getAndAdd(5));

        // 加值并获取(i=5, 结果i=0, 返回10)
        System.out.println(i.addAndGet(5));
    }
}

结果:
0
2
1
1
0
10

原来代码的改进:

    public void withdraw(Integer amount) {
//        while(true) {
//            // 获取余额的最新值
//            int prev = balance.get();
//            // 要修改的余额
//            int next = prev - amount;
//            // 真正修改
//            boolean flag;
//            if (flag = balance.compareAndSet(prev, next)) {
//                break;
//            }
//        }
        // 上述注释的代码等价于以下:
        balance.getAndAdd(-amount);
    }
/**
 * 模拟AtomicInteger中的updateAndGet()方法(使用回调函数)
 */
public class Test3 {
    public static void main(String[] args) {
        AtomicInteger i = new AtomicInteger(5);

        // lambda表达式:读取到的值 -> 设置的值
        i.updateAndGet(value -> value * 10); // 50
        i.getAndUpdate(value -> value * 10); // 50

        updateAndGet(i, p -> p / 2); // 250

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

    public static void updateAndGet(AtomicInteger i, IntUnaryOperator operator) {
        while (true) {
            // 获取当前值
            int prev = i.get();
            // 进行计算
            int next = operator.applyAsInt(prev);
            // 重新赋值结果给共享变量
            if (i.compareAndSet(prev, next)) {
                break;
            }
        }
    }
}

结果:
250

4、原子引用

为什么需要原子引用类型?

  • AtomicReference;
  • AtomicMarkableReference;
  • AtomicStampedReference。

有如下方法:

public class Test4 {
    public static void main(String[] args) {
        DecimalAccount.demo(new CasDecimalAccount(new BigDecimal(10000)));
    }
}

class CasDecimalAccount implements DecimalAccount {
    // <>内为要保护的对象
    private AtomicReference<BigDecimal> balance;

    public CasDecimalAccount(BigDecimal balance) {
        this.balance = new AtomicReference<>(balance);
    }

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

    @Override
    public void withdraw(BigDecimal amount) {
        while(true) {
            // 获取值
            BigDecimal prev = balance.get();
            // 运算
            BigDecimal next = prev.subtract(amount);
            // 更新结果
            if (balance.compareAndSet(prev, next)) {
                break;
            }
        }
    }
}

interface DecimalAccount {
    // 获取余额
    BigDecimal getBalance();

    // 取款
    void withdraw(BigDecimal amount);

    /**
     * 方法内部会启动1000个线程,每个线程做 -10元 的操作
     * 如果初始余额为10000那么正确的结果应当是0
     */
    static void demo(DecimalAccount account) {
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            threads.add(new Thread(()->{
                account.withdraw(BigDecimal.TEN);
            }));
        }
        long start = System.nanoTime();
        threads.forEach(Thread::start);
        threads.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 cost: 143ms

4.1、ABA问题及解决

4.1.1、ABA问题

/**
 * A -> B -> A 问题
 */
public class Test5 {
    private static final Logger LOGGER = LoggerFactory.getLogger(Test5.class);
    static AtomicReference<String> ref = new AtomicReference<>("A");

    public static void main(String[] args) throws InterruptedException {
        LOGGER.debug("main start ...");
        // 获取值A
        // 这个共享变量被其他线程修改过
        String prev = ref.get();
        other();
        Thread.sleep(1000);
        // 尝试改为C(因为A改为B又改回C,主线程无法感知到共享变量被修改)
        LOGGER.debug("change A to C {}", ref.compareAndSet(prev, "C"));
    }
    
    private static void other() {
        new Thread(()->{
            LOGGER.debug("change A to B {}", ref.compareAndSet(ref.get(), "B"));
        }, "t1").start();
        new Thread(()-> {
            LOGGER.debug("change B to A {}", ref.compareAndSet(ref.get(), "A"));
        },"t2").start();
    }
}

结果:
10:14:49.832 [main] DEBUG com.multiThreads.test16.Test5 - main start ...
10:14:49.876 [t2] DEBUG com.multiThreads.test16.Test5 - change B to A true
10:14:49.875 [t1] DEBUG com.multiThreads.test16.Test5 - change A to B true
10:14:50.875 [main] DEBUG com.multiThreads.test16.Test5 - change A to C true

主线程仅能判断出共享变量的值与最初值A是否相同,不能感知到这种从A改为B又改回A的情况,如果主线程希望:只要有其他线程【动过了】共享变量,那么自己的CAS就算失败,这时,仅比较值是不够的,需要再加一个版本号。

4.1.2、AtomicStampedReference

public class Test6 {
    private static final Logger LOGGER = LoggerFactory.getLogger(Test5.class);
    static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);

    public static void main(String[] args) throws InterruptedException {
        LOGGER.debug("main start ...");
        // 获取值A
        // 这个共享变量被其他线程修改过
        String prev = ref.getReference();
        // 获取版本号
        int stamp = ref.getStamp();
        LOGGER.debug("main before {}", stamp);
        other();
        Thread.sleep(1000);
        // 尝试改为C
        LOGGER.debug("main before {}", stamp);
        // 额外参数为原版本号和新版本号,会比较版本号的值来解决ABA问题
        LOGGER.debug("change A to C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
    }

    private static void other() {
        new Thread(() -> {
            int stamp1 = ref.getStamp();
            LOGGER.debug("t1 before {}", stamp1);
            LOGGER.debug("change A to B {}", ref.compareAndSet(ref.getReference(), "B", stamp1, stamp1 + 1));
        }, "t1").start();
        new Thread(() -> {
            int stamp2 = ref.getStamp();
            LOGGER.debug("t2 before {}", stamp2);
            LOGGER.debug("change B to A {}", ref.compareAndSet(ref.getReference(), "A", stamp2, stamp2 + 1));
        }, "t2").start();
    }
}

结果:
10:27:15.667 [main] DEBUG com.multiThreads.test16.Test5 - main start ...
10:27:15.670 [main] DEBUG com.multiThreads.test16.Test5 - main before 0
10:27:15.729 [t1] DEBUG com.multiThreads.test16.Test5 - t1 before 0
10:27:15.729 [t1] DEBUG com.multiThreads.test16.Test5 - change A to B true
10:27:15.730 [t2] DEBUG com.multiThreads.test16.Test5 - t2 before 1
10:27:15.730 [t2] DEBUG com.multiThreads.test16.Test5 - change B to A true
10:27:16.730 [main] DEBUG com.multiThreads.test16.Test5 - main before 0
10:27:16.730 [main] DEBUG com.multiThreads.test16.Test5 - change A to C false

AtomicStampedReference可以给原子引用加上版本号,追踪原子引用整个的变化过程,如:A -> B -> A -> C,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。

但是有时候,并不关心引用变量更改了几次,只是单纯地关心是否更改过,所以就有了AtomicMarkableReference。

4.1.3、AtomicMarkableReference

public class Test7 {
    private static final Logger LOGGER = LoggerFactory.getLogger(Test7.class);

    public static void main(String[] args) throws InterruptedException {
        GarbageBag bag = new GarbageBag("装满了垃圾");
        // 参数2 mark 可以看作一个标记,表示垃圾袋满了
        AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);

        LOGGER.debug("start");
        GarbageBag prev = ref.getReference();
        LOGGER.debug(prev.toString());

        new Thread(()->{
            LOGGER.debug("start...");
            bag.setBag("空垃圾袋");
            // 不换垃圾袋,仅拿出垃圾
            ref.compareAndSet(bag, bag, true, false);
            LOGGER.debug(bag.toString());
        }, "保洁阿姨").start();

        Thread.sleep(1000);
        LOGGER.debug("想换一只新垃圾袋?");
        boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾袋"), true, false);
        LOGGER.debug("换了么?" + success);
        LOGGER.debug(ref.getReference().toString());
    }
}

@Data
class GarbageBag {
    private String bag;
    public GarbageBag(String bag) {
        this.bag = bag;
    }

    @Override
    public String toString() {
        return bag;
    }
}

结果:
11:05:12.166 [main] DEBUG com.multiThreads.test16.Test7 - start
11:05:12.169 [main] DEBUG com.multiThreads.test16.Test7 - 装满了垃圾
11:05:12.229 [保洁阿姨] DEBUG com.multiThreads.test16.Test7 - start...
11:05:12.229 [保洁阿姨] DEBUG com.multiThreads.test16.Test7 - 空垃圾袋
11:05:13.228 [main] DEBUG com.multiThreads.test16.Test7 - 想换一只新垃圾袋?
11:05:13.228 [main] DEBUG com.multiThreads.test16.Test7 - 换了么?false
11:05:13.228 [main] DEBUG com.multiThreads.test16.Test7 - 空垃圾袋

5、原子数组

  • AtomicIntegerArray;
  • AtomicLongArray;
  • AtomicReferenceArray。

有如下方法:

public class Test8 {
    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)
        );
    }

    /**
     * 方法内会启动10个线程,并发让数组所有元素总共自增10000次
     *
     * @param arraySupplier:Supplier:提供者,()->结果。提供数组、可以是线程不安全数组或线程安全数组
     * @param lengthFun:Function:函数,(参数)->结果,还有一种为BiFunction 为 (参数1,参数2) -> 结果。获取数组长度的方法
     * @param putConsumer:Consumer:消费者,一个参数,没结果,(参数)->void。BiConsumer为(参数1,参数2)->{} 自增方法,回传array,length,index。
     * @param printConsumer:打印数组的方法
     * @param <T>
     */
    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);
    }
}

结果:
[9780, 9779, 9782, 9788, 9778, 9772, 9777, 9776, 9776, 9780]
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]

6、字段更新器

  • AtomicReferenceFieldUpdater // 域 字段;
  • AtomicIntegerFieldUpdater;
  • AtomicLongFieldUpdater。

利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合volatile修饰的字段使用,否则会出现异常。

public class Test9 {
    public static void main(String[] args) {
        Student stu = new Student();

        AtomicReferenceFieldUpdater updater = AtomicReferenceFieldUpdater.newUpdater(Student.class, String.class, "name");

        // 参数:要更新的对象,原来的值,更新的值
        System.out.println(updater.compareAndSet(stu, null, "张三"));
        System.out.println(stu);
    }
}

class Student {
    volatile String name;

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

结果:
true
Student{name='张三'}

7、原子累加器

累加器性能比较。

public class Test10 {
    public static void main(String[] args) {
        // 使用原子整数
        for (int i = 0; i < 5; i++) {
            demo(
                    () -> new AtomicLong(0),
                    (adder) -> adder.getAndIncrement()
            );
        }

        System.out.println();

        // 使用原子累加器
        for (int i = 0; i < 5; i++) {
            demo(
                    () -> new LongAdder(),
                    adder -> adder.increment()
            );
        }
    }

    /**
     *
     * @param adderSupplier:提供累加器对象
     * @param action:执行累加操作
     * @param <T>
     */
    private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
        T adder = adderSupplier.get();
        ArrayList<Thread> ts = new ArrayList<>();
        // 4 个线程,每人累加50万
        for (int i = 0; i < 4; i++) {
            ts.add(new Thread(()->{
                for (int j = 0; j < 500000; j++) {
                    action.accept(adder);
                }
            }));
        }
        long start = System.nanoTime();
        ts.forEach(t -> t.start());
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        long end = System.nanoTime();
        System.out.println(adder + " cost:" + (end - start)/1000_000);
    }
}

结果:
2000000 cost:26
2000000 cost:26
2000000 cost:30
2000000 cost:31
2000000 cost:30

2000000 cost:14
2000000 cost:9
2000000 cost:9
2000000 cost:9
2000000 cost:8

性能提升的原因很简单,就是在竞争时,设置多个累加单元,Thread-0累加Cell[0],而Thread-1累加Cell[1]...最后将结果汇总。这样它们在累加时操作不同的Cell变量,因此减少了CAS重试失败次数,从而提高性能。

7.1、LongAdder原理分析

LongAdder是并发大师@author Doug Lea (大哥李)的作品,设计得非常精巧。

LongAdder类有几个关键域:

// 累加单元数组,懒惰初始化
transient volatile Cell[] cells;

// 基础值,如果没有竞争,则用cas累加这个域
transient volatile long base;

// 在cells创建或扩容时,置为1,表示加锁
transient volatile int cellsBusy;

7.1.1、CAS锁

// 实际尽量不要使用CasLock
class CasLock {
    private static final Logger LOGGER = LoggerFactory.getLogger(CasLock.class);
    // 0:没加锁;1:加了锁
    private AtomicInteger state = new AtomicInteger(0);

    public void lock() {
        while(true) {
            if (state.compareAndSet(0, 1)) {
                break;
            }
        }
    }

    public void unlock() {
        LOGGER.debug("unlock...");
        state.set(0);
    }

    public static void main(String[] args) {
        CasLock lock = new CasLock();
        new Thread(()->{
            LOGGER.debug("begin...");
            lock.lock();
            LOGGER.debug("lock...");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();

        new Thread(()->{
            LOGGER.debug("begin...");
            lock.lock();
            try {
                LOGGER.debug("lock...");
            } finally {
                lock.unlock();
            }
        }).start();
    }
}

结果:
15:35:45.783 [Thread-0] DEBUG com.multiThreads.test16.CasLock - begin...
15:35:45.783 [Thread-1] DEBUG com.multiThreads.test16.CasLock - begin...
15:35:45.786 [Thread-0] DEBUG com.multiThreads.test16.CasLock - lock...
15:35:46.786 [Thread-0] DEBUG com.multiThreads.test16.CasLock - unlock...
15:35:46.786 [Thread-1] DEBUG com.multiThreads.test16.CasLock - lock...
15:35:46.786 [Thread-1] DEBUG com.multiThreads.test16.CasLock - unlock...

7.1.2、原理之伪共享

其中Cell即为累加单元。

    @sum.misc.Contended
    static final class Cell {
        volatile long value;
        
        Cell(long x) {value = x;}
        
        // 最重要的方法,用来cas方式进行累加,prev表示旧值,next表示新值
        final boolean cas(long prev, long next) {
            return UNSAFE.compareAndSwapLong(this, valueOffset, prev, next);
        }
        
        // 省略不重要代码
        ...
    }

因为CPU与内存的速度差异很大,需要靠预读数据至缓存来提升效率。而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是64 byte(8个long),缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中,CPU要保证数据的一致性,如果某个CPU核心更改了数据,其他CPU核心对应的整个缓存行必须失效。

因为Cell是数组形式,在内存中是连续存储的,一个Cell为24字节(16字节的对象头和8字节的value),因此缓存行可以存下2个Cell对象。这样问题来了:

  • Core-0要修改Cell[0];
  • Core-1要修改Cell[1]。

无论谁修改成功,都会导致对方Core的缓存行失效,比如Core-0中Cell[0]=6000,Cell[1]=8000要累加Cell[0]=6001,Cell[1]=8000,这时会让Core-1的缓存行失效。

@sun.misc.Contended用来解决这个问题,它的原理是使用此注解的对象或字段的前后各增加128字节的大小的padding,从而让CPU将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效。

8、Unsafe

8.1、概述

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

class UnsafeAccessor {
    static Unsafe unsafe;
    
    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true); // 允许访问私有成员
            unsafe = (Unsafe) theUnsafe.get(null); // 获得Unsafe对象
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    
    static Unsafe getUnsafe() {
        return unsafe;
    }
}

8.2、Unsafe CAS操作

public class Test13 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true); // 允许访问私有成员
        Unsafe unsafe = (Unsafe) theUnsafe.get(null); // 获得Unsafe对象

        // 1、获取域的偏移地址
        long idOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("id"));
        long nameOffset = unsafe.objectFieldOffset(Teacher.class.getDeclaredField("name"));
        // 2、执行CAS操作
        Teacher t = new Teacher();
        unsafe.compareAndSwapInt(t, idOffset, 0, 1); // 修改id:0->1
        unsafe.compareAndSwapObject(t, nameOffset, null, "张三"); // 修改name:null->张三
        // 3、验证
        System.out.println(t);
    }
}

@Data
class Teacher {
    volatile int id;
    volatile String name;
}

结果:
Teacher(id=1, name=张三)
public class Test14 {
    public static void main(String[] args) {
        Account.demo(new MyAtomicInteger(10000));
    }
}

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

    // 取款
    void withdraw(Integer amount);

    /**
     * 方法内部会启动1000个线程,每个线程做 -10元 的操作
     * 如果初始余额为10000那么正确的结果应当是0
     */
    static void demo(Account account) {
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            threads.add(new Thread(()->{
                account.withdraw(10);
            }));
        }
        long start = System.nanoTime();
        threads.forEach(Thread::start);
        threads.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");
    }
}

class UnsafeAccessor {
    static Unsafe unsafe;

    static {
        try {
            Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafe.setAccessible(true); // 允许访问私有成员
            unsafe = (Unsafe) theUnsafe.get(null); // 获得Unsafe对象
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    static Unsafe getUnsafe() {
        return unsafe;
    }
}

class MyAtomicInteger implements Account{
    private static final Unsafe UNSAFE;
    private static final long valueOffset;
    private volatile int value;
    static {
        try {
            UNSAFE = UnsafeAccessor.getUnsafe();
            valueOffset = UNSAFE.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    public MyAtomicInteger(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public void decrement(int amount) {
        while(true) {
            int prev = this.value;
            int next = prev - amount;
            if (UNSAFE.compareAndSwapInt(this, valueOffset, prev, next)) {
                break;
            }
        }
    }

    @Override
    public Integer getBalance() {
        return getValue();
    }

    @Override
    public void withdraw(Integer amount) {
        decrement(amount);
    }
}

结果:
0 cost: 111ms

posted on 2022-09-18 15:47  啊噢1231  阅读(25)  评论(0编辑  收藏  举报

导航

回到顶部