【高并发专题】-java线程安全-原子性-Automic包详解

线程安全性

当多个线程同时访问某个类时,不管采取何种线程调度方法,在主调代码中不需要采取额外的同步或者协同,这个类都能表现出正确的行为,那么这个类就是线程安全的.

线程安全的三个特性

原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作.

可见性:一个线程对主内存的修改会被其他线程所看到.

有序性:编译器和处理器可能会为了性能对操作指令进行重新排序,重新排序后对单个线程的程序执行不会有影响,但对多个线程的程序执行却会造成影响.

模拟线程不安全案例:

//模拟线程不安全
public class AtomicTest1 {
    private static final int clientTotal = 5000;
    private static final int threadTotal = 200;
    private static int count = 0;

    public static void main(String[] args) throws Exception {
        ExecutorService es = Executors.newCachedThreadPool();
        final CountDownLatch ctl = new CountDownLatch(clientTotal);
        final Semaphore semaphore = new Semaphore(threadTotal);
        for (int i = 0; i < clientTotal; i++) {
            es.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                ctl.countDown();
            });
        }
        ctl.await();
        es.shutdown();
        System.out.println("count:" + count);
    }

    public static void add() {
        count++;
    }
}

几次运行结果:

  

显然出现了线程不安全的情况.

我们可以使用java.util.current.atomic包下提供的类进行解决

//模拟线程安全
public class AtomicTest2 {
    private static final int clientTotal = 5000;
    private static final int threadTotal = 200;
    private static AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws Exception {
        ExecutorService es = Executors.newCachedThreadPool();
        final CountDownLatch ctl = new CountDownLatch(clientTotal);
        final Semaphore semaphore = new Semaphore(threadTotal);
        for (int i = 0; i < clientTotal; i++) {
            es.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                ctl.countDown();
            });
        }
        ctl.await();
        es.shutdown();
        System.out.println("count:" + count);
    }

    public static void add() {
        count.getAndIncrement();
    }
}

多次执行后,结果依旧正确:

其中getAndIncrement方法的实现,调用了sun.misc.unsafe包下的unsafe方法

compareAndSwap,这就是我们熟悉的CAS自旋锁,Jdk1.8中的concurrentHashMap也有用到它.

提到CAS就顺便说一下CAS中的ABA问题,ABA即一个线程在把A的值改成了B,另外一个线程有把B改回了A,此时再来一个线程操作时发现A的值并没有改变,但实际上A的值是有被操作过的,为了避免在这种情况下CAS锁失效.atomic包提供了AtomicStampReference类来解决ABA问题,每次对值的改变操作,都会使得版本号发生改变,这样就可以避免ABA问题.

顺便提一下,AtomicLong,AtomicLong作用和AtomicInteger类似,只不过数据类型不一样,之所以提AtomicLong是为了引出jdk8中新提供的LongAdder,它们功能相仿,但性能却有差别,在上面的源码中我们可以看到,getAndAdd方法使用do while循环调用cas实现的,这在并发比较高的情况下,循环耗费较多性能,所以在LongAdder对其进行了改进,将对数字的读写操作通过类似hash的算法分散到不同的cell中,最后再对cell累加得出计算后的值,在高并发的场景下,分散带来的性能提升是显而易见的,不过也因此付出了精度损失的代价,累加后的结果可能会出现小幅度误差.

JUC下的atomic包提供了很多可以保证原子性的类,其中常见常用的如图:

对比字段的期望值和更新值AtomicReference:

public class AtomicTest4 {
    public static void main(String[] args) {
        AtomicReference<Integer> atomicReference = new AtomicReference<>(1);
        atomicReference.compareAndSet(1,2);
        atomicReference.compareAndSet(2,4);
        atomicReference.compareAndSet(3,5);
        atomicReference.compareAndSet(4,6);
        System.out.println(atomicReference.get());
    }
}

结果:

AtomicXXXFieldUpdater实现对类中指定的volatile对应类型字段进行原子性操作.

public class AtomicTest5 {
    @Getter
    public volatile int count = 100;
    private static AtomicTest5 atomicTest5 = new AtomicTest5();
    private static AtomicIntegerFieldUpdater<AtomicTest5> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicTest5.class, "count");
    public static void main(String[] args) {
        if (updater.compareAndSet(atomicTest5,100,200)){
            System.out.println("success::"+atomicTest5.getCount());
        }else if (updater.compareAndSet(atomicTest5,500,800)){
            System.out.println("success::"+atomicTest5.getCount());
        }else {
            System.out.println("fail:"+atomicTest5.getCount());
        }
    }
}

结果:

使用AtomicBoolean来确保某段代码/方法只被执行一次:

public class AutomicTest6 {
    private static final int clientTotal = 5000;
    private static final int threadTotal = 2000;
    private static AtomicBoolean isHappend = new AtomicBoolean(false);

    public static void main(String[] args) throws Exception {
        ExecutorService es = Executors.newCachedThreadPool();
        final CountDownLatch ctl = new CountDownLatch(clientTotal);
        final Semaphore semaphore = new Semaphore(threadTotal);
        for (int i = 0; i < clientTotal; i++) {
            es.execute(() -> {
                try {
                    semaphore.acquire();
                    test();
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                ctl.countDown();
            });
        }
        ctl.await();
        es.shutdown();
    }

    private static void test() {
        if (isHappend.compareAndSet(false, true)) {
            System.out.println("executed");
        }
    }
}

结果:

AtomicIntegerArray,相当于是一个Int数组,但其中的元素可用原子更新.

public class AtomicTest7 {
    public static void main(String[] args) {
        AtomicIntegerArray array = new AtomicIntegerArray(10);
        array.set(5,50);
        ExecutorService es = Executors.newCachedThreadPool();
        for (int i = 0; i < 50; i++) {
            es.execute(()->{
                array.compareAndSet(5,50,80);
            });
        }
        es.shutdown();
        System.out.println(array.get(5));
    }
}

结果: 

关于atomic包就分享到这里,感兴趣可以翻阅Jdk1.8-api,翻api+适当读点源码对加深理解非常有帮助,最后感谢阅读,文中如有不正之处或有更好的建议还望各位看客不吝赐教.

发布了89 篇原创文章 · 获赞 70 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/lovexiaotaozi/article/details/90751466