原子类相干

打印 上一主题 下一主题

主题 868|帖子 868|积分 2604

原子引用

JUC 并发包提供了:


  • AtomicReference
  • AtomicMarkableReference
  • AtomicStampedReference
AtomicReference 使用举例

  1. public interface DecimalAccount {
  2.     // 获取余额
  3.     BigDecimal getBalance();
  4.     // 取款
  5.     void withdraw(BigDecimal amount);
  6.     /**
  7.     * 方法内会启动 1000 个线程,每个线程做 -10 元 的操作
  8.     * 如果初始余额为 10000 那么正确的结果应当是 0
  9.     */
  10.     static void demo(DecimalAccount account) {
  11.         List<Thread> ts = new ArrayList<>();
  12.         for (int i = 0; i < 1000; i++) {
  13.             ts.add(new Thread(() -> {
  14.                 account.withdraw(BigDecimal.TEN);
  15.             }));
  16.         }
  17.         ts.forEach(Thread::start);
  18.         ts.forEach(t -> {
  19.             try {
  20.                 t.join();
  21.             } catch (InterruptedException e) {
  22.                 e.printStackTrace();
  23.             }
  24.         });
  25.         System.out.println(account.getBalance());
  26.     }
  27. }
复制代码
怎样提供 DecimalAccount 实现,实现安全的取款操作
不安全实现

  1. class DecimalAccountUnsafe implements DecimalAccount {
  2.     BigDecimal balance;
  3.    
  4.     public DecimalAccountUnsafe(BigDecimal balance) {
  5.         this.balance = balance;
  6.     }
  7.     @Override
  8.     public BigDecimal getBalance() {
  9.         return balance;
  10.     }
  11.     @Override
  12.     public void withdraw(BigDecimal amount) {
  13.         BigDecimal balance = this.getBalance();
  14.         this.balance = balance.subtract(amount);
  15.     }
  16. }
复制代码
安全实现-使用synchronized

  1. class DecimalAccountSafeLock implements DecimalAccount {
  2.     private final Object lock = new Object();
  3.     BigDecimal balance;
  4.    
  5.     public DecimalAccountSafeLock(BigDecimal balance) {
  6.         this.balance = balance;
  7.     }
  8.     @Override
  9.     public BigDecimal getBalance() {
  10.         return balance;
  11.     }
  12.     @Override
  13.     public void withdraw(BigDecimal amount) {
  14.         synchronized (lock) {
  15.             BigDecimal balance = this.getBalance();
  16.             this.balance = balance.subtract(amount);
  17.         }
  18.     }
  19. }
复制代码
安全实现-使用CAS

  1. class DecimalAccountSafeCas implements DecimalAccount {
  2.    
  3.     AtomicReference<BigDecimal> ref;
  4.    
  5.     public DecimalAccountSafeCas(BigDecimal balance) {
  6.         ref = new AtomicReference<>(balance);
  7.     }
  8.    
  9.     @Override
  10.     public BigDecimal getBalance() {
  11.         return ref.get();
  12.     }
  13.     @Override
  14.     public void withdraw(BigDecimal amount) {
  15.         while (true) {
  16.             BigDecimal prev = ref.get();
  17.             BigDecimal next = prev.subtract(amount);
  18.             if (ref.compareAndSet(prev, next)) {
  19.                 break;
  20.             }
  21.         }
  22.     }
  23. }
复制代码
测试代码
  1. DecimalAccount.demo(new DecimalAccountUnsafe(new BigDecimal("10000")));
  2. DecimalAccount.demo(new DecimalAccountSafeLock(new BigDecimal("10000")));
  3. DecimalAccount.demo(new DecimalAccountSafeCas(new BigDecimal("10000")));
复制代码
运行结果
  1. 4310 cost: 425 ms
  2. 0 cost: 285 ms
  3. 0 cost: 274 ms
复制代码
AtomicStampedReference 使用举例

