Java原子操作保证方案

引言

原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。之所以要把它们排除在外是因为它们都比较大,而JVM的设计规范又没有要求读操作和赋值操作必须是原子操作(JVM可以试着去这么做,但并不保证)。

三种保证方式

  1. 加锁
  2. AtomicXXX
  3. LongAdder

现在使用1000个线程,对一个数进行加1操作,每一个线程操作10_0000遍;
最终结果应该是:10_0000*1000 = 1_0000_0000

不作任何操作

在多线程的情况下,对一个变量进行修改,如果不做任何操作,使用传统的方式来进行的话,会导致最终的结果不是我们想要的结果。

    int count = 0;
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
        UnsaveTest unsaveTest = new UnsaveTest();
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < 1000; i++) {
            threads[i] = new Thread(() -> {
                int j = 0;
                //每个线程对成员变量count进行加1操作,每个线程加10_0000次,1000个线程。
                //所以 最终结果应该是:10_0000*1000 = 1_0000_0000
                while (j++ < 10_0000) {
                    unsaveTest.count++;
                }
            });
        }
        long timeStart = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            threads[i].start();
        }
        for (int i = 0; i < 1000; i++) {
            threads[i].join();
        }
        long timeEnd = System.currentTimeMillis();
        System.out.println("期望结果:"+100000 * 1000);
        System.out.println("运行结果 : " + unsaveTest.count);
        System.out.println("耗时:" + (timeEnd - timeStart));
    }

运行结果

期望结果:100000000
运行结果 : 97416060
耗时:82

加锁Synchronized

通过加锁的方式来保证原子操作,但有的时候同步变量只有一个,如果依然使用加锁的方式来保证原子性,在多线程竞争激烈的情况下,升级为重量级锁,会导致效率低下。
代码如下

    int count = 0;
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
        UnsaveTest unsaveTest = new UnsaveTest();
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < 1000; i++) {
            threads[i] = new Thread(() -> {
                int j = 0;
                //每个线程对成员变量count进行加1操作,每个线程加10_0000次,1000个线程。
                //所以 最终结果应该是:10_0000*1000 = 1_0000_0000
                while (j++ < 10_0000) {
                    synchronized (unsaveTest) {
                        unsaveTest.count++;
                    }
                }
            });
        }
        long timeStart = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            threads[i].start();
        }
        for (int i = 0; i < 1000; i++) {
            threads[i].join();
        }
        long timeEnd = System.currentTimeMillis();
        System.out.println("期望结果:" + 100000 * 1000);
        System.out.println("运行结果 : " + unsaveTest.count);
        System.out.println("耗时:" + (timeEnd - timeStart));
    }

运行结果

期望结果:100000000
运行结果 : 100000000
耗时:2574

使用AtomicXXXX

AtomicXXXX是并发包里面提供的一系列并发工具类,通过Unsafe的CAS操作(最终调用是通过CPU指令级别的方式)来保证原子操作,也就是所谓的无锁优化
代码如下

AtomicInteger counter = new AtomicInteger(0);
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
        UnsaveTest unsaveTest = new UnsaveTest();
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < 1000; i++) {
            threads[i] = new Thread(() -> {
                int j = 0;
                //每个线程对成员变量count进行加1操作,每个线程加10_0000次,1000个线程。
                //所以 最终结果应该是:10_0000*1000 = 1_0000_0000
                while (j++ < 10_0000) {
                    unsaveTest.counter.incrementAndGet();
                }
            });
        }
        long timeStart = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            threads[i].start();
        }
        for (int i = 0; i < 1000; i++) {
            threads[i].join();
        }
        long timeEnd = System.currentTimeMillis();
        System.out.println("期望结果:" + 100000 * 1000);
        System.out.println("运行结果 : " + unsaveTest.counter.get());
        System.out.println("耗时:" + (timeEnd - timeStart));
    }

运行结果

期望结果:100000000
运行结果 : 100000000
耗时:1006

使用LongAdder

LongAdder是JDK1.8后为了提高AtomicLong运行效率的一个新并发工具类
代码如下:

LongAdder longAdder = new LongAdder();
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
        UnsaveTest unsaveTest = new UnsaveTest();
        Thread[] threads = new Thread[1000];
        for (int i = 0; i < 1000; i++) {
            threads[i] = new Thread(() -> {
                int j = 0;
                //每个线程对成员变量count进行加1操作,每个线程加10_0000次,1000个线程。
                //所以 最终结果应该是:10_0000*1000 = 1_0000_0000
                while (j++ < 10_0000) {
                    unsaveTest.longAdder.increment();
                }
            });
        }
        long timeStart = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            threads[i].start();
        }
        for (int i = 0; i < 1000; i++) {
            threads[i].join();
        }
        long timeEnd = System.currentTimeMillis();
        System.out.println("期望结果:" + 100000 * 1000);
        System.out.println("运行结果 : " + unsaveTest.longAdder.intValue());
        System.out.println("耗时:" + (timeEnd - timeStart));
    }

运行结果

期望结果:100000000
运行结果 : 100000000
耗时:212

总结

通过以上的几种测试,我们可以得出以下结果
在不作任何其它操作的情况下,运行效率最高,但是结果却不正确,这也是最大的问题。如果一个操作不能保证结果正确,那它就失去了意义。
在保证结果正确的情况下
Synchronized效率最低
Synchronized在多线程竞争激烈的情况下,效率会显著下降,因为无锁————》偏向锁————》轻量级锁(自旋锁)————》重量级锁,大量线程在多次等待锁的过程中,会自旋为重量级锁,这个时候效率会变得很低。
其次是AtomicInteger
AtomicInteger底层使用的Unsafe类的CAS操作,是通过CPU指令级别的方式来保证原子操作。效率也还可以。
效率最高的是LongAdder。
LongAdder类与AtomicLong类的区别在于高并发时前者将对单一变量的CAS操作分散为对数组cells中多个元素的CAS操作,取值时进行求和;而在并发较低时仅对base变量进行CAS操作,与AtomicLong类原理相同

posted @ 2021-05-19 18:31  心若向阳花自开  阅读(280)  评论(0编辑  收藏  举报