原子类的分析与使用

原子类

1、原子类作用与介绍。

1.1特点

  • 不可分离
  • 一个操作中式不可中断的,即使是多线程的情况下也可以保证。

1.2作用

  • 当某些变量需要保证线程安全的时候使用。

  • 原子类的作用和锁类似,是为了保证并发情况下线程安全。不过原子类相比于锁,有一定的优势

    • 粒度更细:原子变量可以把竞争范围缩小到变量级别,这时我们可以获得的最细粒度的情况了,通常锁的粒度都要大于原子变量的粒度。
    • 效率更高:通常,使用原子类的效率会比使用锁的效率更高,除了高度竞争的情况。

2、6类原子类

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KYPXC70s-1600216554785)(%E5%8E%9F%E5%AD%90%E7%B1%BB.assets/image-20200915172735035.png)]

2.1、Atomic*基本类型原子类

2.1.1AtomicInteger

常用方法

  1. public final int get();
  2. public final int getAndSet(int newValue)//获取当前值,并设定新的值
  3. public final int getAndIncrement();//获取当前值并自增。
  4. public final int getAndAdd(int data);//获取当前值并加上data。
  5. boolean compareAndSet(int expect,int newValue);//如果是期待的值就替换为newValue。

代码演示

/**
 * 描述:     演示AtomicInteger的基本用法,对比非原子类的线程安全问题,使用了原子类之后,不需要加锁,也可以保证线程安全。
 */
public class AtomicIntegerDemo1 implements Runnable {

    private static final AtomicInteger atomicInteger = new AtomicInteger();

    public void incrementAtomic() {
        atomicInteger.getAndAdd(1);
    }

    private static volatile int basicCount = 0;

    public  void incrementBasic() {
        basicCount++;
    }

    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerDemo1 r = new AtomicIntegerDemo1();
        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
普通变量的结果:19672

  • AtomicLong
  • AtomicBoolean

2.1.2Atomic数组

代码演示


/**
 * 描述:     演示原子数组的使用方法
 */
public class AtomicArrayDemo {

    public static void main(String[] args) {
        //创建长度为1000的数据,初始化为0-
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000);
        Incrementer incrementer = new Incrementer(atomicIntegerArray);
        Decrementer decrementer = new Decrementer(atomicIntegerArray);
        //建立两个包含100个线程的线程数组
        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();
            }
        }

        //自加了一千次,自减了一千次。所以最后结果为0
        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);
        }
    }
}

2.1.3 把普通变量升级为具有原子功能。

  • AtomicIntegerFieldUpdater对普通变量进行升级

  • 使用场景:偶尔需要一个原子get-set操作。

  • 代码演示:

    
    /**
     * 描述:     演示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);
        }
    }
    
    

    普通变量:18696
    升级后的结果20000

3、Adder累加器

  • 是Java8引入的,是一个比较新的类

  • 高并发下LongAdder比AtomicLong效率高,本质上是空间换时间。

  • 竞争激烈的时候,LongAdder把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,是多段锁的理念,提高了并发性。

  • AtomicLong与LongAdder 的代码演示。

    /**
     * 描述:     演示高并发场景下,LongAdder比AtomicLong性能好
     */
    public class AtomicLongDemo {
    
        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();
                }
            }
        }
    }
    
    /**
     * 描述:    TODO 演示高并发场景下,LongAdder比AtomicLong性能好
     */
    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();
                }
            }
        }
    }
    
    

    原因分析

    • ​ 由于竞争很激烈,每一次加法,都要进行flush和refresh,导致非常耗费资源。每一次加法都需要同步。

    • 而LongAdder,每个线程都有一个自己的计数器,仅仅是用在自己线程内计数,这样一来就不会被其它线程的计数器干扰。最后再把所有的线程的计数器汇总,获得累加次数。

    • LongAdder引入了分段累加的概念,内部有一个base变量和一个Cell[]数组共同参与计数:

      • base变量:竞争不激烈,直接累加到该变量上

      • Cell[]数组:竞争激烈,各个线程分散累加到自己的Cell[i]中。

      • sum源码分析。通过源码可知,在累加的过程中并没有添加锁,as[i]在加的过程中仍然有可能发生变化,仍是线程不安全的。

        [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-92IU5DK5-1600216554787)(%E5%8E%9F%E5%AD%90%E7%B1%BB.assets/image-20200916082202165.png)]

猜你喜欢

转载自blog.csdn.net/qq_45372719/article/details/108614215