使用 AtomicReference 时线程仅能判定出共享变量的值与最初值 A 是否类似,不能感知到从 A 改为 B 又改回 A 的情况,假如主线程 希望: 只要有其它线程【动过了】共享变量,那么自己的 cas 就算失败,这时,仅比力值是不够的,须要再加一个版本号,使用 AtomicStampedReference 可以实现
  1. static AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
  2. public static void main(String[] args) throws InterruptedException {
  3.     log.debug("main start...");
  4.     // 获取值 A
  5.     String prev = ref.getReference();
  6.     // 获取版本号
  7.     int stamp = ref.getStamp();
  8.     log.debug("版本 {}", stamp);
  9.     // 如果中间有其它线程干扰,发生了 ABA 现象
  10.     other();
  11.     sleep(1);
  12.     // 尝试改为 C
  13.     log.debug("change A->C {}", ref.compareAndSet(prev, "C", stamp, stamp + 1));
  14. }
  15. private static void other() {
  16.     new Thread(() -> {
  17.         log.debug("change A->B {}", ref.compareAndSet(ref.getReference(), "B",
  18.                                                       ref.getStamp(), ref.getStamp() + 1));
  19.         log.debug("更新版本为 {}", ref.getStamp());
  20.     }, "t1").start();
  21.     sleep(0.5);
  22.     new Thread(() -> {
  23.         log.debug("change B->A {}", ref.compareAndSet(ref.getReference(), "A",
  24.                                                       ref.getStamp(), ref.getStamp() + 1));
  25.         log.debug("更新版本为 {}", ref.getStamp());
  26.     }, "t2").start();
  27. }
复制代码
输出如下,最后因为版本号不同等没有修改成功
  1. 15:21:34.891 c.Test36 [main] - main start...
  2. 15:21:34.894 c.Test36 [main] - 版本 0
  3. 15:21:34.956 c.Test36 [t1] - change A->B true
  4. 15:21:34.956 c.Test36 [t1] - 更新版本为 1
  5. 15:21:35.457 c.Test36 [t2] - change B->A true
  6. 15:21:35.457 c.Test36 [t2] - 更新版本为 2
  7. 15:21:36.457 c.Test36 [main] - change A->C false
复制代码
AtomicMarkableReference 使用举例

AtomicStampedReference 可以给原子引用加上版本号,追踪原子引用整个的变化过程,如: A -> B -> A -> C ,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了几次。但是有时间,并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了 AtomicMarkableReference。
  1. class GarbageBag {
  2.     String desc;
  3.     public GarbageBag(String desc) {
  4.         this.desc = desc;
  5.     }
  6.     public void setDesc(String desc) {
  7.         this.desc = desc;
  8.     }
  9.     @Override
  10.     public String toString() {
  11.         return super.toString() + " " + desc;
  12.     }
  13. }
复制代码
  1. @Slf4j
  2. public class TestABAAtomicMarkableReference {
  3.     public static void main(String[] args) throws InterruptedException {
  4.         GarbageBag bag = new GarbageBag("装满了垃圾");
  5.         // 参数2 mark 可以看作一个标记,表示垃圾袋满了
  6.         AtomicMarkableReference<GarbageBag> ref = new AtomicMarkableReference<>(bag, true);
  7.         log.debug("主线程 start...");
  8.         GarbageBag prev = ref.getReference();
  9.         log.debug(prev.toString());
  10.         new Thread(() -> {
  11.             log.debug("打扫卫生的线程 start...");
  12.             bag.setDesc("空垃圾袋");
  13.             while (!ref.compareAndSet(bag, bag, true, false)) {}
  14.             log.debug(bag.toString());
  15.         }).start();
  16.         Thread.sleep(1000);
  17.         log.debug("主线程想换一只新垃圾袋?");
  18.         boolean success = ref.compareAndSet(prev, new GarbageBag("空垃圾袋"), true, false);
  19.         log.debug("换了么?" + success);
  20.         log.debug(ref.getReference().toString());
  21.     }
  22. }
