zoukankan      html  css  js  c++  java
  • java并发编程笔记(三)——线程安全性

    java并发编程笔记(三)——线程安全性

    线程安全性:

    ​ 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

    线程安全体现在三个方面:

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

    原子性:Atomic包

    使用AtomicInteger保证该变量操作的原子性

    public class CountExample2 {
    
        // 请求总数
        public static int clientTotal = 5000;
    
        // 同时并发执行的线程数
        public static int threadTotal = 200;
    
        public static AtomicInteger count = new AtomicInteger(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();  //相当于++x;
            // count.getAndIncrement();    //相当于x++
        }
    }
    

    原理:AtomicInteger的incrementAndGet()方法里边用到了一个unsafe的类

    public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }
    

    继续深入点进去看getAndAddInt的实现:

    //
    public final int getAndAddInt(Object var1, long var2, int var4) {
            int var5;
            do {
                var5 = this.getIntVolatile(var1, var2);
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    
            return var5;
        }
    

    这里最重要的一个方法是:compareAndSwapInt(),这是java底层的一个方法,它不是通过java实现的:

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    

    参数解释:

    Object var1:所操作的对象,比如本次案例中,这个Obect是AtomicInteger count;

    long var2:这个对象当前的值;

    int var4:当前对象要增加的值,比如本次案例中做+1操作,那么var4就是1;

    int var5:调用底层得到的一个值,如果没有其他线程过来操作,这个值应该是等于var2

    getAndAddInt()方法中compareAndSwapInt()方法执行解释:如果对于var1这个对象,如果var2与从底层获取的值var5是相同的,那么就执行var5 + var4;

    进一步解释:count的当前值,是当前线程中的值,属于线程中的工作内存中的值,而底层获取的值是主存中值,只有当工作内存中的值和主存中的值是一致的时候,才可以修改。

    AtomicLong、LongAdder

    在上边的例子中,把AtomicInteger 替换成AtomicLong,整个方法依然是线程安全的。

    第二种方式是使用LongAdder:

    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);
            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();
        }
    }
    

    AtomicLong和LongAdder的对比:

    AtomicLong:该类底层实现是在一个死循环内,不断的尝试修改目标值,直到修改成功,在竞争不激烈的情况下,修改成功概率很大,在竞争激烈情况下修改失败的概率较大,这种情况下会有损性能。

    LongAdder:由于Long、Double类型的值JVM允许将他们64位的读写操作分拆成32位的读写操作,根据此原理 LongAdder将操作的数值分拆成数组,然后最终得到的是数组的加和,通过分拆均衡操作压力,因此其性能相对较好

    使用场景的选择:在高并发计数的情景下优先使用LongAdder,其他情况使用AtomicLong

    **AtomicBoolean **

    底层实现的方法是:

    public final boolean compareAndSet(boolean expect, boolean update) {
            int e = expect ? 1 : 0;
            int u = update ? 1 : 0;
            return unsafe.compareAndSwapInt(this, valueOffset, e, u);
        }
    

    这个方法是指某个代码块逻辑值执行一次。

    使用案例(该案例演示了某一段代码在多线程情况下,只执行了一次):

    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");
            }
        }
    }
    

    AtomicReference、AtomicReferenceFieldUpdater

    AtomicReference使用示例:

    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());   //4
        }
    }
    

    AtomicReferenceFieldUpdater使用示例:

    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());
            }
        }
    }
    

    AtomicStampedReference:解决CAS的ABA问题

    ABA问题:在CAS操作的时候,其他线程将变量的值A改成了B,但是又改回了A,本线程使用期望值A与当前变量进行比较的时候,发现变量A没有变,于是CAS将A值进行了交换操作。

    解决思路:每次变量更新的时候,把版本号+1

    核心类:

    AtomicStampedReference

    其中的核心方法:compareAndSet()

    public boolean compareAndSet(V   expectedReference,
                                     V   newReference,
                                     int expectedStamp,
                                     int newStamp) {
            Pair<V> current = pair;
            return
                expectedReference == current.reference &&
                expectedStamp == current.stamp &&
                ((newReference == current.reference &&
                  newStamp == current.stamp) ||
                 casPair(current, Pair.of(newReference, newStamp)));
        }
    

    AtomicLongArray

    这个类维护的是一个数组

    这个类与AtomicLong比较,方法 里多了一个索引值让我们指定。

    原子性——锁

    • synchronized:依赖JVM去实现锁
    • Lock:依赖特殊的cpu指令,代码实现,ReenteantLock

    synchronized:

    • 修饰代码块:大括号括起来的代码,作用于调用的对象

    • 修饰方法:整个方法,作用于调用的对象

    • 修饰静态方法:整个静态方法,作用于所有对象

    • 修饰类,括号括起来的部分,作用于所有对象

    原子性——对比

    synchronized:不可中断锁,适合竞争不激烈,可读性好

    Lock:可中断锁,多样化同步,竞争激烈时能维持常态

    Atomic:竞争激烈时能维持常态,比Lock性能好;只能同步一个值

    可见性

    导致共享变量在线程间不可见的原因

    • 线程交叉执行
    • 重排序结合线程交叉执行
    • 共享变量更新后的值没有在工作内存与主内存间及时更新

    可见性——synchronized

    JMM关于synchronized的两条规定:

    • 线程解锁前,必须把共享变量的最新值刷新到主内存
    • 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁是同一把锁)

    可见性——volatile

    通过假如内存屏障和禁止重排序优化来实现

    • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内
    • 对volatileb变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量

    volatile关键字不具有原子性

    适合的场景:

    • 对变量的写操作不依赖与当前值;
    • 该变量没有包含在具有其他变量的不变式中。

    因此volatile特别适合状态标记量

    有序性

    java内存模型中,允许编辑器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

    通常情况下可以通过以下三个关键字来保证有序性:

    • volatile
    • synchronized
    • Lock

    happens-before原则

    如果两个操作的执行次序无法从happens-before原则推导出来,那么就不能保证他们的有序性,虚拟机就可以对他们随意的进行重排序。

    也就是除了下面这些规则规定的场景,其他场景,虚拟机可以对其进行重排序。

    • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作

    • 锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作

    • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作

    • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C

    • 线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作

    • 线程中断原则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生。

    • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行。

    • 对象终结规则:一个对象的初始化完成先行发生于它的finalize()方法的开始

  • 相关阅读:
    C# 编写一个控制台应用程序,输入三角形或者长方形边长,计算其周长和面积并输出
    nopCommerce 3.9 大波浪系列 之 使用部署在Docker中的Redis缓存主从服务
    Docker 学习笔记
    nopCommerce 3.9 大波浪系列 之 微信公众平台登录插件
    nopCommerce 3.9 大波浪系列 之 可退款的支付宝插件(下)
    nopCommerce 3.9 大波浪系列 之 可退款的支付宝插件(上)
    nopCommerce 3.9 接口笔记
    nopCommerce 3.9 大波浪系列 之 开发支持多店的插件
    nopCommerce 3.9 大波浪系列 之 网页加载Widgets插件原理
    nopCommerce 3.9 大波浪系列 之 事件机制(生产者、消费者)
  • 原文地址:https://www.cnblogs.com/xujie09/p/11694133.html
Copyright © 2011-2022 走看看