• JUC-无锁


    一、问题引出

    1.1 需求与问题

    需求:模拟一千个线程进行取款操作,需要保证取款方法的线程安全。

    • 代码示例(线程不安全)
    public interface Account {
    
        /**
         * 获取余额。
         *
         * @return {@link Integer}
         */
    
        Integer getBalance();
    
        /**
         * 取款。
         *
         * @param amount 数额
         */
    
        void withdraw(Integer amount);
    
        /**
         * 模拟一千个用户取款的方法:
         * 方法内会启动 1000 个线程,每个线程做 -1 元 的操作;
         * 如果初始余额为 1000 那么正确的结果应当是 0。
         *
         * @param account 账户
         * @throws InterruptedException 中断异常
         */
    
        static void oneThousandUsersWithdraw(Account account) throws InterruptedException {
    
            List<Thread> threads = new ArrayList<>();
    
            Instant start = Instant.now();
    
            for (int i = 0; i < 1000; i++) {
                threads.add(new Thread(() -> account.withdraw(1)));
            }
    
            threads.forEach(Thread::start);
    
            for (Thread thread : threads) {
                thread.join();
            }
    
            Instant end = Instant.now();
            long costTime = Duration.between(start, end).toMillis();
            System.out.println("余额为:" + account.getBalance() + ",花费时间为:" + costTime + "ms。");
        }
    
    
        @AllArgsConstructor
        class AccountUnsafe implements Account {
    
            private Integer balance;
    
            @Override
            public Integer getBalance() {
                return balance;
            }
    
            @Override
            public void withdraw(Integer amount) {
                balance -= amount;
            }
    
            public static void main(String[] args) throws InterruptedException {
                Account.oneThousandUsersWithdraw(new AccountUnsafe(1000));
                // 第一次执行结果:余额为:7,花费时间为:86ms。
                // 第二次执行结果:余额为:3,花费时间为:89ms。
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    1.2 解决方式

    • 使用『锁』方式解决线程不安全问题
        @AllArgsConstructor
        class AccountSafeByLock implements Account {
    
            private Integer balance;
    
            @Override
            public synchronized Integer getBalance() {
                return balance;
            }
    
            @Override
            public synchronized void withdraw(Integer amount) {
                balance -= amount;
            }
    
            public static void main(String[] args) throws InterruptedException {
                Account.oneThousandUsersWithdraw(new AccountSafeByLock(1000));
                // 第一次执行结果:余额为:0,花费时间为:92ms。
                // 第二次执行结果:余额为:0,花费时间为:95ms。
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 使用『无锁』方式解决线程不安全问题
        class AccountSafeByNoLock implements Account {
    
            private final AtomicInteger balance;
    
            public AccountSafeByNoLock(Integer balance) {
                this.balance = new AtomicInteger(balance);
            }
    
            @Override
            public Integer getBalance() {
                return this.balance.get();
            }
    
            @Override
            public void withdraw(Integer amount) {
                while (true) {
                    int prev = getBalance();
                    int next = prev - amount;
                    if (this.balance.compareAndSet(prev, next)) {
                        break;
                    }
                }
            }
    
            public static void main(String[] args) throws InterruptedException {
                Account.oneThousandUsersWithdraw(new AccountSafeByNoLock(1000));
                // 第一次执行结果:余额为:0,花费时间为:84ms。
                // 第二次执行结果:余额为:0,花费时间为:85ms。
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    二、CAS 与 volatile

    2.1 CAS

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

    • 代码说明
            @Override
            public void withdraw(Integer amount) {
                while (true) {
                    // 1.假设此处取到值为 1000。
                    int prev = getBalance();
                    // 2.在 1000 基础上减 1 = 999。
                    int next = prev - amount;
                    /* *
                     * 3.compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值:
                     *  3.1.不一致了,next 作废,返回 false 表示失败。
                     *  比如,别的线程已经做了减法,当前值已经被减成了 999,那么本线程的这次 999 就作废了,进入 while 下次循环重试。
                     *
                     *  3.2.一致,以 next 设置为新值,返回 true 表示成功,并结束循环。
                     */
                    if (this.balance.compareAndSet(prev, next)) {
                        break;
                    }
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 其中的关键是 compareAndSet(),它的简称就是 CAS (也有 Compare And Swap 的说法),它必须是原子操作
    • 示意图

    • 注意
      • 其实 CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核 cpu 和多核 cpu 下都能够保证『比较-交换』的原子性。
      • 在多核状态下,某个核执行到带 lock 的指令时,cpu 会让总线锁住,当这个核把此指令执行完毕,再开启总线。这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的。

    2.2 volatile

    • 获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。
    • 它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存。即一个线程对 volatile 变量的修改,对另一个线程可见。
    • CAS 必须借助 volatile 才能读取到共享变量的最新值来实现『比较-交换』的效果。

    2.3 为什么无锁效率高?

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

    2.4 CAS 的特点

    • 结合 CASvolatile 可以实现无锁并发,适用于线程数少多核 cpu 的场景下。
    • CAS 是基于乐观锁的思想:不怕别的线程来修改共享变量,就算改了也没关系,大不了再进行重试。
    • synchronized 是基于悲观锁的思想:得防着其它线程来修改共享变量,我上了锁你们都别想改,我改完了解开锁,你们才有机会。
    • CAS 体现的是无锁并发、无阻塞并发:
      • 因为没有使用 synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一。
      • 如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响

    三、原子整数

    • java.util.concurrent 并发包提供了:
      • AtomicBoolean
      • AtomicInteger
      • AtomicLong
    • 此处以 AtomicInteger 为例:
    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, 返回 0)
    System.out.println(i.addAndGet(-5));
    
    // 获取并更新(i = 0, p 为 i 的当前值, 结果 i = -2, 返回 0)
    // 其中函数中的操作能保证原子,但函数需要无副作用
    System.out.println(i.getAndUpdate(p -> p - 2));
    
    // 更新并获取(i = -2, p 为 i 的当前值, 结果 i = 0, 返回 0)
    // 其中函数中的操作能保证原子,但函数需要无副作用
    System.out.println(i.updateAndGet(p -> p + 2));
    
    // 获取并计算(i = 0, p 为 i 的当前值, x 为参数1, 结果 i = 10, 返回 0)
    // 其中函数中的操作能保证原子,但函数需要无副作用
    // getAndUpdate 如果在 lambda 中引用了外部的局部变量,要保证该局部变量是 final 的
    // getAndAccumulate 可以通过 参数1 来引用外部的局部变量,但因为其不在 lambda 中因此不必是 final
    System.out.println(i.getAndAccumulate(10, (p, x) -> p + x));
    
    // 计算并获取(i = 10, p 为 i 的当前值, x 为参数1, 结果 i = 0, 返回 0)
    // 其中函数中的操作能保证原子,但函数需要无副作用
    System.out.println(i.accumulateAndGet(-10, (p, x) -> p + x))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    四、原子引用

    4.1 AtomicReference

    一个可以被原子化更新的对象引用,多线程操作下,不会使 AtomicReference 最终达到不一致的状态。

    • 代码示例
    @Slf4j
    public class AtomicReferenceSample {
    
        private static AtomicReference<String> ref = new AtomicReference<>("initValue");
    
        private static void otherThreadCAS() throws InterruptedException {
            new Thread(() -> {
                boolean isSuccess = ref.compareAndSet(ref.get(), "value 1");
                log.debug("t1 try set={}", isSuccess);
            }, "t1").start();
    
            TimeUnit.SECONDS.sleep(1);
    
            new Thread(() -> {
                boolean isSuccess = ref.compareAndSet(ref.get(), "value 2");
                log.debug("t2 try set={}", isSuccess);
            }, "t2").start();
        }
    
        public static void main(String[] args) throws InterruptedException {
    
            otherThreadCAS();
            TimeUnit.SECONDS.sleep(1);
            boolean isSuccess = ref.compareAndSet(ref.get(), "value 3");
            log.debug("main try set={}", isSuccess);
            log.debug("last value={}", ref.get());
    
            // [t1] t1 try set=true
            // [t2] t2 try set=true
            // [main] main try set=true
            // last value=value 3
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 问题点:主线程仅能判断出共享变量的值与最初值是否相同,不能感知到值改变后又改回的中间情况。
    • 只要有其它线程动过了共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个标记,此时推荐使用 AtomicStampedReference

    4.2 AtomicStampedReference

    一个带标记的原子化更新对象引用。

    • 代码示例
    @Slf4j
    public class AtomicStampedReferenceSample {
    
        static AtomicStampedReference<String> ref = new AtomicStampedReference<>("initValue", 0);
    
        private static void otherThreadCAS() throws InterruptedException {
    
            new Thread(() -> {
                log.debug("t1 try set={}", ref.compareAndSet(ref.getReference(), "value 1",
                        ref.getStamp(), ref.getStamp() + 1));
                log.debug("t1 stamp={}", ref.getStamp());
            }, "t1").start();
    
            TimeUnit.SECONDS.sleep(1);
    
            new Thread(() -> {
                log.debug("t2 try set={}", ref.compareAndSet(ref.getReference(), "initValue",
                        ref.getStamp(), ref.getStamp() + 1));
                log.debug("t2 stamp={}", ref.getStamp());
            }, "t2").start();
        }
    
    
        public static void main(String[] args) throws InterruptedException {
            // 获取值。
            String prev = ref.getReference();
            // 获取版本号。
            int stamp = ref.getStamp();
            log.debug("main stamp={}", stamp);
            // 模拟中间有其它线程干扰。
            otherThreadCAS();
            TimeUnit.SECONDS.sleep(1);
            // 尝试改为 value 3
            log.debug("main try set={}", ref.compareAndSet(prev, "value 3", stamp, stamp + 1));
    
            // [main] main stamp=0
            // [t1] t1 try set=true
            // [t1] t1 stamp=1
            // [t2] t2 try set=true
            // [t2] t2 stamp=2
            // [main] main try set=false
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程。但有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,此时就推荐使用 AtomicMarkableReference

    4.3 AtomicMarkableReference

    维护一个对象引用以及一个整数 “stamp”,它可以被原子化地更新。

    • 代码示例
    @Slf4j
    public class AtomicMarkableReferenceSample {
    
        private static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("initValue", true);
    
        public static void main(String[] args) throws InterruptedException {
    
            // 主线程获取值。
            String prev = ref.getReference();
    
            log.debug("main get ref={}", prev);
    
            // 其他线程改变了引用变量。
            new Thread(() -> {
                ref.set("value 1", false);
                log.debug("t1 set value");
            }, "t1").start();
    
            TimeUnit.SECONDS.sleep(1);
    
            // 主线程尝试设置值。
            boolean isSuccess = ref.compareAndSet(prev, "value 2", true, false);
            log.debug("main try set={}", isSuccess);
    
            // [main] main get ref=initValue
            // [t1] t1 set value
            // [main] main try set=false
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    五、原子数组

    • AtomicIntegerArray
    • AtomicLongArray
    • AtomicReferenceArray
    • 此处以 AtomicIntegerArray 为例:
    @Slf4j
    public class AtomicIntegerArraySample {
    
        /**
         * 示例一:使用不安全的整型数组。
         *
         * @throws InterruptedException 中断异常
         */
        
        @Test
        public void unSafeArray() throws InterruptedException {
    
            // 创建一个长度为 2 的数组。
            Integer[] ints = new Integer[2];
            List<Thread> threads = new ArrayList<>();
            // 3个线程对它执行操作。
            for (int i = 0; i < 3; i++) {
                // 每个线程对数组做1次操作 。
                int finalI = i;
                threads.add(new Thread(() -> {
                    for (int j = 0; j <= 1; j++) {
                        ints[finalI] = j;
                    }
                }, "t_" + i));
            }
    
            // 启动线程,并等待所有线程执行结束。
            threads.forEach(Thread::start);
            for (Thread thread : threads) {
                thread.join();
            }
    
            log.debug("array={}", Arrays.toString(ints));
            // Exception in thread "t_2" java.lang.ArrayIndexOutOfBoundsException
            // [main] array=[1, 1]
        }
    
    
        /**
         * 示例二:使用原子的整型数组。
         *
         * @throws InterruptedException 中断异常
         */
    
        @Test
        public void safeArray() throws InterruptedException {
    
            // 创建一个长度为 2 的『原子数组』。
            AtomicIntegerArray ints = new AtomicIntegerArray(2);
            List<Thread> threads = new ArrayList<>();
            for (int i = 0; i < 3; i++) {
                threads.add(new Thread(() -> {
                    for (int j = 0; j <= 1; j++) {
                        ints.getAndIncrement(j);
                    }
                }, "t_" + i));
            }
    
            threads.forEach(Thread::start);
            for (Thread thread : threads) {
                thread.join();
            }
    
            log.debug("array={}", ints);
            // [main] array=[3, 3]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    六、字段更新器

    • AtomicReferenceFieldUpdater // 域 字段
    • AtomicIntegerFieldUpdater
    • AtomicLongFieldUpdater
    • 注意:使用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现异常
    • 此处以 AtomicIntegerFieldUpdater 为例:
    @Slf4j
    public class AtomicIntegerFieldUpdaterSample {
    
        /* *
         * 注意:要使用字段更新器必须使用 volatile 进行修饰。
         */
    
        private volatile int field = 0;
    
        public static void main(String[] args) {
    
            AtomicIntegerFieldUpdater<AtomicIntegerFieldUpdaterSample> aifu =
                    AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdaterSample.class, "field");
    
            AtomicIntegerFieldUpdaterSample sample = new AtomicIntegerFieldUpdaterSample();
    
            // 将 field 字段值更新为 1。
            aifu.compareAndSet(sample, 0, 1);
            log.debug("updated field ={}", sample.field);
            // updated field =1
    
            // 将 field 字段值更新为 2。
            aifu.compareAndSet(sample, 1, 2);
            log.debug("updated field ={}", sample.field);
            // updated field =2
    
            // 期望值不存在,则修改失败。
            aifu.compareAndSet(sample, 1, 3);
            log.debug("updated field ={}", sample.field);
            // updated field =2
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    七、原子累加器性能比较

    比较 AtomicLongLongAdder 性能差异。

    • 代码示例
    @Slf4j
    public class AtomicLongAndLongAdderComparePerfSample {
    
        /**
         * 用于累加器比较性能的方法。
         *
         * @param sup    供给型接口 - 获取累加器。
         * @param action 消费型接口 - 接收值。
         * @throws InterruptedException 中断异常
         */
    
        private static <T> void comparePerf(Supplier<T> sup, Consumer<T> action) throws InterruptedException {
            T adder = sup.get();
            Instant start = Instant.now();
            List<Thread> ts = new ArrayList<>();
            // 40 个线程,每人累加 50 万
            for (int i = 0; i < 40; i++) {
                ts.add(new Thread(() -> {
                    for (int j = 0; j < 50_0000; j++) {
                        action.accept(adder);
                    }
                }));
            }
    
            ts.forEach(Thread::start);
    
            for (Thread t : ts) {
                t.join();
            }
    
            Instant end = Instant.now();
            long cost = Duration.between(start, end).toMillis();
            log.debug("total={},cost={}ms", adder, cost);
        }
    
        public static void main(String[] args) throws InterruptedException {
    
            // 两种累加器,各输出3次的结果。
            for (int i = 0; i < 3; i++) {
                comparePerf(LongAdder::new, LongAdder::increment);
            }
            // total=20000000,cost=79ms
            // total=20000000,cost=10ms
            // total=20000000,cost=9ms
    
            log.debug("------------------------------------------------");
    
            for (int i = 0; i < 3; i++) {
                comparePerf(AtomicLong::new, AtomicLong::getAndIncrement);
            }
            // total=20000000,cost=271ms
            // total=20000000,cost=276ms
            // total=20000000,cost=202ms
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 结论LongAdder 性能优于 AtomicLong
    • LongAdder 实现思路:性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 cas 重试失败,从而提高性能

    八、Unsafe

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

    8.1 CAS 操作

    • 注意事项(这里 Unsafe 名字的 “不安全” 并不是指"线程不安全",而是指操作底层的不安全性,具体原因如下)

      • 不受 JVM 管理,也就意味着无法被 GC,需要我们手动 GC稍有不慎就会出现内存泄漏
      • Unsafe 的不少方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量要自己计算,一旦出现问题就是 JVM 崩溃级别的异常,会导致整个 JVM 实例崩溃,表现为应用程序直接崩掉
      • 直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率。
    • 代码示例

    public class UnsafeAccessor {
    
        private static final 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;
        }
    
    }
    
    @Slf4j
    @Data
    class Student {
    
        private volatile int id;
        private volatile String name;
    
        public static void main(String[] args) throws NoSuchFieldException {
    
            Unsafe unsafe = UnsafeAccessor.getUnsafe();
            Field id = Student.class.getDeclaredField("id");
            Field name = Student.class.getDeclaredField("name");
            // 获得成员变量的偏移量。
            long idOffset = unsafe.objectFieldOffset(id);
            long nameOffset = unsafe.objectFieldOffset(name);
            Student student = new Student();
            // 使用 cas 方法替换成员变量的值。
            unsafe.compareAndSwapInt(student, idOffset, 0, 20);
            unsafe.compareAndSwapObject(student, nameOffset, null, "张三");
    
            log.debug("student={}", student);
            // student=Student(id=20, name=张三)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    8.2 解决最开始提出的问题

    需求:通过 UnsafeCAS 方式,编写自定义的 AtomicData 类并实现 Account 接口,最终完成模拟一千个取款的线程安全操作。

    • 代码示例
    public class AtomicData {
    
        /**
         * 变量 - 使用 volatile 修饰。
         */
        private volatile int data;
    
        /**
         * 操作底层的实例。
         */
    
        private static final Unsafe UNSAFE = UnsafeAccessor.getUnsafe();
    
        /**
         * 偏移量。
         */
    
        private static final long DATA_OFFSET;
    
        static {
    
            try {
                Field field = AtomicData.class.getDeclaredField("data");
                // 偏移量,用于 Unsafe 直接访问该属性。
                DATA_OFFSET = UNSAFE.objectFieldOffset(field);
            } catch (NoSuchFieldException e) {
                throw new Error(e);
            }
    
        }
    
        public AtomicData(int data) {
            this.data = data;
        }
    
        public int getData() {
            return this.data;
        }
    
        /**
         * CAS 自减方法。
         *
         * @param amount 数量。
         */
    
        public void decrease(int amount) {
            while (true) {
                int oldValue = data;
                int newValue = oldValue - amount;
                // cas 尝试修改 data 为 旧值 + amount,如果期间旧值被别的线程改了,返回 false。
                if (UNSAFE.compareAndSwapInt(this, DATA_OFFSET, oldValue, newValue)) {
                    return;
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
    
            // 使用自定义实现的 AtomicData 完成原子操作。
            AtomicData atomicData = new AtomicData(1000);
    
            Account.oneThousandUsersWithdraw(new Account() {
                @Override
                public Integer getBalance() {
                    return atomicData.getData();
                }
    
                @Override
                public void withdraw(Integer amount) {
                    atomicData.decrease(amount);
                }
            });
    
            // 第一次运行:balance:0,cost:50ms
            // 第二次运行:balance:0,cost:49ms
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 总结:可以看到使用 Unsafe 直接操作底层的效率更高

    九、结束语


    “-------怕什么真理无穷,进一寸有一寸的欢喜。”

    微信公众号搜索:饺子泡牛奶

  • 相关阅读:
    SpringMVC---SSM整合&&异常处理&&拦截器
    【365天深度学习训练营】第三周 天气识别
    ElasticSearch-查询语法(聚合查询)
    台式机电脑电源的使用
    vue+Vant,关闭Popup弹框,遮罩层并没有消失
    对不起,我错了,这代码不好写
    网络安全(黑客)自学
    圆弧插补-逐点比较法
    下班路上捡了一部手机,我用8年开发知识主动找到了失主
    [Mysql]数据库约束
  • 原文地址:https://blog.csdn.net/weixin_48776531/article/details/126826125