复制代码
输出
  1. 15:30:09.264 [main] 主线程 start...
  2. 15:30:09.270 [main] cn.itcast.GarbageBag@5f0fd5a0 装满了垃圾
  3. 15:30:09.293 [Thread-1] 打扫卫生的线程 start...
  4. 15:30:09.294 [Thread-1] cn.itcast.GarbageBag@5f0fd5a0 空垃圾袋
  5. 15:30:10.294 [main] 主线程想换一只新垃圾袋?
  6. 15:30:10.294 [main] 换了么?false
  7. 15:30:10.294 [main] cn.itcast.GarbageBag@5f0fd5a0 空垃圾袋
复制代码
原子数组



  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray
AtomicIntegerArray 使用举例

有如下方法
  1. /**
  2. 参数1,提供数组、可以是线程不安全数组或线程安全数组
  3. 参数2,获取数组长度的方法
  4. 参数3,自增方法,回传 array, index
  5. 参数4,打印数组的方法
  6. */
  7. // supplier 提供者 无中生有 ()->结果
  8. // function 函数 一个参数一个结果 (参数)->结果 , BiFunction (参数1,参数2)->结果
  9. // consumer 消费者 一个参数没结果 (参数)->void, BiConsumer (参数1,参数2)-> void
  10. private static <T> void demo(
  11.     Supplier<T> arraySupplier,
  12.     Function<T, Integer> lengthFun,
  13.     BiConsumer<T, Integer> putConsumer,
  14.     Consumer<T> printConsumer ) {
  15.     List<Thread> ts = new ArrayList<>();
  16.     T array = arraySupplier.get();
  17.     int length = lengthFun.apply(array);
  18.     for (int i = 0; i < length; i++) {
  19.         // 每个线程对数组作 10000 次操作
  20.         ts.add(new Thread(() -> {
  21.             for (int j = 0; j < 10000; j++) {
  22.                 putConsumer.accept(array, j%length);
  23.             }
  24.         }));
  25.     }
  26.     ts.forEach(t -> t.start()); // 启动所有线程
  27.     ts.forEach(t -> {
  28.         try {
  29.             t.join();
  30.         } catch (InterruptedException e) {
  31.             e.printStackTrace();
  32.         }
  33.     }); // 等所有线程结束
  34.     printConsumer.accept(array);
  35. }
复制代码
不安全的数组

  1. demo(
  2.     ()->new int[10],
  3.     (array)->array.length,
  4.     (array, index) -> array[index]++,
  5.     array-> System.out.println(Arrays.toString(array))
  6. );
复制代码
结果
  1. [9870, 9862, 9774, 9697, 9683, 9678, 9679, 9668, 9680, 9698]
复制代码
安全的数组

  1. demo(
  2.     ()-> new AtomicIntegerArray(10),
  3.     (array) -> array.length(),
  4.     (array, index) -> array.getAndIncrement(index),
  5.     array -> System.out.println(array)
  6. );
