美文网首页
线程安全性-原子性

线程安全性-原子性

作者: 三不猴子 | 来源:发表于2019-05-05 21:48 被阅读0次

    线程安全性

    定义

    当多个线程访问同一个类时,不管运行时环境采用何种调度方式,不论线程如何交替执行,在主调代码中不需要额外的协同或者同步代码时,这个类都可以表现出正确的行为,我们则称这个类为线程安全的。

    线程安全性

    1. 原子性:提供了互斥访问,同一时刻只能有一个线程来对他进行操作。
    2. 可见性:一个线程对主内存的修改可以及时被其他线程观察到。
    3. 有序性:一个线程观察其他线程中的指令顺序,由于指令重排序的存在,该结果一般杂乱无序。

    原子性 - Atomic包

    1. AtomicXXX 是通过 CAS(CompareAndSwap)来保证线程原子性 通过比较操作的对象的值(工作内存的值)与底层的值(共享内存中的值)对比是否相同来判断是否进行处理,如果不相同则重新获取。如此循环操作,直至获取到期望的值。
      (关于什么是主内存什么事工作内存在上篇博客中进行介绍了,不懂的同学可以翻一下)示例代码:
    @Slf4j
    public class AtomicExample2 {
    
        // 请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static AtomicLong count = new AtomicLong(0);
    
        public static void main(String[] args) throws Exception {
            ExecutorService executorService = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(threadTotal);
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal ; i++) {
                executorService.execute(() -> {
                    try {
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (Exception e) {
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}", count.get());
        }
    
        private static void add() {
            count.incrementAndGet();
            // count.getAndIncrement();
        }
    }
    
    
    1. LongAdder和DoubleAdder
      jdk8中新增的保证同步操作的类,我们之前介绍了AtomicXXX来保证原子性,那么为什么还有有LongAdder呢?
      说AtomicXXX的实现是通过死循环来判断值的,在低并发的情况下AtomicXXX进行更改值的命中率还是很高的。但是在高并发下进行命中率可能没有那么高,从而一直执行循环操作,此时存在一定的性能消耗,在jvm中我们允许将64位的数值拆分成2个32位的数进行储存的,LongAdder的思想就是将热点数据分离,将AtomicXXX中的核心数据分离,热点数据会被分离成多个数组,每个数据都单独维护各自的值,将单点的并行压力发散到了各个节点,这样就提高了并行,在低并发的时候性能基本和AtomicXXX相同,在高并发时具有较好的性能,缺点是在并发更新时统计时可能会出现误差。在低并发,需要全局唯一,准确的比如id等使用AtomicXXX,要求性能使用LongAdder
    @Slf4j
    public class AtomicExample3 {
    
        // 请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static LongAdder count = new LongAdder();
    
        public static void main(String[] args) throws Exception {
            ExecutorService executorService = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(threadTotal);
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);、】【poiuytrewq;'
            
            for (int i = 0; i < clientTotal ; i++) {
                executorService.execute(() -> {
                    try {
                        semaphore.acquire();
                        add();
                        semaphore.release();
                    } catch (Exception e) {
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("count:{}", count);
        }
    
        private static void add() {
            count.increment();
        }
    }
    
    
    1. AtomicReference、AtomicReferenceFieldUpdater
      AtomicReference是给定指定的期望值当期望值与主内存中的值相同然后更新,示例代码
    
    @Slf4j
    public class AtomicExample4 {
    
        private static AtomicReference<Integer> count = new AtomicReference<>(0);
    
        public static void main(String[] args) {
            count.compareAndSet(0, 2); // 2
            count.compareAndSet(0, 1); // no
            count.compareAndSet(1, 3); // no
            count.compareAndSet(2, 4); // 4
            count.compareAndSet(3, 5); // no
            log.info("count:{}", count.get());
        }
    }
    
    AtomMNBVCXZenceFieldUpdater主要是更新某一个实例对象的一个字段这个字段必须是用volatile修饰同时不能是private修饰的,·157-=·   123444457890-
    
    @Slf4j
    public class AtomicExample5 {
    
        private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
                AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");
    
        @Getter
        public volatile int count = 100;
    
        public static void main(String[] args) {
    
            AtomicExample5 example5 = new AtomicExample5();
    
            if (updater.compareAndSet(example5, 100, 120)) {
                log.info("update success 1, {}", example5.getCount());
            }
    
            if (updater.compareAndSet(example5, 100, 120)) {
                log.info("update success 2, {}", example5.getCount());
            } else {
                log.info("update failed, {}", example5.getCount());
            }
        }
    }
    
    

    最后我们介绍一下使用AtomicBoolean来实现只执行一次的操作,我们使用private static AtomicBoolean isHappened = new AtomicBoolean(false)来初始化一个具有原子性的一个Boolean的记录是否已经被执行

    @Slf4j
    public class AtomicExample6 {
    
        private static AtomicBoolean isHappened = new AtomicBoolean(false);
    
        // 请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static void main(String[] args) throws Exception {
            ExecutorService executorService = Executors.newCachedThreadPool();
            final Semaphore semaphore = new Semaphore(threadTotal);
            final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
            for (int i = 0; i < clientTotal ; i++) {
                executorService.execute(() -> {
                    try {
                        semaphore.acquire();
                        test();
                        semaphore.release();
                    } catch (Exception e) {
                        log.error("exception", e);
                    }
                    countDownLatch.countDown();
                });
            }
            countDownLatch.await();
            executorService.shutdown();
            log.info("isHappened:{}", isHappened.get());
        }
    
        private static void test() {
            if (isHappened.compareAndSet(false, true)) {
                log.info("execute");
            }
        }
    }
    

    原子性 - 锁

    我们除了可以使用Atomic包还可以使用锁来实现。

    1. synchronize:依赖jvm
    • 修饰代码块:适用范围大括号括起来的代码,作用于调用的对象
    • 修饰方法:适用范围整个方法,作用于调用的对象
    • 修饰静态方法:适用范围整个静态方法,作用于所有对象
    • 修饰一个类:适用范围是括起来的部分,作用于所有对象
    1. Lock:依赖特殊的cpu指令、代码实现,ReentrantLock

    相关文章

      网友评论

          本文标题:线程安全性-原子性

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