Atomic详解

    技术2022-07-11  79

    举个例子,比如说10个线程分别对data执行一次data++操作,我们以为最后data的值会变成10,其实不是。因为多线程并发操作下,就是会有这种安全问题,导致数据结果不准确。 public class Test {      private int data=0;     //多个线程对一个data不停的累加1操作 }

    针对并发问题,可以加锁(比如内置锁或者lock),还有一种方式是使用Atomic相应的类和方法保证在并发下数据的准确性

    一、Atomic

    AtomicInteger ,AtomicBoolean,AtomicLong,AtomicReference

    public static void main(String[] args) throws Exception{ Account account=new Account(10000); List<Thread> list=new ArrayList<>(); for(int i=0;i<1000;i++){ list.add(new Thread(()->{ account.updateAcount(-10); })); } long startTime=System.nanoTime(); list.forEach(t->t.start()); while (Thread.activeCount()>2){ Thread.sleep(100); } long end=System.nanoTime(); System.out.println(account.getAcount()+"耗时:"+(end-startTime)/1000_000+"ms"); } static class Account{ private AtomicInteger balance; public Account(Integer balance){ this.balance=new AtomicInteger(balance); } private Integer getAcount(){ return balance.get(); } private void updateAcount(Integer amount){ while (true){ //获取最新余额 int currentAcount = balance.get(); Integer newBalance=currentAcount+amount; if(balance.compareAndSet(currentAcount, newBalance)){ break; } } } }

    二、原子数组

    AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray public static void main(String[] args) throws Exception{ test(()->new int[10], (arr)->arr.length, (arr,index)->arr[index]++, (arr)-> System.out.println(Arrays.toString(arr))); test(()->new AtomicIntegerArray(10), (arr)->arr.length(), (arr,index)->arr.getAndIncrement(index), (arr)-> System.out.println(arr)); } private static <T> void test( Supplier<T> supplier, Function<T,Integer> function, BiConsumer<T,Integer> biConsumer, Consumer<T> printConsumer) throws Exception{ List<Thread> list=new ArrayList<>(); T t = supplier.get(); Integer length = function.apply(t); for(int i=0;i<length;i++){ list.add(new Thread(()->{ for(int j=0;j<10000;j++){ biConsumer.accept(t,j%length); } })); } list.forEach(th->th.start()); while (Thread.activeCount()>2){ Thread.sleep(100); } printConsumer.accept(t); }

    三、字段更新器

    AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater

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

    public class AtomicReferenceFieldUpdaterDemo { public static void main(String[] args) { Student student=new Student(); AtomicReferenceFieldUpdater updater=AtomicReferenceFieldUpdater.newUpdater(Student.class,String.class,"name"); boolean bool = updater.compareAndSet(student, null, "neo"); System.out.println(bool); System.out.println(student); } static class Student{ volatile String name; @Override public String toString() { return "Student{" + "name='" + name + '\'' + '}'; } } }

    四、原子累加器

    DoubleAccumulator,DoubleAdder,LongAccumulator,LongAdder

    public static void main(String[] args) throws Exception{ test( ()->new AtomicLong(0), (addr)->addr.getAndIncrement() ); //使用累计器 test( ()->new LongAdder(),//默认值是0,累加性能高于AtomicLong (add)->add.increment() ); } private static <T> void test(Supplier<T> adder, Consumer<T> action) throws Exception{ T add = adder.get(); List<Thread> ts=new ArrayList<>(); for(int i=0;i<4;i++){ ts.add(new Thread(()->{ for(int j=0;j<500000;j++){ action.accept(add); } })); } long start=System.nanoTime(); ts.forEach(t->t.start()); while (Thread.activeCount()>2){ Thread.sleep(100); } long end=System.nanoTime(); System.out.println("花费:"+(end-start)/1000_000+"ms"); System.out.println(add); }

    LongAdder性能提升的原因:在竞争时,设置多个累加单元,Thread-0累加Cell[0],Thread-1累加Cell[1]依次类推。最后将结果汇总。这样他们在累加的时候操作不同的Cell变量,因此减少了CAS重试失败,提高性能。

    关于java8对cas的优化,请查看博主另外一篇博文(https://blog.csdn.net/zuodaoyong/article/details/104122924)

     

     

    Processed: 0.011, SQL: 9