并发编程(第六章 共享模型之无锁) 小咪咪 2024-04-08 10:04 124阅读 0赞 #### 文章标题 #### * * 一、问题提出 * 二、CAS 与 volatile * 三、原子整数 * 四、原子引用 * 五、原子数组 * 六、字段更新器 * 七、原子累加器 * 八、Unsafe * 本章小结 > 本章内容 > > * CAS与volatile > * 原子整数 > * 原子引用 > * 原子累加器 > * Unsafe ### 一、问题提出 ### > 有如下需求,保证`accout.withdraw`取款方法的线程安全 interface Account { // 获取余额 Integer getBalance(); // 取款 void withdraw(Integer amount); /** * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作 * 如果初始余额为 10000 那么正确的结果应当是 0 */ static void demo(Account account) { List<Thread> ts = new ArrayList<>(); for (int i = 0; i < 1000; i++) { ts.add(new Thread(() -> { account.withdraw(10); })); } long start = System.nanoTime(); ts.forEach(Thread::start); ts.forEach(t -> { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); long end = System.nanoTime(); System.out.println(account.getBalance() + " cost: " + (end-start)/1000_000 + " ms"); } } > 原有实现并不是线程安全的 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)); } > 某次的执行结果: > ![在这里插入图片描述][392640527a7643cc8ebf97dd6baede6f.png] **1、为什么不安全** > `withdraw`方法 public void withdraw(Integer amount) { balance -= amount; } > 对应的字节码: > ![在这里插入图片描述][691a539ef7a042088beea8a01aba7412.png] > 多线程执行流程 > ![在这里插入图片描述][0e8694d9602548f7aa75f27fc3a34856.png] > ![在这里插入图片描述][4fc5edc16220488a9db3f9eef046e8c5.png] > > * 单核的指令交错 > * 多核的指令交错 **2、解决思路 - 锁** > 首先想到的是给Account对象加锁 class AccountUnsafe implements Account { private Integer balance; public AccountUnsafe(Integer balance) { this.balance = balance; } @Override public Integer getBalance() { synchronized (this) { return this.balance; } } @Override public void withdraw(Integer amount) { synchronized (this) { this.balance -= amount; } } } > 结果为: > ![在这里插入图片描述][9e49461631c9438bbf667b389e8ae0d7.png] **3、解决思路 - 无锁** class AccountSafe implements Account { private AtomicInteger balance; public AccountSafe(Integer 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; if (balance.compareAndSet(prev, next)) { break; } } // 可以简化为下面的方法 // balance.addAndGet(-1 * amount); } } > 执行测试代码: public static void main(String[] args) { Account.demo(new AccountSafe(10000)); } > 某次执行的结果: > ![在这里插入图片描述][9e76a3f519d147fba390e96cbfda10cd.png] ### 二、CAS 与 volatile ### > 前面看到的`AtomicInteger`的解决方法,内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢? public void withdraw(Integer amount) { while(true) { // 需要不断尝试,直到成功为止 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; } } } } > 其中的关键是compareAndSet,它的简称就是CAS(也有Compare And Swap的说法),它必须是原子操作。 ![在这里插入图片描述][5fcae7c5598e41fc9054cbc231380b61.png] > 注意: > 其实CAS的底层是`lock cmpxchg`指令(X86架构),在单核CPU和多核CPU下都能够保证【比较-交换】的原子性 **1、慢动作分析** @Slf4j public class SlowMotion { public static void main(String[] args) { AtomicInteger balance = new AtomicInteger(10000); int mainPrev = balance.get(); log.debug("try get {}", mainPrev); new Thread(() -> { sleep(1000); int prev = balance.get(); balance.compareAndSet(prev, 9000); log.debug(balance.toString()); }, "t1").start(); sleep(2000); log.debug("try set 8000..."); boolean isSuccess = balance.compareAndSet(mainPrev, 8000); log.debug("is success ? {}", isSuccess); if(!isSuccess){ mainPrev = balance.get(); log.debug("try set 8000..."); isSuccess = balance.compareAndSet(mainPrev, 8000); log.debug("is success ? {}", isSuccess); } } private static void sleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { e.printStackTrace(); } } } > 输出结果 > ![在这里插入图片描述][009567128dff442b98bda74c2439f4c1.png] **2、volatile** > 1、获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。 > 2、它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存。即一个线程对volatile变量的修改,对另一个线程可见。 > > * 注意: > volatile仅仅保证了共享变量的可见性,让其它线程能够看到最新值,但不能解决指令交错问题(不能保证原子性) > > 3、CAS必须借助volatile才能读取到共享变量的最新值来实现【比较并交换】的效果。 **3、为什么无锁效率高** > * 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而synchronized会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。打个比喻; > * 线程就好像高速跑道上的赛车,高速运行时,速度超快,一旦发生上下文切换,就好比赛车要减速、熄火,等被唤醒又得重新打火、启动、加速…恢复到高速运行,代价比较大; > * 但无锁情况下,因为线程要保持运行,需要额外CPU的支持,CPU在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还是会导致上下文切换。 ![在这里插入图片描述][0c572dab30f746cf98f287d7c1226508.png] **4、CAS的特点** > 结合CAS和volatile可以实现无锁并发,适用于线程数少、多核CPU的场景下。 > > * CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我吃亏点再重试呗; > * synchronized是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上来锁你们都别想改,我改完了解开锁,你们才有机会; > * CAS体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思: > – 因为没有使用synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一; > – 但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响。 ### 三、原子整数 ### > J.U.C 并发包提供了: > > * 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)); ### 四、原子引用 ### > 为什么需要原子引用类型? > > * AtomicReference > * AtomicMarkableReference > * AtomicStampedReference > 有如下方法: 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()); } } > 试着提供不同的DecimalAccount实现,实现安全的取款操作 **1、不安全实现** 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); } } **2、安全实现 - 使用锁** 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); } } } **3、安全实现 - 使用CAS** class DecimalAccountSafeCas implements DecimalAccount { AtomicReference<BigDecimal> ref; public DecimalAccountSafeCas(BigDecimal balance) { ref = new AtomicReference<>(balance); } @Override public BigDecimal getBalance() { return ref.get(); } @Override public void withdraw(BigDecimal amount) { while (true) { BigDecimal prev = ref.get(); BigDecimal next = prev.subtract(amount); if (ref.compareAndSet(prev, next)) { break; } } } } > 测试代码 DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal("10000"))); DecimalAccount.demo(new DecimalAccountSafeLock(new BigDecimal("10000"))); DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal("10000"))); > 运行结果 > ![在这里插入图片描述][4ff178b52b784d40a6804bfaa82a303d.png] **4、ABA问题及解决** **4.1ABA问题** 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(); sleep(1); // 尝试改为 C log.debug("change A->C {}", ref.compareAndSet(prev, "C")); } private static void other() { new Thread(() -> { log.debug("change A->B {}", ref.compareAndSet(ref.get(), "B")); }, "t1").start(); sleep(0.5); new Thread(() -> { log.debug("change B->A {}", ref.compareAndSet(ref.get(), "A")); }, "t2").start(); } > 输出 > ![在这里插入图片描述][2d38a177c55343bea8b7f888ffc7aadf.png] > * 主线程仅能判断出共享变量的值与最初值A是否相同,不能感知到这种从A改为B又改回A的情况,如果主线程希望: > * 只要有其它线程【动过了】共享变量,那么自己的cas就算失败,这时,仅比较值是不够的,需要再加一个版本号 AtomicStampedReference 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(); sleep(1); // 尝试改为 C log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1)); } private static void other() { new Thread(() -> { log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B", ref.getStamp(), ref.getStamp() + 1)); log.debug("更新版本为 {}", ref.getStamp()); }, "t1").start(); sleep(0.5); new Thread(() -> { log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A", ref.getStamp(), ref.getStamp() + 1)); log.debug("更新版本为 {}", ref.getStamp()); }, "t2").start(); } > 输出为: > ![在这里插入图片描述][34c7dd2c5b484edaab23990be5280fa5.png] > * AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: `A -> B -> A -> C` ,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。 > * 但是有时候,并不关心引用变量更改了几次,只是单纯的关心`是否更改过`,所以就有了 > AtomicMarkableReference > ![在这里插入图片描述][8398c24fc9bf4d089f9d559fffc9c017.png] AtomicMarkableReference 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; } } @Slf4j public class TestABAAtomicMarkableReference { public static void main(String[] args) throws InterruptedException { GarbageBag bag = new GarbageBag("装满了垃圾"); // 参数2 mark 可以看作一个标记,表示垃圾袋满了 AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true); log.debug("主线程 start..."); GarbageBag prev = ref.getReference(); log.debug(prev.toString()); new Thread(() -> { log.debug("打扫卫生的线程 start..."); bag.setDesc("空垃圾袋"); 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()); } } > 输出 > ![在这里插入图片描述][7b41c930c733448a857c5c8fac00a067.png] ### 五、原子数组 ### > * AtomicIntegerArray > * AtomicLongArray > * AtomicReferenceArray 有如下方法 /** 参数1,提供数组、可以是线程不安全数组或线程安全数组 参数2,获取数组长度的方法 参数3,自增方法,回传 array, index 参数4,打印数组的方法 */ // supplier 提供者 无中生有 ()->结果 // function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果 // consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)-> 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); } **1、不安全的数组** demo( ()->new int[10], (array)->array.length, (array, index) -> array[index]++, array-> System.out.println(Arrays.toString(array)) ); > 结果: > ![在这里插入图片描述][516213d171db4171a6124a3f5d7d81f3.png] **2、安全的数组** demo( ()-> new AtomicIntegerArray(10), (array) -> array.length(), (array, index) -> array.getAndIncrement(index), array -> System.out.println(array) ); > 结果: > ![在这里插入图片描述][eeee0988fc0748f8bc81869c13846ae5.png] ### 六、字段更新器 ### > * AtomicReferenceFieldUpdater //域 字段 > * AtomicIntegerFieldUpdater > * AtomicLongFieldUpdater > 利用字段更新器,可以针对对象的某个域(Field)进行原子操作,只能配合 volatile 修饰的字段使用,否则会出现 异常 > ![在这里插入图片描述][8f4e881f42e3457d8fccbb98efbc00f0.png] public class Test5 { private volatile int field; public static void main(String[] args) { AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Test5.class, "field"); Test5 test5 = new Test5(); fieldUpdater.compareAndSet(test5, 0, 10); // 修改成功 field = 10 System.out.println(test5.field); // 修改成功 field = 20 fieldUpdater.compareAndSet(test5, 10, 20); System.out.println(test5.field); // 修改失败 field = 20 fieldUpdater.compareAndSet(test5, 10, 30); System.out.println(test5.field); } } > 输出: > ![在这里插入图片描述][d094f21489194cad8f094aaa9e35f141.png] ### 七、原子累加器 ### **1、累加器性能比较** 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); } > 比较AtomicLong与LongAdder for (int i = 0; i < 5; i++) { demo(() -> new LongAdder(), adder -> adder.increment()); } for (int i = 0; i < 5; i++) { demo(() -> new AtomicLong(), adder -> adder.getAndIncrement()); } > 输出: > ![在这里插入图片描述][4ee5a88cf57e49ad9ae7c51700a8386e.png] > 性能提升的原因很简单,就是在有竞争时,设置多个累加单元,Thread-0累加Cell\[0\],而Thread-1累加Cell\[1\]…最后将结果汇总。这样它们在累加时操作的不同的Cell变量,因此减少了CAS重试失败,从而提高性能。 **2、源码之LongAdder** > 1、LongAdder是并发大师@author Doug Lea(大哥李)的作品,设计的非常精巧 > 2、LongAdder类有几个关键域 // 累加单元数组, 懒惰初始化 transient volatile Cell[] cells; // 基础值, 如果没有竞争, 则用 cas 累加这个域 transient volatile long base; // 在 cells 创建或扩容时, 置为 1, 表示加锁 transient volatile int cellsBusy; > cas锁 // 不要用于实践!!! (容易造成代码出现空转现象) public class LockCas { 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); } } > 测试 LockCas lock = new LockCas(); new Thread(() -> { log.debug("begin..."); lock.lock(); try { log.debug("lock..."); sleep(1); } finally { lock.unlock(); } }).start(); new Thread(() -> { log.debug("begin..."); lock.lock(); try { log.debug("lock..."); } finally { lock.unlock(); } }).start(); > 输出: > ![在这里插入图片描述][b84a5839c9684557bbfcb91a97e9b627.png] **3、原理之伪共享** > 其中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、缓存与内存的速度比较 > ![在这里插入图片描述][c95505aa65004d938abb85d269556e93.png] > ![在这里插入图片描述][75d78fdfbc51436297a285788974d470.png] > > * 因为CPU与内存的速度差异很大,需要靠预读数据至缓存来提升效率; > * 而缓存以缓存为单位,每个缓存行对应着一块内存,一般是64byte(8个long) > * 缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中 > * CPU要保证数据的一致性,如果某个CPU核心更改了数据,其它CPU核心对应的整个缓存行必须失效 > ![在这里插入图片描述][85f5b3abd14a44ebb6c04df465615f6c.png] > * 因为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将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效。 > ![在这里插入图片描述][b655ddb6c7914a3097a529cca5c61489.png] **累加主要调用下面的方法** 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); } } } > add流程图: > ![在这里插入图片描述][1d3de4f8acdf46489d85e2f8a61f989b.png] final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) { int h; // 当前线程还没有对应的 cell, 需要随机生成一个 h 值用来将当前线程绑定到 cell if ((h = getProbe()) == 0) { // 初始化 probe ThreadLocalRandom.current(); // h 对应新的 probe 值, 用来对应 cell h = getProbe(); wasUncontended = true; } // collide 为 true 表示需要扩容 boolean collide = false; for (;;) { Cell[] as; Cell a; int n; long v; // 已经有了 cells if ((as = cells) != null && (n = as.length) > 0) { // 还没有 cell if ((a = as[(n - 1) & h]) == null) { // 为 cellsBusy 加锁, 创建 cell, cell 的初始累加值为 x // 成功则 break, 否则继续 continue 循环 } // 有竞争, 改变线程对应的 cell 来重试 cas else if (!wasUncontended) wasUncontended = true; // cas 尝试累加, fn 配合 LongAccumulator 不为 null, 配合 LongAdder 为 null else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x)))) break; // 如果 cells 长度已经超过了最大长度, 或者已经扩容, 改变线程对应的 cell 来重试 cas else if (n >= NCPU || cells != as) collide = false; // 确保 collide 为 false 进入此分支, 就不会进入下面的 else if 进行扩容了 else if (!collide) collide = true; // 加锁 else if (cellsBusy == 0 && casCellsBusy()) { // 加锁成功, 扩容 continue; } // 改变线程对应的 cell h = advanceProbe(h); } // 还没有 cells, 尝试给 cellsBusy 加锁 else if (cellsBusy == 0 && cells == as && casCellsBusy()) { // 加锁成功, 初始化 cells, 最开始长度为 2, 并填充一个 cell // 成功则 break; } // 上两种情况失败, 尝试给 base 累加 else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x)))) break; } } > longAccumulate流程图: > ![在这里插入图片描述][9a00051888164bffaa48cb021a077089.png] > ![在这里插入图片描述][cf5c73af7c57466592b723f89a8dc9df.png] > 每个线程刚进入longAccumlate时,会尝试对应一个cell对象(找到一个坑位) > ![在这里插入图片描述][bb080141d761416e9a492b32e36a7807.png] > 获取最终结果通过sum方法 public long sum() { Cell[] as = cells; Cell a; long sum = base; if (as != null) { for (int i = 0; i < as.length; ++i) { if ((a = as[i]) != null){ sum += a.value(); } } return sum; } ### 八、Unsafe ### **1、概述** > Unsafe对象提供了非常底层的,操作内存、线程的方法,Unsafe对象不能直接调用,只能通过反射获得 public class UnsafeAccessor { static 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; } } **2、Unsafe CAS操作** @Data class Student { volatile int id; volatile String name; } Unsafe unsafe = UnsafeAccessor.getUnsafe(); Field id = Student.class.getDeclaredField("id"); Field name = Student.class.getDeclaredField("name"); // 获得成员变量的偏移量 long idOffset = UnsafeAccessor.unsafe.objectFieldOffset(id); long nameOffset = UnsafeAccessor.unsafe.objectFieldOffset(name); Student student = new Student(); // 使用 cas 方法替换成员变量的值 UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0, 20); // 返回 true UnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null, "张三"); // 返回 true System.out.println(student); > 输出: > ![在这里插入图片描述][1baf60fa472f446d950acf711931aff8.png] > 使用自定义的AtomicData实现之前线程安全的原子整数Account实现 class AtomicData { private volatile int data; static final Unsafe unsafe; static final long DATA_OFFSET; static { unsafe = UnsafeAccessor.getUnsafe(); try { // data 属性在 DataContainer 对象中的偏移量,用于 Unsafe 直接访问该属性 DATA_OFFSET = unsafe.objectFieldOffset(AtomicData.class.getDeclaredField("data")); } catch (NoSuchFieldException e) { throw new Error(e); } } public AtomicData(int data) { this.data = data; } public void decrease(int amount) { int oldValue; while(true) { // 获取共享变量旧值,可以在这一行加入断点,修改 data 调试来加深理解 oldValue = data; // cas 尝试修改 data 为 旧值 + amount,如果期间旧值被别的线程改了,返回 false if (unsafe.compareAndSwapInt(this, DATA_OFFSET, oldValue, oldValue - amount)) { return; } } } public int getData() { return data; } } > Account实现 Account.demo(new Account() { AtomicData atomicData = new AtomicData(10000); @Override public Integer getBalance() { return atomicData.getData(); } @Override public void withdraw(Integer amount) { atomicData.decrease(amount); } }); ### 本章小结 ### > * CAS与volatile > * API > – 原子整数 > – 原子引用 > – 原子数组 > – 字段更新器 > – 原子累加器 > * Unsafe > * 原理方面(重要) > – LongAdder源码 > – 伪共享 [392640527a7643cc8ebf97dd6baede6f.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/6971d1ba54844f5ab920e439c8c8442c.png [691a539ef7a042088beea8a01aba7412.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/b670476f12c14f0b9118fb483199485f.png [0e8694d9602548f7aa75f27fc3a34856.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/421ac7830b8847e0a5c6d16b0e84e0c3.png [4fc5edc16220488a9db3f9eef046e8c5.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/ba7c26e0534841ec9b16ff49c3644e57.png [9e49461631c9438bbf667b389e8ae0d7.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/2ba7df9c5d9745c68025fe526bb6e238.png [9e76a3f519d147fba390e96cbfda10cd.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/7d513a35fa324268a3295a3cedc81f14.png [5fcae7c5598e41fc9054cbc231380b61.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/a1a84b9c01384015839e97dcbf1e338f.png [009567128dff442b98bda74c2439f4c1.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/7948ea5fa6dd4726bedd6cb18344bcf6.png [0c572dab30f746cf98f287d7c1226508.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/90fe6021c44b41daaa0f6a8539535155.png [4ff178b52b784d40a6804bfaa82a303d.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/4e25b7f0c33f41bd9545de5584bcce47.png [2d38a177c55343bea8b7f888ffc7aadf.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/6313134e94a4481290413277f18931bb.png [34c7dd2c5b484edaab23990be5280fa5.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/d45bf5a1055649349e00cce237d2e278.png [8398c24fc9bf4d089f9d559fffc9c017.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/9572c2d2e6cb4577a007561b870529b8.png [7b41c930c733448a857c5c8fac00a067.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/7be775d668654cc6a0b6b39e51abe065.png [516213d171db4171a6124a3f5d7d81f3.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/6c091a4035e84186b6ac4e184f63825f.png [eeee0988fc0748f8bc81869c13846ae5.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/9d8106da274244b3b086493d3e4330a1.png [8f4e881f42e3457d8fccbb98efbc00f0.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/8b5f3538938647ab94d31e24b4ae4c05.png [d094f21489194cad8f094aaa9e35f141.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/d887d84132494df78bd6979748d2a429.png [4ee5a88cf57e49ad9ae7c51700a8386e.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/4afd2695fdf942418c4c166d8feeba3f.png [b84a5839c9684557bbfcb91a97e9b627.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/1dfbdfacf4bc43adb5aa2d2d080e7890.png [c95505aa65004d938abb85d269556e93.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/84f232cc820645cca1a497d1f850772c.png [75d78fdfbc51436297a285788974d470.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/a210c118d2804f61bcb20143ab4553e3.png [85f5b3abd14a44ebb6c04df465615f6c.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/cc56ff03553041f6a6082bbc89481239.png [b655ddb6c7914a3097a529cca5c61489.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/71b11269ff604eb2ba63123211c34d8d.png [1d3de4f8acdf46489d85e2f8a61f989b.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/10ba8de87f524bc6ad0fd4879551a5a1.png [9a00051888164bffaa48cb021a077089.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/ba451807a027411c9c9601bd446142c0.png [cf5c73af7c57466592b723f89a8dc9df.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/393657502d3c4fa1828c482546ead911.png [bb080141d761416e9a492b32e36a7807.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/9692f083dbac4569ae5eae75156156d9.png [1baf60fa472f446d950acf711931aff8.png]: https://image.dandelioncloud.cn/pgy_files/images/2024/04/08/9085c758fbf844f68e639a5af16766e0.png
相关 并发编程(第八章 共享模型之工具 - 线程池) 文章目录 线程池 一、自定义线程池 二、ThreadPoolExecutor 1、线程池 女爷i/ 2024年04月08日 11:10/ 0 赞/ 98 阅读
相关 并发编程(第六章 共享模型之无锁) 文章标题 一、问题提出 二、CAS 与 volatile 三、原子整数 四、原子引用 五、原子数组 小咪咪/ 2024年04月08日 10:04/ 0 赞/ 125 阅读
相关 并发编程——3.共享模型之管程 目录 3.共享模型之管程 3.1.共享带来的问题 3.1.1.Java中的体现 3.1.2.问题分析 浅浅的花香味﹌/ 2024年03月31日 17:24/ 0 赞/ 137 阅读
相关 并发编程——7.共享模型之工具 目录 7.共享模型之工具 7.1.线程池 7.1.1.自定义线程池 7.1.2.ThreadPoolExecu 喜欢ヅ旅行/ 2023年10月07日 22:29/ 0 赞/ 80 阅读
相关 并发编程——6.共享模型之不可变 目录 6.共享模型之不可变 6.1.日期转换的问题 6.1.1.问题提出 6.1.2.解决思路——同步锁 亦凉/ 2023年10月07日 22:29/ 0 赞/ 98 阅读
相关 并发编程——5.共享模型之无锁 目录 5.共享模型之无锁 5.1.问题提出 5.1.1.案例 5.1.2.解决思路——锁 不念不忘少年蓝@/ 2023年10月07日 22:28/ 0 赞/ 27 阅读
相关 Java并发编程之 无锁(CAS) 共享模型之无锁 文章目录 共享模型之无锁 一、CAS 与 volatile 1. CAS 2. vola 深藏阁楼爱情的钟/ 2022年11月13日 11:20/ 0 赞/ 353 阅读
相关 【五】多线程 —— 共享模型之无锁 Java中 `synchronized` 和 `ReentrantLock` 等 `独占锁` 就是 `悲观锁` 思想的实现。管程即`monitor`是阻塞式的`悲观锁`实现并发 冷不防/ 2022年09月02日 11:37/ 0 赞/ 219 阅读
相关 Java7并发编程指南——第六章:并发集合 Java7并发编程指南——第六章:并发集合 @(并发和IO流) Java7并发编程指南第六章并发集合 思维导图 项目代码 小鱼儿/ 2022年06月14日 05:43/ 0 赞/ 249 阅读
还没有评论,来说两句吧...