复制代码
结果
  1. [10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
复制代码
字段更新器



  • AtomicReferenceFieldUpdater // 域 字段
  • AtomicIntegerFieldUpdater
  • AtomicLongFieldUpdater
使用字段更新器,可以针对对象的某个域(Field)举行原子操作,只能配合 volatile 修饰的字段使用,否则会出现 IllegalArgumentException 异常
AtomicReferenceFieldUpdater 使用举例

  1. public class Test {
  2.     @Test
  3.     public void test() {
  4.         A a = new A();
  5.         a.setNum(0);
  6.         AtomicReferenceFieldUpdater<A, Integer> num = AtomicReferenceFieldUpdater.newUpdater(A.class, Integer.class, "num");
  7.         List<Thread> ts = new ArrayList<>();
  8.         for (int i = 0; i < 20; i++) {
  9.             // 每个线程对数组作 10000 次操作
  10.             ts.add(new Thread(() -> {
  11.                 for (int j = 0; j < 10000; j++) {
  12.                     while (true) {
  13.                         Integer prev = num.get(a);
  14.                         Integer next = prev + 1;
  15.                         if (num.compareAndSet(a, prev, next)) {
  16.                             break;
  17.                         }
  18.                     }
  19.                 }
  20.             }));
  21.         }
  22.         ts.forEach(Thread::start);
  23.         ts.forEach(t -> {
  24.             try {
  25.                 t.join();
  26.             } catch (InterruptedException e) {
  27.                 e.printStackTrace();
  28.             }
  29.         });
  30.         System.out.println(num.get(a));
  31.     }
  32. }
  33. @Data
  34. class A{
  35.     volatile Integer num;
  36. }
复制代码
结果
200000
执行对比:
  1. public class Test {
  2.    
  3.     @Test
  4.     public void test() {
  5.         A a = new A();
  6.         a.setNum(new AtomicInteger(0));
  7.         List<Thread> ts = new ArrayList<>();
  8.         for (int i = 0; i < 20; i++) {
  9.             // 每个线程对数组作 10000 次操作
  10.             ts.add(new Thread(() -> {
  11.                 for (int j = 0; j < 1000; j++) {
  12.                     a.getNum().addAndGet(1);
  13.                 }
  14.             }));
  15.         }
  16.         ts.forEach(Thread::start);
  17.         ts.forEach(t -> {
  18.             try {
  19.                 t.join();
  20.             } catch (InterruptedException e) {
  21.                 e.printStackTrace();
  22.             }
  23.         });
  24.         System.out.println(a.getNum().get());
  25.     }
  26. }
  27. @Data
  28. class A {
  29.     AtomicInteger num;
  30. }
复制代码
  1. public class Test {
  2.     @Test
  3.     public void test() {
  4.         A a = new A();
  5.         a.setNum(new AtomicReference<>(0));
  6.         List<Thread> ts = new ArrayList<>();
  7.         for (int i = 0; i < 20; i++) {
  8.             // 每个线程对数组作 10000 次操作
  9.             ts.add(new Thread(() -> {
  10.                 for (int j = 0; j < 10000; j++) {
  11.                     while (true) {
  12.                         Integer prev = a.getNum().get();
  13.                         Integer next = prev + 1;
  14.                         if (a.getNum().compareAndSet(prev, next)) {
  15.                             break;
  16.                         }
  17.                     }
  18.                 }
  19.             }));
  20.         }
  21.         ts.forEach(Thread::start);
  22.         ts.forEach(t -> {
  23.             try {
  24.                 t.join();
  25.             } catch (InterruptedException e) {
  26.                 e.printStackTrace();
  27.             }
  28.         });
  29.         System.out.println(a.getNum().get());
  30.     }
  31. }
  32. @Data
  33. class A {
  34.     AtomicReference<Integer> num;
  35. }
复制代码
AI 解释:
  1. 在第三个示例中,使用的是AtomicReference<Integer>,并且在每次递增时,需要显式地进行一个循环,在这个循环中不断尝试使用compareAndSet方法来安全地更新共享变量。在高并发的情况下,多个线程可能会竞争更新同一个变量,这可能导致多次失败的compareAndSet尝试,因而增加了循环的迭代次数。
  2. 在第二个示例中,使用的是AtomicInteger,这是一个针对整数值的原子类。在这个示例中,使用了addAndGet方法,它是一个原子操作,无需显式循环来尝试更新。addAndGet方法会自动处理并发更新的问题,使得代码更加简洁,并且在性能上可能更优,因为它减少了失败更新尝试的可能性。
  3. 在效率和性能方面,第二种方法(使用AtomicInteger和addAndGet)通常比第一种方法(使用AtomicReference<Integer>和显式循环)更优,原因包括:
  4. 更少的重试次数:AtomicInteger的addAndGet方法通常会有更少的重试次数,因为它是专为数值运算设计的原子操作。
  5. 原子操作的优化:AtomicInteger的方法可能会更好地利用底层硬件的原子性支持,如使用CPU的原子指令集(比如compareAndSwap),从而提高操作的效率。
  6. 更简洁的代码:AtomicInteger提供了直接的方法来进行原子性数值操作,使得代码更简洁、更易于理解和维护。
  7. 但是,实际性能的差异还是需要通过具体的性能测试来确定,因为它可能受到多种因素的影响,包括JVM实现、底层硬件、操作系统以及其他并发任务的特性。
  8. --------------------------------------------------------------------------------------------
  9. 这第一段代码使用了AtomicReferenceFieldUpdater来以一种线程安全的方式更新A类的num字段。与前面的例子相比,这里有几个关键点需要注意:
  10. AtomicReferenceFieldUpdater的使用:AtomicReferenceFieldUpdater是一个原子更新器,它允许你以线程安全的方式更新指定类的指定volatile字段。这是一个高级工具,通常用于在不直接使用原子类(例如AtomicInteger)时优化内存使用和性能,因为它可以在不为每个字段创建额外对象的情况下实现字段的原子操作。
  11. 字段定义:num字段定义为volatile类型的Integer,这意味着其修改对其他线程立即可见,但请注意volatile并不能保证复合(多步)操作的原子性。
  12. 性能:尽管使用了AtomicReferenceFieldUpdater,但每次递增操作仍然需要显式的循环来尝试更新,类似于第三个示例中的AtomicReference<Integer>。这意味着高并发下可能会有多次失败的compareAndSet尝试,这可能导致性能上的开销。
  13. 在性能和效率方面,尽管AtomicReferenceFieldUpdater提供了一种更轻量级的原子操作方式,这段代码的性能可能与第三个示例(使用AtomicReference<Integer>)相似,因为它也依赖于显式的重试循环来成功更新值。虽然AtomicReferenceFieldUpdater可能会比为每个变量创建一个AtomicReference对象更节省内存,但由于有失败重试的可能性,其性能可能不如第二个示例(使用AtomicInteger和addAndGet方法)。
  14. 需要注意的是,AtomicReferenceFieldUpdater要求字段必须是volatile类型的,而且对于基本数据类型,比如int,应该使用相应的AtomicIntegerFieldUpdater。在此代码示例中,可能需要将Integer字段替换为int类型,并使用AtomicIntegerFieldUpdater,因为AtomicReferenceFieldUpdater通常是用于引用类型的字段。如果保持为Integer类型,可能会产生额外的装箱和拆箱操作,这将进一步影响性能。
复制代码
原子累加器

LongAdder 使用举例

  1. private static <T> void demo(Supplier<T> adderSupplier, Consumer<T> action) {
  2.     T adder = adderSupplier.get();
  3.     List<Thread> ts = new ArrayList<>();
  4.     // 4 个线程,每人累加 50 万
  5.     for (int i = 0; i < 40; i++) {
  6.         ts.add(new Thread(() -> {
  7.             for (int j = 0; j < 500000; j++) {
  8.                 action.accept(adder);
  9.             }
  10.         }));
  11.     }
  12.     ts.forEach(t -> t.start());
  13.     ts.forEach(t -> {
  14.         try {
  15.             t.join();
  16.         } catch (InterruptedException e) {
  17.             e.printStackTrace();
  18.         }
  19.     });
  20.     long end = System.nanoTime();
  21.     System.out.println(adder + " cost:" + (end - start)/1000_000);
  22. }
复制代码
比力 AtomicLong 与 LongAdder
  1. for (int i = 0; i < 5; i++) {
  2. demo(() -> new LongAdder(), adder -> adder.increment());
  3. }
  4. for (int i = 0; i < 5; i++) {
  5. demo(() -> new AtomicLong(), adder -> adder.getAndIncrement());
  6. }
复制代码
测试结果:LongAdder 的累加性能更优,因为 LongAdder 在有竞争时,设置多个累加单元,Therad-0 累加 Cell[0],而 Thread-1 累加 Cell[1]… 总Cell个数不超过CPU核心数,最后将结果汇总。这样它们在累加时操作的不同的 Cell 变量,因此淘汰了 CAS 重试失败,从而进步性能
Unsafe类

Unsafe 对象提供了非常底层的,操作内存、线程的方法,Unsafe 对象不能直接调用,只能通过反射得到,前面的原子整数、原子引用这些类的底层都是调用 Unsafe,Unsafe 底层会调用 CPU 的 lock 打头的指令之类
得到 Unsafe 对象的代码:
  1. public class UnsafeAccessor {
  2.     static Unsafe unsafe;
  3.     static {
  4.         try {
  5.             Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe 是private的");
  6.             // 因为 theUnsafe 是 private 的
  7.             theUnsafe.setAccessible(true);
  8.             // 因为 theUnsafe 是静态的,从属于类,不属于对象,所以传null即可获得字段值
  9.             unsafe = (Unsafe) theUnsafe.get(null);
  10.         } catch (NoSuchFieldException | IllegalAccessException e) {
  11.             throw new Error(e);
  12.         }
  13.     }
  14.     static Unsafe getUnsafe() {
  15.         return unsafe;
  16.     }
  17. }
复制代码
使用 Unsafe 对象实现 CAS 操作

  1. @Data
  2. class Student {
  3.     volatile int id;
  4.     volatile String name;
  5. }
复制代码
  1. Unsafe unsafe = UnsafeAccessor.getUnsafe();
  2. Field id = Student.class.getDeclaredField("id");
  3. Field name = Student.class.getDeclaredField("name");
  4. // 获得成员变量的偏移量
  5. long idOffset = UnsafeAccessor.unsafe.objectFieldOffset(id);
  6. long nameOffset = UnsafeAccessor.unsafe.objectFieldOffset(name);
  7. Student student = new Student();
  8. // 使用 cas 方法替换成员变量的值
  9. UnsafeAccessor.unsafe.compareAndSwapInt(student, idOffset, 0, 20); // 返回 true
  10. UnsafeAccessor.unsafe.compareAndSwapObject(student, nameOffset, null, "张三"); // 返回 true
  11. System.out.println(student);
复制代码
输出
Student(id=20, name=张三)
怎样自己实现线程安全的整数自减操作?

  1. class MyAtomicInteger {
  2.     private volatile int num;
  3.     static final Unsafe unsafe;
  4.     static final long DATA_OFFSET;
  5.    
  6.     static {
  7.         unsafe = UnsafeAccessor.getUnsafe();
  8.         try {
  9.             // num 属性在 DataContainer 对象中的偏移量,用于 Unsafe 直接访问该属性
  10.             DATA_OFFSET = unsafe.objectFieldOffset(AtomicData.class.getDeclaredField("num"));
  11.         } catch (NoSuchFieldException e) {
  12.             throw new Error(e);
  13.         }
  14.     }
  15.    
  16.     public MyAtomicInteger(int num) {
  17.         this.num = num;
  18.     }
  19.     public void decrease(int amount) {
  20.         while(true) {
  21.             int prev = this.num;
  22.             int next = prev - amount;
  23.             // cas 尝试修改 num 为 旧值 + amount,如果期间旧值被别的线程改了,返回 false
  24.             if (unsafe.compareAndSwapInt(this, DATA_OFFSET, prev, next)) {
  25.                 return;
  26.             }
  27.         }
  28.     }
  29.     public int getNum() {
  30.         return num;
  31.     }
  32. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

飞不高

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表