美文网首页一些收藏
Java并发编程 原子类

Java并发编程 原子类

作者: 香沙小熊 | 来源:发表于2020-03-09 10:51 被阅读0次

    1.什么是原子类

    • 不可分割
    • 一个操作是不可中断的,即便是多线程的情况下也可以保证
    • java.util.concurrent.atomic
    原子类的作用

    原子类的作用和锁类似,是为了保证并发情况下线程安全。不过原子类型相比于锁,有一定优势:
    粒度更细:原子变量可以把竞争范围缩小到变量级别,这使我们可以获得的最细粒度的情况了,通常锁的粒度都要大于原子变量的粒度。
    效率更高:通常,使用原子类的效率会比使用锁的效率要高,除了高度竞争的情况

    2.Atomic*基本类型原子类,以AtomicInteger为例

    • AtomicInteger 整型原子类

    AtomicInteger常用方法:
    public final int get() //取当前的值
    public final int getAndSet(int newValue) //获取当前的值,并设置新的值
    public final int getAndIncrement() //取当前的值,并自增
    public final int getAndDecrement() //获取当前的值,并自减
    public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
    public final boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)

    public class AtomicIntegerDemo implements Runnable {
    
        private static final AtomicInteger atomicInteger = new AtomicInteger();
    
        public void incrementAtomic() {
            atomicInteger.getAndIncrement();
        }
    
        private static volatile int basicCount = 0;
    
        public  void incrementBasic() {
            basicCount++;
        }
    
        public static void main(String[] args) throws InterruptedException {
            AtomicIntegerDemo r = new AtomicIntegerDemo();
            Thread t1 = new Thread(r);
            Thread t2 = new Thread(r);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("原子类的结果:" + atomicInteger.get());
            System.out.println("普通变量的结果:" + basicCount);
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                incrementAtomic();
                incrementBasic();
            }
        }
    }
    
    原子类的结果:20000
    普通变量的结果:18488
    
    • AtomicLong 长整型原子类
    • AtomicBoolean 布尔型原子类

    4.Atomic*Array数组类型原子类

    AtomicIntegerArray:提供对int[]数组元素的原子性更新操作。

    public class AtomicArrayDemo {
    
        public static void main(String[] args) {
            AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000);
            Incrementer incrementer = new Incrementer(atomicIntegerArray);
            Decrementer decrementer = new Decrementer(atomicIntegerArray);
            Thread[] threadsIncrementer = new Thread[100];
            Thread[] threadsDecrementer = new Thread[100];
            for (int i = 0; i < 100; i++) {
                threadsDecrementer[i] = new Thread(decrementer);
                threadsIncrementer[i] = new Thread(incrementer);
                threadsDecrementer[i].start();
                threadsIncrementer[i].start();
            }
    
    //        Thread.sleep(10000);
            for (int i = 0; i < 100; i++) {
                try {
                    threadsDecrementer[i].join();
                    threadsIncrementer[i].join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            for (int i = 0; i < atomicIntegerArray.length(); i++) {
    //            if (atomicIntegerArray.get(i)!=0) {
    //                System.out.println("发现了错误"+i);
    //            }
                System.out.println(atomicIntegerArray.get(i));
            }
            System.out.println("运行结束");
        }
    }
    
    class Decrementer implements Runnable {
    
        private AtomicIntegerArray array;
    
        public Decrementer(AtomicIntegerArray array) {
            this.array = array;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < array.length(); i++) {
                 array.getAndDecrement(i);
            }
        }
    }
    
    class Incrementer implements Runnable {
    
        private AtomicIntegerArray array;
    
        public Incrementer(AtomicIntegerArray array) {
            this.array = array;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < array.length(); i++) {
                array.getAndIncrement(i);
            }
        }
    }
    

    AtomicLongArray:提供对long[]数组元素的原子性更新操作。

    AtomicReferenceArray:提供对引用类型[]数组元素的原子性更新操作。

    5.Atomic*Reference引用类型原子类

    AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。

    AtomicReference是作用是对”对象”进行原子操作。 提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference(例如比较和交换操作)将不会使得AtomicReference处于不一致的状态。

    public class AtomicReferenceDemo {
        public static void main(String[] args) {
    
            // 创建两个Person对象,它们的id分别是101和102。
            Person2 p1 = new Person2(101);
            Person2 p2 = new Person2(102);
            // 新建AtomicReference对象,初始化它的值为p1对象
            AtomicReference ar = new AtomicReference(p1);
            //更改p1的id.
            p1.setId(106);
            // 通过CAS设置ar。如果ar的值为p1的话,则将其设置为p2。
            ar.compareAndSet(p1, p2);
    
            Person2 p3 = (Person2)ar.get();
            System.out.println("p3 is "+p3);
            System.out.println("p3.equals(p1)="+p3.equals(p1));
        }
    
    }
    class Person2 {
        volatile long id;
        public Person2(long id) {
            this.id = id;
        }
        @Override
        public String toString() {
            return "id:"+id;
        }
        public void setId(long id){
            this.id=id;
        }
    }
    
    • AtomicStampedReference
    • AtomicMarkableReference

    6.把普通变量升级为原子类

    AtomicReference,用于整个对象的更新。但不是每次都必须更新整个对象,有可能我们只需对对象中的某个字段进行原子性修改时,那么就需要使用原子更新字段类

    • AtomicIntegerFieldUpdater对普通变量进行升级
    public class AtomicIntegerFieldUpdaterDemo implements Runnable{
    
        static Candidate tom;
        static Candidate peter;
    
        public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater
                .newUpdater(Candidate.class, "score");
    
        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                peter.score++;
                scoreUpdater.getAndIncrement(tom);
            }
        }
    
        public static class Candidate {
    
            volatile int score;
        }
    
        public static void main(String[] args) throws InterruptedException {
            tom=new Candidate();
            peter=new Candidate();
            AtomicIntegerFieldUpdaterDemo r = new AtomicIntegerFieldUpdaterDemo();
            Thread t1 = new Thread(r);
            Thread t2 = new Thread(r);
            t1.start();
            t2.start();
            t1.join();
            t2.join();
            System.out.println("普通变量:"+peter.score);
            System.out.println("升级后的结果"+ tom.score);
        }
    }
    
    普通变量:19831
    升级后的结果20000
    
    AtomicIntegerFieldUpdater 注意点

    不同于以往的几个原子更新类,对于 AtomicIntegerFieldUpdater 的使用稍微有一些限制和约束,约束如下:

    1. 字段必须是 volatile 类型的,在线程之间共享变量时保证立即可见。

    2. 字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。

    3. 只能是实例变量,不能是类变量,也就是说不能加static关键字。

    4. 只能是可修改变量,不能是final变量,因为final的语句就是不可修改。实际上final的语义与volatile是有冲突的,两个关键字不能同时存在。

    5. 对于 AtomicIntegerFieldUpdate 和 AtomicLongFieldUpdate 只能修改 int/long 类型的字段,不能修改包装类型。如果要修改包装类型就需要使用 AtomicReferenceFieldUpdate。

    • AtomicLongFieldUpdater 原子更新长整型字段的更新器
    • AtomicReferenceFieldUpdater 原子更新带有版本号的引用类型

    7.Adder累加器

    • 是Java 8引入的,相对是比较新的一个类
    • 高并发下LongAdder比AtomicLong效率高,不过本质是空间换时间
    • 竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,提高了并发性
      AtomicLong
        public static void main(String[] args) throws InterruptedException {
            AtomicLong counter = new AtomicLong(0);
            ExecutorService service = Executors.newFixedThreadPool(20);
            long start = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                service.submit(new Task(counter));
            }
            service.shutdown();
            while (!service.isTerminated()) {
    
            }
            long end = System.currentTimeMillis();
            System.out.println(counter.get());
            System.out.println("AtomicLong耗时:" + (end - start));
        }
    
        private static class Task implements Runnable {
    
            private AtomicLong counter;
    
            public Task(AtomicLong counter) {
                this.counter = counter;
            }
    
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    counter.incrementAndGet();
                }
            }
        }
    }
    
    100000000
    AtomicLong耗时:1575
    
    public class LongAdderDemo {
    
        public static void main(String[] args) throws InterruptedException {
            LongAdder counter = new LongAdder();
            ExecutorService service = Executors.newFixedThreadPool(20);
            long start = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                service.submit(new Task(counter));
            }
            service.shutdown();
            while (!service.isTerminated()) {
    
            }
            long end = System.currentTimeMillis();
            System.out.println(counter.sum());
            System.out.println("LongAdder耗时:" + (end - start));
        }
    
        private static class Task implements Runnable {
    
            private LongAdder counter;
    
            public Task(LongAdder counter) {
                this.counter = counter;
            }
    
            @Override
            public void run() {
                for (int i = 0; i < 10000; i++) {
                    counter.increment();
                }
            }
        }
    }
    
    100000000
    LongAdder耗时:225
    
    1. 这里演示多线程情况下AtomicLong的性能,有16个线程对同一个AtomicLong累加
    2. AtomicLong由于竞争很激烈,每一次加法,都要flush和refresh,导致很消耗资源
    LongAdder带来的改进和原理
    • 在内部,这个LongAdder的实现原理和刚才的AtomicLong是有不同的,AtomicLong的实现原理是,每一次加法都需要做同步,所以在高并发的时候会导致冲突比较多,也就是降低了效率
    • 而此时的LongAdder,每个线程会有自己的一个计数器,仅用来在自己线程内计数,这样一来就不会和其他线程的计数器干扰
    • LongAdder引入了分段累加的概念,内部有一个base变量和一个Cell[]数组共同参与计算
    • base变量:竞争不激烈,直接累加到该变量上
    • Cell[]数组:竞争激烈,各个线程分散累加到自己的槽Cell[i]中
      源码解析
        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;
        }
    

    LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。
    缺点是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。

    8.Accumulator累加器

    Accumulator的Adder非常相似,Accumulator就是一个更通用版本的Adder

    public class LongAccumulatorDemo {
    
        public static void main(String[] args) {
            LongAccumulator accumulator = new LongAccumulator((x, y) -> x + y, 0);
            ExecutorService executor = Executors.newFixedThreadPool(8);
            /**
             * 从1累加99
             */
            IntStream.range(1, 100).forEach(i ->
            executor.submit(() -> accumulator.accumulate(i)));
            executor.shutdown();
            while (!executor.isTerminated()) {
    
            }
            System.out.println(accumulator.getThenReset());
        }
    }
    
    
    4950
    

    使用场景
    1.并行计算
    2.程序不要求顺序计算

    相关文章

      网友评论

        本文标题:Java并发编程 原子类

        本文链接:https://www.haomeiwen.com/subject/uqxddhtx.html