• 138.【JUC并发编程- 03】


    (六)、共享模型之无锁

    • CAS 与 volatile
    • 原子整数
    • 原子引用
    • 原子累加器
    • Unsafe

    1.问题提出

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

    package com.jsxs.Test;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 14:58
     * @PackageName:com.jsxs.Test
     * @ClassName: Account
     * @Description: TODO
     * @Version 1.0
     */
    public interface Account {
        // 获取余额
        Integer getBalance();
    
        // 取款
        void withdraw(Integer amount);
    
        /**
         * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
         * 如果初始余额为 10000 那么正确的结果应当是 0
         */
        static void demo(Account account) {
            List<Thread> ts = new ArrayList<>();
            long start = System.nanoTime();
            for (int i = 0; i < 1000; i++) {
                ts.add(new Thread(() -> {
                    account.withdraw(10);
                }));
            }
            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");
        }
    }
    
    • 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

    原有实现并不是线程安全的

    package com.jsxs.Test;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 14:59
     * @PackageName:com.jsxs.Test
     * @ClassName: AccountUnsafe
     * @Description: TODO
     * @Version 1.0
     */
    public class AccountUnsafe implements Account{
        private Integer balance;
        public AccountUnsafe(Integer balance) {
            this.balance = balance;
        }
        @Override
        public Integer getBalance() {
            return balance;
        }
        @Override
        public void withdraw(Integer amount) {
            balance -= amount;
        }
    	// 进行测试
        public static void main(String[] args) {
            Account.demo(new AccountUnsafe(10000));
        }
    }
    
    
    • 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

    某次的执行结果

    在这里插入图片描述

    (1).为什么不安全?

    withdraw 方法: 对共享资源产生了竞态条件

        public void withdraw(Integer amount) {
            balance -= amount;
        }
    
    • 1
    • 2
    • 3

    对应的字节码

    ALOAD 0 // <- this
    ALOAD 0
    GETFIELD cn/itcast/AccountUnsafe.balance : Ljava/lang/Integer; // <- this.balance
    INVOKEVIRTUAL java/lang/Integer.intValue ()I // 拆箱
    ALOAD 1 // <- amount
    INVOKEVIRTUAL java/lang/Integer.intValue ()I // 拆箱
    ISUB // 减法
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; // 结果装箱
    PUTFIELD cn/itcast/AccountUnsafe.balance : Ljava/lang/Integer; // -> this.balance
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    多线程执行流程

    ALOAD 0 // thread-0 <- this 
    ALOAD 0 
    GETFIELD cn/itcast/AccountUnsafe.balance // thread-0 <- this.balance 
    INVOKEVIRTUAL java/lang/Integer.intValue // thread-0 拆箱
    ALOAD 1 // thread-0 <- amount 
    INVOKEVIRTUAL java/lang/Integer.intValue // thread-0 拆箱
    ISUB // thread-0 减法
    INVOKESTATIC java/lang/Integer.valueOf // thread-0 结果装箱
    PUTFIELD cn/itcast/AccountUnsafe.balance // thread-0 -> this.balance 
     
    ALOAD 0 // thread-1 <- this 
    ALOAD 0 
    GETFIELD cn/itcast/AccountUnsafe.balance // thread-1 <- this.balance 
    INVOKEVIRTUAL java/lang/Integer.intValue // thread-1 拆箱
    ALOAD 1 // thread-1 <- amount 
    INVOKEVIRTUAL java/lang/Integer.intValue // thread-1 拆箱
    ISUB // thread-1 减法
    INVOKESTATIC java/lang/Integer.valueOf // thread-1 结果装箱
    PUTFIELD cn/itcast/AccountUnsafe.balance // thread-1 -> this.balance
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    (2).安全实现_使用锁

    首先想到的是给 Account 对象加锁

    在这里插入图片描述

    (3).安全实现_使用CAS
    package com.jsxs.Test;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 15:15
     * @PackageName:com.jsxs.Test
     * @ClassName: AccountSafe
     * @Description: TODO
     * @Version 1.0
     */
    public class AccountSafe implements Account {
    
        // 1. AtomicInteger 原子性整形  ⭐
        private AtomicInteger balance;
    
        public AccountSafe(Integer balance) {
    
            this.balance = new AtomicInteger(balance);  // ⭐⭐ 通过AtomicInteger 进行构造
        }
    
        @Override
        public Integer getBalance() {
            return balance.get();  //  ⭐⭐⭐ 调用原子类的get方法
        }
    
        @Override
        public void withdraw(Integer amount) {
    
            while (true) {
                // 获取余额的最新值 调用get方法   ⭐⭐⭐
                int prev = balance.get();
                // 修改后的余额=余额的最新值减去取款金额  ⭐⭐⭐⭐
                int next = prev - amount;
                // 局部变量都是存储在线程的工作内存上(并不是主存上),  调用这个方法就是讲内存和主存同步 ⭐⭐⭐⭐⭐
                if (balance.compareAndSet(prev, next)) { // 第一个参数: 修改前的值,第二个参数: 修改后的值
                    break;
                }
            }
    
            // 可以简化为下面的方法
            // balance.addAndGet(-1 * amount);
            
        }
    
        public static void main(String[] args) {
            Account.demo(new AccountSafe(10000));
        }
    }
    
    • 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

    在这里插入图片描述

    2.CAS与volatile

    (1).CAS_原理介绍

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

        public void withdraw(Integer amount) {
            // 需要不断尝试,直到成功为止
            while (true) {
                // 比如拿到了旧值 1000
                int prev = balance.get();
                // 在这个基础上 1000-10 = 990
                int next = prev - amount;
     /*
     compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值
     - 不一致了,next 作废,返回 false 表示失败
     比如,别的线程已经做了减法,当前值已经被减成了 990
     那么本线程的这次 990 就作废了,进入 while 下次循环重试
     - 一致,以 next 设置为新值,返回 true 表示成功
     */
                if (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 的说法),它必须是原子操作。

    解释:

    比如说线程1先获取余额为100,然后减去10结果为90,此时还没有来得及进行cas的操作,线程2已经把原来100的值修改为了90。经比较线程2的将prev90更新到主存,主存prev为90与线程1获取的prev值100是不相等,那么返回fasle,线程1再次进行重新-10操作.

    在这里插入图片描述

    注意

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

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

    (2).CAS_Debug分析

    1. 设置线程数为1,然后在CAS的地方进行断点
    在这里插入图片描述
    2.查看断点达到的信息,并且给共享变量赋值9000
    在这里插入图片描述
    3.获取最新值再次比较,如果其他线程没有再次改变那么就返回true
    在这里插入图片描述

    (3).volatile

    获取共享变量时,为了保证该变量的可见性,需要使用 volatile 修饰。

    它可以用来修饰成员变量静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作 volatile 变量都是直接操作主存即一个线程对 volatile 变量的修改,对另一个线程可见。

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

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

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

    线程数不超过CPU的核定线程数都很高效,假如超过了效率就会大幅降低。

    (5).CAS的特点

    结合 CASvolatile 可以实现无锁并发适用于线程数少、多核 CPU 的场景下

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

    3.原子整形

    J.U.C 并发包提供了:

    • AtomicBoolean
    • AtomicInteger
    • AtomicLong

    以 AtomicInteger 为例

    (1).原子整数类型_ 自增自减

    原子类的所有方法都是线程安全的,都具有原子性CAS自旋的操作。所以刚才的取账问题我们可以用 addAndGet() 方法进行替换。

    package com.jsxs.Test;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 17:11
     * @PackageName:com.jsxs.Test
     * @ClassName: Test09
     * @Description: TODO
     * @Version 1.0
     */
    public class Test09 {
        public static void main(String[] args) {
            AtomicInteger integer = new AtomicInteger(1);
            System.out.println("先获取再自增的操作" + integer.getAndIncrement());  // i++
            System.out.println("先自增再获取的操作:" + integer.incrementAndGet());  // ++i
            System.out.println("先自减再获取的操作:"+integer.decrementAndGet());   // --i
            System.out.println("先获取再自检的操作:"+integer.getAndDecrement());   // i--
            System.out.println("最终得值为:"+integer);
            System.out.println("================================");
            System.out.println("自定义后增加:"+integer.getAndAdd(4));  // 先获取再+4
            System.out.println("最终得值为:"+integer);
            System.out.println("自定义先增加:"+integer.addAndGet(4));  // 先自增4 在获取
            System.out.println("最终得值为:"+integer);
            System.out.println("================================");
            
        }
    }
    
    • 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

    在这里插入图片描述
    默认都是cas自旋的操作
    在这里插入图片描述

    (2).原子整数类型_乘除模
    package com.jsxs.Test;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 17:11
     * @PackageName:com.jsxs.Test
     * @ClassName: Test09
     * @Description: TODO
     * @Version 1.0
     */
    public class Test09 {
        public static void main(String[] args) {
            AtomicInteger integer = new AtomicInteger(1);
            // 这里是一个接口类型返回值类型是int,且参数类型是int,   value是读取到的值, value*10是设置的值  会发生CAS自旋的操作
            System.out.println(integer.updateAndGet((value)->{return value*10;}));  // 先修改在获取之
            System.out.println(integer.getAndUpdate((value)->{return value*10;}));  // 先获取值再修改
            
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述
    默认都是CAS自旋的操作
    在这里插入图片描述

    4. 原子引用类型

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

    • AtomicReference
    • AtomicMarkableReference
    • AtomicStampedReference

    有如下方法

    package com.jsxs.Test;
    
    import java.math.BigDecimal;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 19:28
     * @PackageName:com.jsxs.Test
     * @ClassName: DecimalAccount
     * @Description: TODO
     * @Version 1.0
     */
    public interface DecimalAccount {
        
        // 获取余额:  钱币类型
        BigDecimal getBalance();
    
        // 取款
        void withdraw(BigDecimal amount);
    
        /**
         * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
         * 如果初始余额为 10000 那么正确的结果应当是 0
         */
        static void demo(DecimalAccount account) {
    
            List<Thread> ts = new ArrayList<>();
            for (int i = 0; i < 1000; i++) {
                ts.add(new Thread(() -> {
                    account.withdraw(BigDecimal.TEN);
                }));
            }
            ts.forEach(Thread::start);
            ts.forEach(t -> {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            System.out.println(account.getBalance());
        }
    }
    
    • 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
    (1).不安全的实现
    package com.jsxs.Test;
    
    import java.math.BigDecimal;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 19:30
     * @PackageName:com.jsxs.Test
     * @ClassName: DecimalAccountUnsafe
     * @Description: TODO
     * @Version 1.0
     */
    public class DecimalAccountUnsafe implements DecimalAccount {
    
        BigDecimal balance;
    
        public DecimalAccountUnsafe(BigDecimal balance) {
            this.balance = balance;
        }
    
        @Override
        public BigDecimal getBalance() {
            return balance;
        }
    
        @Override
        public void withdraw(BigDecimal amount) {
            BigDecimal balance = this.getBalance();
            this.balance = balance.subtract(amount);
        }
    
        public static void main(String[] args) {
            DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal("10000")));
        }
    }
    
    
    • 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

    在这里插入图片描述

    (2).安全实现_使用锁
    package com.jsxs.Test;
    
    import java.math.BigDecimal;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 20:09
     * @PackageName:com.jsxs.Test
     * @ClassName: DecimalAccountSafeLock
     * @Description: TODO
     * @Version 1.0
     */
    public class DecimalAccountSafeLock implements DecimalAccount{
        private final Object lock= new Object();
    
        BigDecimal balance;
    
        public DecimalAccountSafeLock(BigDecimal balance) {
            this.balance = balance;
        }
    
        @Override
        public BigDecimal getBalance() {
            return balance;
        }
    
        @Override
        public void withdraw(BigDecimal amount) {
            synchronized (lock){
                BigDecimal balance = this.getBalance();
                // 旧余额 - 取款 = 新余额
                this.balance = balance.subtract(amount);
            }
        }
    
        public static void main(String[] args) {
            DecimalAccount.demo(new DecimalAccountSafeLock(new BigDecimal("10000")));
        }
    
    }
    
    
    • 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
    (3).安全实现_使用 CAS (AtomicReference)
    package com.jsxs.Test;
    
    import java.math.BigDecimal;
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 19:38
     * @PackageName:com.jsxs.Test
     * @ClassName: DecimalAccountSafeCas
     * @Description: TODO
     * @Version 1.0
     */
    public class DecimalAccountSafeCas implements DecimalAccount {
        // 1.我们这里使用原子引用类型进行修饰 ⭐
        AtomicReference<BigDecimal> ref;
    
        public DecimalAccountSafeCas(BigDecimal balance) {
            // 2.创建我们的原子引用类 ⭐⭐
            ref = new AtomicReference<>(balance);
        }
    
        @Override
        public BigDecimal getBalance() {
            // 3.调用get()返回最新值 ⭐⭐⭐
            return ref.get();
        }
    
        @Override
        public void withdraw(BigDecimal amount) {
            // 4. CAS  ⭐⭐⭐⭐
            while (true) {
                // 获取现在的余额 (旧值)
                BigDecimal prev = ref.get();
                // 返回的新余额(新值) = 现在的余额 - 取款金额
                BigDecimal next = prev.subtract(amount);
                if (ref.compareAndSet(prev, next)) {  // 进行我们的CAS操作,  主存与新值进行比较,一样则false。
                    break;
                }
            }
        }
    
        public static void main(String[] args) {
            DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal("10000")));  // 构造金额为10000
        }
    }
    
    • 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

    在这里插入图片描述

    (4).ABA 问题
    1. 无线程冲突的时候
    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 20:18
     * @PackageName:com.jsxs.Test
     * @ClassName: Test10
     * @Description: TODO
     * @Version 1.0
     */
    @Slf4j(topic = "c.test10")
    public class Test10 {
        // 1. 设置原子引用类型
        static AtomicReference<String> ref = new AtomicReference<>("A");
    
        public static void main(String[] args) throws InterruptedException {
            log.debug("main start...");
            // 获取值 A
            String prev = ref.get();
            Thread.sleep(1000);
            // 尝试改为 C
            log.debug("change A->C {}", ref.compareAndSet(prev, "C"));
        }
    }
    
    • 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

    在这里插入图片描述

    1. ABA问题的展现

    A->B->A->C

    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 20:18
     * @PackageName:com.jsxs.Test
     * @ClassName: Test10
     * @Description: TODO
     * @Version 1.0
     */
    @Slf4j(topic = "c.test10")
    public class Test10 {
        // 1. 设置原子引用类型
        static AtomicReference<String> ref = new AtomicReference<>("A");
    
        public static void main(String[] args) throws InterruptedException {
            log.debug("main start...");
            // 获取值 A
            // 这个共享变量被它线程修改过?
            String prev = ref.get();
            other();
            Thread.sleep(1000);
            // 尝试改为 C
            log.debug("change A->C {}", ref.compareAndSet(prev, "A"));
        }
    
        private static void other() throws InterruptedException {
            new Thread(() -> {
                log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
            }, "t1").start();
            Thread.sleep(500);
            new Thread(() -> {
                log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A"));
            }, "t2").start();
        }
    }
    
    
    • 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

    ABA问题成立!!!!
    在这里插入图片描述

    1. 估计将A的地址进行修改之后
    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 20:18
     * @PackageName:com.jsxs.Test
     * @ClassName: Test10
     * @Description: TODO
     * @Version 1.0
     */
    @Slf4j(topic = "c.test10")
    public class Test10 {
        // 1. 设置原子引用类型
        static AtomicReference<String> ref = new AtomicReference<>("A");
    
        public static void main(String[] args) throws InterruptedException {
            log.debug("main start...");
            // 获取值 A
            // 这个共享变量被它线程修改过?
            String prev = ref.get();
            other();
            Thread.sleep(1000);
            // 尝试改为 C
            log.debug("change A->C {}", ref.compareAndSet(prev, "A"));
        }
    
        private static void other() throws InterruptedException {
            new Thread(() -> {
                log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B"));
            }, "t1").start();
            Thread.sleep(500);
            new Thread(() -> {
                log.debug("change B->A {}", ref.compareAndSet(ref.get(), new String("A")));
            }, "t2").start();
        }
    }
    
    • 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

    我们发现ABA问题将不成立!!!!
    在这里插入图片描述
    主线程仅能判断出共享变量的值最初值 A 是否相同,不能感知到这种从 A 改为 B 又 改回 A 的情况,如果主线程希望:

    (5).ABA问题解决1_AtomicStampedReference

    只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比较值是不够的,需要再加一个版本号

    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.atomic.AtomicReference;
    import java.util.concurrent.atomic.AtomicStampedReference;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 20:18
     * @PackageName:com.jsxs.Test
     * @ClassName: Test10
     * @Description: TODO
     * @Version 1.0
     */
    @Slf4j(topic = "c.test10")
    public class Test10 {
        // 1. 设置原子版本引用类型
        static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
    
        public static void main(String[] args) throws InterruptedException {
            log.debug("main start...");
            // 获取值 A
            String prev = ref.getReference();
            // 获取版本号 (提前获取⭐在其他线程进入前)
            int stamp = ref.getStamp();
            log.debug("版本 {}", stamp);
            // 如果中间有其它线程干扰,发生了 ABA 现象
            other();
            Thread.sleep(1000);
            // 尝试改为 C
            log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));  // 判断stamp当前版本号,与主存中的版本号是否一致
        }
    
        private static void other() throws InterruptedException {
            new Thread(() -> {
                log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
                        ref.getStamp(), ref.getStamp() + 1));
                log.debug("更新版本为 {}", ref.getStamp());
            }, "t1").start();
    
            Thread.sleep(500);
    
            new Thread(() -> {
                log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
                        ref.getStamp(), ref.getStamp() + 1));
                log.debug("更新版本为 {}", ref.getStamp());
            }, "t2").start();
        }
    }
    
    • 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

    在这里插入图片描述

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

    (6).ABA问题解决2_AtomicMarkableReference

    但是有时候,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference.
    在这里插入图片描述

    package com.jsxs.Test;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 21:23
     * @PackageName:com.jsxs.Test
     * @ClassName: GarbageBag
     * @Description: TODO
     * @Version 1.0
     */
    public class GarbageBag {
    
        String desc;
    
        public GarbageBag(String desc) {
            this.desc = desc;
        }
    
        public void setDesc(String desc) {
            this.desc = desc;
        }
    
        @Override
        public String toString() {
            return super.toString() + " " + desc;
        }
    }
    
    • 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
    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.atomic.AtomicMarkableReference;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 21:24
     * @PackageName:com.jsxs.Test
     * @ClassName: TestABAAtomicMarkableReference
     * @Description: TODO
     * @Version 1.0
     */
    @Slf4j(topic = "c.test007")
    public class TestABAAtomicMarkableReference {
        public static void main(String[] args) throws InterruptedException {
    
            GarbageBag bag = new GarbageBag("装满了垃圾");
    
            // 参数2 mark 可以看作一个标记,true表示垃圾袋满了  ⭐
            AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);
    
            log.debug("主线程 start...");
            // 1.获取余额
            GarbageBag prev = ref.getReference();
            log.debug(prev.toString());
    
            // 2.保洁阿姨打扫卫生...
            new Thread(() -> {
                log.debug("打扫卫生的线程 start...");
                bag.setDesc("空垃圾袋");
                // 3. 保洁阿姨对判断是否是空垃圾袋  (也就是新的和主存是否一致)
                while (!ref.compareAndSet(bag, bag, true, false)) {
                }
                log.debug(bag.toString());
            }).start();
    
            Thread.sleep(1000);
    
            log.debug("主线程想换一只新垃圾袋?");
            boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾袋"), true, false);
            log.debug("换了么?" + success);
            log.debug(ref.getReference().toString());
        }
    }
    
    • 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

    依然是同一个垃圾袋...

    在这里插入图片描述

    5.原子数组

    • AtomicIntegerArray
    • AtomicLongArray
    • AtomicReferenceArray

    原子数组主要保护的是我们数组中的元素!!!

    (1).不安全的实现
    package com.jsxs.Test;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.function.BiConsumer;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import java.util.function.Supplier;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/13 19:15
     * @PackageName:com.jsxs.Test
     * @ClassName: Test11
     * @Description: TODO
     * @Version 1.0
     */
    public class Test11 {
        public static void main(String[] args) {
    
            demo(
                    ()->new int[10],   // ⭐非原子数组的创建
                    (array)->array.length,
                    (array,index)->array[index]++,
                    (array)-> System.out.println(Arrays.toString(array))
            );
        }
    
        /**
         * 参数1,提供数组、可以是线程不安全数组或线程安全数组
         * 参数2,获取数组长度的方法
         * 参数3,自增方法,回传 array, index
         * 参数4,打印数组的方法
         */
    // supplier 提供者 无中生有 ()->结果
    // function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
    // consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)->
        private static <T> void demo(
    
                Supplier<T> arraySupplier,  // 1.没有参数一个结果
                Function<T, Integer> lengthFun,  // 2. 一个参数一个结果
                BiConsumer<T, Integer> putConsumer,  // 3.两个参数,一个结果的
                Consumer<T> printConsumer) {  // 4.一个参数,但没有返回值 (用作打印)
    
            List<Thread> ts = new ArrayList<>();
            // 1.获取数组
            T array = arraySupplier.get();
            // 2.设置数组的长度
            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);
        }
    }
    
    • 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

    在这里插入图片描述

    (2).安全实现_ 使用CAS
    package com.jsxs.Test;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicIntegerArray;
    import java.util.function.BiConsumer;
    import java.util.function.Consumer;
    import java.util.function.Function;
    import java.util.function.Supplier;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/13 19:15
     * @PackageName:com.jsxs.Test
     * @ClassName: Test11
     * @Description: TODO
     * @Version 1.0
     */
    public class Test11 {
        public static void main(String[] args) {
    
            demo(
                    ()->new AtomicIntegerArray(10),  // 1.创建数组 
                    (array)->array.length(),   // 2. 提供长度
                    (array,index)->array.getAndIncrement(index),  // 3.对原子数组中的元素(下标)进行 +1 的操作
                    (array)-> System.out.println(array)
            );
        }
    
        /**
         * 参数1,提供数组、可以是线程不安全数组或线程安全数组
         * 参数2,获取数组长度的方法
         * 参数3,自增方法,回传 array, index
         * 参数4,打印数组的方法
         */
    // supplier 提供者 无中生有 ()->结果
    // function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
    // consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)->
        private static <T> void demo(
    
                Supplier<T> arraySupplier,  // 1.没有参数一个结果
                Function<T, Integer> lengthFun,  // 2. 一个参数一个结果
                BiConsumer<T, Integer> putConsumer,  // 3.两个参数,一个结果的
                Consumer<T> printConsumer) {  // 4.一个参数,但没有返回值 (用作打印)
    
            List<Thread> ts = new ArrayList<>();
            // 1.获取数组
            T array = arraySupplier.get();
            // 2.设置数组的长度
            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);
        }
    }
    
    • 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

    在这里插入图片描述

    6.字段更新器

    • AtomicReferenceFieldUpdater // 域 字段
    • AtomicIntegerFieldUpdater
    • AtomicLongFieldUpdater

    字段更新器保护的是对象的属性和成员变量!!!

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

    (1).未使用voliatile修饰的时候会报异常
    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/13 20:10
     * @PackageName:com.jsxs.Test
     * @ClassName: Test12
     * @Description: TODO
     * @Version 1.0
     */
    @Slf4j(topic = "c.test12")
    public class Test12 {
        public static void main(String[] args) {
            Student student = new Student();
    
            // 1.第一个参数: 类名; 第二个参数: 参数类型; 第三个: 参数名
            AtomicReferenceFieldUpdater updater =AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
    
            // 1.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值
            updater.compareAndSet(student,null,"张三");
        }
    }
    
    class Student {
        String name;  // 我们这里没有使用 voliate
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + 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

    在这里插入图片描述

    (2).使用voliatile修饰后

    为什么要添加voliatile进行修饰呢? 为了保证我们保护的成员变量的可见性!!!

    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/13 20:10
     * @PackageName:com.jsxs.Test
     * @ClassName: Test12
     * @Description: TODO
     * @Version 1.0
     */
    @Slf4j(topic = "c.test12")
    public class Test12 {
        public static void main(String[] args) {
            Student student = new Student();
    
            // 1.第一个参数: 类名; 第二个参数: 参数类型; 第三个: 参数名
            AtomicReferenceFieldUpdater updater =AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
    
            // 1.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值
            updater.compareAndSet(student,null,"张三");
    
            System.out.println(student.toString());
        }
    }
    
    class Student {
        volatile String name;
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + 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

    在这里插入图片描述

    (3).存在问题ABA问题
    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/13 20:10
     * @PackageName:com.jsxs.Test
     * @ClassName: Test12
     * @Description: TODO
     * @Version 1.0
     */
    @Slf4j(topic = "c.test12")
    public class Test12 {
        public static void main(String[] args) {
            Student student = new Student();
    
            // 1.第一个参数: 类名; 第二个参数: 参数类型; 第三个: 参数名  ⭐
            AtomicReferenceFieldUpdater updater =AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name");
    
            // 2.第一个参数: 对象名; 第二个参数:属性的原始值; 第三个参数: 修改成什么值 ⭐⭐
            updater.compareAndSet(student,null,"张三");
            System.out.println(student.toString());
    
            // 3.原始值指的正确,那么会成功。⭐⭐⭐
    
            updater.compareAndSet(student,"张三","张四");
            System.out.println(student.toString());
    
            // 4.原始值指的更改过的,那么会失败。 底层原理还是CAS进行比较的 ⭐⭐⭐⭐
            updater.compareAndSet(student,"张三","张五");
            System.out.println(student.toString());
    
        }
    }
    
    class Student {
        volatile String name;
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + 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
    • 46
    • 47
    • 48

    在这里插入图片描述

    7.原子累加器

    累加器性能比较

    (1).运用累加器类性能更高
    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicLong;
    import java.util.concurrent.atomic.LongAdder;
    import java.util.function.Consumer;
    import java.util.function.Supplier;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/13 20:33
     * @PackageName:com.jsxs.Test
     * @ClassName: Test13
     * @Description: TODO
     * @Version 1.0
     */
    @Slf4j(topic = "c.test13")
    public class Test13 {
        public static void main(String[] args) {
    
            demo(
                    ()->new AtomicLong(0),   // 1.设置我们的原子长整型
                    (adder)->adder.getAndIncrement()  //2.进行自增的操作
            );
    
            System.out.println("======上面是原子长整型类,下面是长整型累加器=======");
    
            demo(
                    ()->new LongAdder(),
                    (adder)->adder.increment()
            );
    
        }
    
    
        /**
         *
         * @param adderSupplier  : ()->结果 ,提供累加器对象
         * @param action :  (参数)-> 无返回结果,  提供累加操作
         * @param 
         */
        private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
            T adder = adderSupplier.get();
            long start = System.nanoTime();
            List<Thread> ts = new ArrayList<>();
            // 4 个线程,每人累加 50 万
            for (int i = 0; i < 40; i++) {
                ts.add(new Thread(() -> {
                    for (int j = 0; j < 500000; j++) {
                        action.accept(adder);
                    }
                }));
            }
            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);
        }
    }
    
    • 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

    在这里插入图片描述

    (2).为什么性能高呢?

    性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加Cell[1]… 最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此减少了 CAS 重试失败,从而提高性能。相当于不共享一个累加单元,每一个线程都有一个自己的累加单元!!!

    (4).源码之 LongAdder

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

    LongAdder 类有几个关键域

            // 累加单元数组, 懒惰初始化
            transient volatile Cell[] cells;
            // 基础值, 如果没有竞争, 则用 cas 累加这个域
            transient volatile long base;
            // 在 cells 创建或扩容时, 置为 1, 表示加锁
            transient volatile int cellsBusy;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    (5).cas 锁源码

    CAS锁的源码实践,但是实际开发中我们不用用下面这样的代码进行开发

    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/15 11:45
     * @PackageName:com.jsxs.Test
     * @ClassName: LockCas
     * @Description: TODO
     * @Version 1.0
     */
    @Slf4j(topic = "c.test")
    public class LockCas {
    
        // 0的时候表示没有加锁,1的时候表示已经加锁
        private AtomicInteger state = new AtomicInteger(0);
    
        public void lock() {
            while (true) {
                if (state.compareAndSet(0, 1)) {
                    break;
                }
            }
        }
    
        public void unlock() {
            log.debug("unlock...");
            state.set(0);
        }
    
        public static void main(String[] args) {
            LockCas lock = new LockCas();
            // 1.线程1执行完毕解锁之后,线程2才能继续执行
            new Thread(() -> {
                log.debug("begin...");
                lock.lock();
                try {
                    log.debug("lock...");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } finally {
                    lock.unlock();
                }
            }).start();
    
            new Thread(() -> {
                log.debug("begin...");
                lock.lock();
                try {
                    log.debug("lock...");
                } finally {
                    lock.unlock();
                }
            }).start();
        }
    }
    
    • 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
    (6).原理之伪共享

    其中 Cell 即为累加单元

    @sun.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);
        }
        // 省略不重要代码
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    得从缓存说起,缓存与内存的速度比较

    在这里插入图片描述
    为什么设置那么多的缓存呢?
    在这里插入图片描述

    因为 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 将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效。

    在这里插入图片描述

    (7).add源码
        public void add(long x) {
            // as 为累加单元数组
            // b 为基础值
            // x 为累加值
            Cell[] as;
            long b, v;
            int m;
            Cell a;
            // 进入 if 的两个条件
            // 1. as 有值, 表示已经发生过竞争, 进入 if
            // 2. cas 给 base 累加时失败了, 表示 base 发生了竞争, 进入 if
            if ((as = cells) != null || !casBase(b = base, b + x)) {
                // uncontended 表示 cell 没有竞争
                boolean uncontended = true;
                if (
                    // as 还没有创建
                        as == null || (m = as.length - 1) < 0 ||
                                // 当前线程对应的 cell 还没有
                                (a = as[getProbe() & m]) == null ||
                                // cas 给当前线程的 cell 累加失败 uncontended=false ( a 为当前线程的 cell )
                                !(uncontended = a.cas(v = a.value, v + x))
                ) {
                    // 进入 cell 数组创建、cell 创建的流程
                    longAccumulate(x, null, uncontended);
                }
            }
        }
    
    • 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

    在这里插入图片描述

    8.UnSafe

    (1).概述

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

    在这里插入图片描述

    (2).获取UnSafe实列
    package com.jsxs.Test;
    
    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/18 21:00
     * @PackageName:com.jsxs.Test
     * @ClassName: Test14
     * @Description: TODO
     * @Version 1.0
     */
    public class Test14 {
        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            // 1.通过反射指定属性名获取具体属性
            Field unsafe = Unsafe.class.getDeclaredField("theUnsafe");
            // 2.因为是私有的成员变量,所以我们需要先设置Accessible
            unsafe.setAccessible(true);
            // 3.返回我们的真正的UnSafe对象
            Unsafe o = (Unsafe)unsafe.get(null);
            System.out.println(o);
        }
    }
    
    • 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

    在这里插入图片描述

    (3).UnSafe的Case操作
    package com.jsxs.Test;
    
    import lombok.Data;
    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/18 21:00
     * @PackageName:com.jsxs.Test
     * @ClassName: Test14
     * @Description: TODO
     * @Version 1.0
     */
    public class Test14 {
        public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
            // 1.通过反射指定属性名获取具体属性
            Field o = Unsafe.class.getDeclaredField("theUnsafe");
            // 2.因为是私有的成员变量,所以我们需要先设置Accessible
            o.setAccessible(true);
            // 3.返回我们的真正的UnSafe对象
            Unsafe unsafe = (Unsafe)o.get(null);
            System.out.println(o);
    
            // 1.获取欲的偏移地址 :  获取Student1中id属性的偏移地址
            long idOffset = unsafe.objectFieldOffset(Student1.class.getDeclaredField("id"));
            long nameOffset = unsafe.objectFieldOffset(Student1.class.getDeclaredField("name"));
    
            Student1 student1 = new Student1();
    
            // 2.执行 cas 操作
            unsafe.compareAndSwapInt(student1,idOffset,0,1);  // 第一个: 对象, 第二个: id的偏移地址,第三个: 旧值,第四个: 新值
            unsafe.compareAndSwapObject(student1,nameOffset,null,"张三");  // 第一个: 对象, 第二个: id的偏移地址,第三个: 旧值,第四个: 新值
            
            // 3.查看我们是否成功!! 
            System.out.println(student1.id+" "+ student1.name);
    
        }
    }
    
    @Data
    class Student1 {
        volatile int id;
        volatile String 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
    • 46

    在这里插入图片描述

    (4).模拟原子整数

    使用自定义的 AtomicData 实现之前线程安全的原子整数 Account 实现。

    1.Account接口

    package com.jsxs.Test;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/11 14:58
     * @PackageName:com.jsxs.Test
     * @ClassName: Account
     * @Description: TODO
     * @Version 1.0
     */
    public interface Account {
        // 获取余额
        Integer getBalance();
    
        // 取款
        void withdraw(Integer amount);
    
        /**
         * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
         * 如果初始余额为 10000 那么正确的结果应当是 0
         */
        static void demo(Account account) {
    
            List<Thread> ts = new ArrayList<>();
    
            long start = System.nanoTime();
            for (int i = 0; i < 1000; i++) {
                ts.add(new Thread(() -> {
                    // 每次取10块钱
                    account.withdraw(10);
                }));
            }
            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");
        }
    }
    
    
    • 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

    2.实现

    package com.jsxs.Test;
    
    import com.jsxs.utils.UnSafeUtils;
    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/18 21:32
     * @PackageName:com.jsxs.Test
     * @ClassName: Test15
     * @Description: TODO
     * @Version 1.0
     */
    public class Test15 {
        public static void main(String[] args) {
            Account.demo(new MyAtomicInteger(10000));
        }
    }
    
    
    class MyAtomicInteger implements Account{
    
        volatile int value;
         static final long valueOffset;
        static Unsafe unsafe;
    
    
        static {
            try {
                // 1.通过反射机制进行获取UnSafe ⭐
                Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
                theUnsafe.setAccessible(true);
                unsafe = (Unsafe) theUnsafe.get(null);
    
                // 2.获取我们偏移域
                valueOffset=unsafe.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
    
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new Error(e);
            }
        }
    
        public int getValue() {
            return value;
        }
    
        public void decrement(int amount){
            while (true){
                // 1.旧值
                int prev = this.value;
                // 2.新值
                int next=prev-amount;
                // 3.利用 UnSafe 进行设置  ⭐⭐
                if (unsafe.compareAndSwapInt(this,valueOffset,prev,next)){
                    break;
                }
    
            }
        }
    
        @Override
        public Integer getBalance() {
            return getValue();
        }
    
        public MyAtomicInteger(int value) {
            this.value = value;
        }
    
        @Override
        public void withdraw(Integer amount) {
            decrement(amount);
        }
    
    }
    
    • 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

    测试成功
    在这里插入图片描述

    (七)、共享模型之不可变

    • 不可变类的使用
    • 不可变类设计
    • 无状态类设计

    1.日期转换的问题

    (1).问题提出

    下面的代码在运行时,由于 SimpleDateFormat 不是线程安全的。

    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.text.SimpleDateFormat;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/19 20:17
     * @PackageName:com.jsxs.Test
     * @ClassName: Test16
     * @Description: TODO
     * @Version 1.0
     */
    
    @Slf4j(topic = "test16")
    public class Test16 {
        public static void main(String[] args) {
            // 1.进行我们的序列化操作
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            // 2.开启十个线程进行解析
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    try {
                        log.debug("{}", sdf.parse("1951-04-21"));
                    } catch (Exception e) {
                        log.error("{}", e);
                    }
                }).start();
            }
        }
    }
    
    • 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

    有很大几率出现 java.lang.NumberFormatException (数字类型错误) 或者出现不正确的日期解析结果,例如:

    在这里插入图片描述

    (2).思路 - 同步锁

    这样虽能解决问题,但带来的是性能上的损失,并不算很好:

    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.text.SimpleDateFormat;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/19 20:17
     * @PackageName:com.jsxs.Test
     * @ClassName: Test16
     * @Description: TODO
     * @Version 1.0
     */
    
    @Slf4j(topic = "c.test16")
    public class Test16 {
        public static void main(String[] args) {
            // 1.进行我们的序列化操作
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            // 2.开启十个线程进行解析
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    synchronized (sdf) {  // ⭐
                        try {
                            log.debug("{}", sdf.parse("1951-04-21"));
                        } catch (Exception e) {
                            log.error("{}", e);
                        }
                    }
                }).start();
            }
        }
    }
    
    • 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

    在这里插入图片描述

    (3).思路 - 不可变

    如果一个对象在不能够修改其内部状态(属性),那么它就是线程安全的,因为不存在并发修改啊!这样的对象在Java 中有很多,例如在 Java 8 后,提供了一个新的日期格式化类:

    package com.jsxs.Test;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.text.SimpleDateFormat;
    import java.time.LocalDate;
    import java.time.format.DateTimeFormatter;
    
    /**
     * @Author Jsxs
     * @Date 2023/10/19 20:17
     * @PackageName:com.jsxs.Test
     * @ClassName: Test16
     * @Description: TODO
     * @Version 1.0
     */
    
    @Slf4j(topic = "c.test16")
    public class Test16 {
        public static void main(String[] args) {
    
            // 1. 利用 线程安全的方法
            DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    LocalDate date = dtf.parse("2018-10-01", LocalDate::from);
                    log.debug("{}", date);
                }).start();
            }
        }
    }
    
    • 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

    在这里插入图片描述

    2.不可变设计

    另一个大家更为熟悉的 String 类也是不可变的,以它为例,说明一下不可变设计的要素

    public final class String
            implements java.io.Serializable, Comparable<String>, CharSequence {
        /**
         * The value is used for character storage.
         */
        private final char value[];
        /**
         * Cache the hash code for the string
         */
        private int hash; // Default to 0
    
        // ...
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    (1).final 的使用

    发现该类、类中所有属性都是 final

    • 属性用 final 修饰保证了该属性是只读的,不能修改
    • 类用 final 修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性
    (2).保护性拷贝

    但有同学会说,使用字符串时,也有一些跟修改相关的方法啊,比如 substring 等,那么下面就看一看这些方法是如何实现的,就以 substring 为例:

    深拷贝!!!: 实质上也就是创建了一个新的类。

        public String substring(int beginIndex) {
            if (beginIndex < 0) {
                throw new StringIndexOutOfBoundsException(beginIndex);
            }
            int subLen = value.length - beginIndex;
            if (subLen < 0) {
                throw new StringIndexOutOfBoundsException(subLen);
            }
            return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    发现其内部是调用 String 的构造方法创建了一个新字符串,再进入这个构造看看,是否对 final char[] value 做出了修改:

        public String(char value[], int offset, int count) {
        
            if (offset < 0) {
                throw new StringIndexOutOfBoundsException(offset);
            }
            
            if (count <= 0) {
            
                if (count < 0) {
                    throw new StringIndexOutOfBoundsException(count);
                }
                if (offset <= value.length) {
                    this.value = "".value;
                    return;
                }
                
            }
            
            if (offset > value.length - count) {
                throw new StringIndexOutOfBoundsException(offset + count);
            }
            this.value = Arrays.copyOfRange(value, offset, offset + count);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    结果发现也没有,构造新字符串对象时,会生成新的 char[] value,对内容进行复制 。这种通过创建副本对象来避免共享的手段称之为【保护性拷贝(defensive copy)】

    3.享元模式

    (1). 简介

    定义 英文名称:Flyweight pattern. 当需要重用数量有限的同一类对象时。

    (2).体现
    1. 包装类

    在JDK中 BooleanByteShortIntegerLongCharacter 等包装类提供了 valueOf 方法,例如 Long 的 valueOf 会缓存 -128~127 之间的 Long 对象,在这个范围之间会重用对象,大于这个范围,才会新建 Long 对象

        public static Long valueOf(long l) {
        
            final int offset = 128;
            if (l >= -128 && l <= 127) { // will cache
                return LongCache.cache[(int) l + offset];
            }
            return new Long(l);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • Byte, Short, Long 缓存的范围都是 -128~127
    • Character 缓存的范围是 0~127
    • Integer的默认范围是 -128~127
      • 最小值不能变
      • 但最大值可以通过调整虚拟机参数 -Djava.lang.Integer.IntegerCache.high 来改变
    • Boolean 缓存了 TRUE 和 FALSE
    (3).DIY

    例如:一个线上商城应用,QPS 达到数千,如果每次都重新创建和关闭数据库连接,性能会受到极大影响。 这时预先创建好一批连接,放入连接池。一次请求到达后,从连接池获取连接,使用完毕后再还回连接池,这样既节约了连接的创建和关闭时间,也实现了连接的重用,不至于让庞大的连接数压垮数据库

    class Pool {
        // 1. 连接池大小
        private final int poolSize;
        // 2. 连接对象数组
        private Connection[] connections;
        // 3. 连接状态数组 0 表示空闲, 1 表示繁忙
        private AtomicIntegerArray states;
    
        // 4. 构造方法初始化
        public Pool(int poolSize) {
            this.poolSize = poolSize;
            this.connections = new Connection[poolSize];
            this.states = new AtomicIntegerArray(new int[poolSize]);
            for (int i = 0; i < poolSize; i++) {
                connections[i] = new MockConnection("连接" + (i + 1));
            }
        }
    
        // 5. 借连接
        public Connection borrow() {
    
            while (true) {
                for (int i = 0; i < poolSize; i++) {
                    // 获取空闲连接
                    if (states.get(i) == 0) {
                        if (states.compareAndSet(i, 0, 1)) {
                            log.debug("borrow {}", connections[i]);
                            return connections[i];
                        }
                    }
                }
                // 如果没有空闲连接,当前线程进入等待
                synchronized (this) {
                    try {
                        log.debug("wait...");
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        // 6. 归还连接
        public void free(Connection conn) {
            for (int i = 0; i < poolSize; i++) {
                if (connections[i] == conn) {
                    states.set(i, 0);
                    synchronized (this) {
                        log.debug("free {}", conn);
                        this.notifyAll();
                    }
                    break;
                }
            }
        }
    
    }
    
    class MockConnection implements Connection {
        // 实现略
    }
    
    • 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

    4.finnal原理

    (1).设置 final 变量的原理

    理解了 volatile 原理,再对比 final 的实现就比较简单了

    public class TestFinal {
     	final int a = 20;
    }
    
    • 1
    • 2
    • 3

    字节码

    0: aload_0
    1: invokespecial #1 // Method java/lang/Object."":()V
    4: aload_0
    5: bipush 20
    7: putfield #2 // Field a:I
     <-- 写屏障
    10: return
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    发现 final 变量的赋值也会通过 putfield 指令来完成,同样在这条指令之后也会加入写屏障,保证在其它线程读到它的值时不会出现为 0 的情况。 因为finnal不能进行修改的特点。

    (2).获取final 变量的原理

    Monitor 原理

    5.无状态

    在 web 阶段学习时,设计 Controller 时为了保证其线程安全,都会有这样的建议,不要为 Controller 设置成员变量,这种没有任何成员变量的类是线程安全的

    因为成员变量保存的数据也可以称为状态信息,因此没有成员变量就称之为【无状态】

  • 相关阅读:
    JAVA IO——BufferedOutputStream,缓冲流写文件内容
    Spring——Spring核心(IoC)控制反转详细说明
    觉哥java网站踩坑+bug记录(困扰一分钟以上的问题全记录)
    在nodejs中实现实时通信的几种方式
    Stable Diffusion科普文章【附升级gpt4.0秘笈】
    springmvc、springBoot---第三篇
    ShinyProxy学习整理记录
    富文本编辑器添加图片
    Kubernetes:kube-apiserver 之启动流程(二)
    Docker常用命令
  • 原文地址:https://blog.csdn.net/qq_69683957/article/details/133770457