zoukankan      html  css  js  c++  java
  • 2. 线程安全性

    本章内容:

      原子性:AtomicXXX、CAS原理、Unsafe、AtomicLong&LongAddr、AtomicReference&AtomicReferenceFieldUpdater、AtomicStampReference

          锁:synchronized(修饰代码块、方法、静态方法、类)、Lock

      可见性:synchronized、volatile(读、写、使用)

      有序性:happens-before原则

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

      

    【可见性】一个线程对主内存的修改可被其他线程看到。

    【有序性】一个线程观察其他线程的指令执行顺序,由于指令重排序的存在,该观察结果一般无序。


    一、原子性——java.util.concurrent.atomic包/CAS

      【原子性】提供了互斥访问,同一时刻只有一个线程对它操作。

    1.Atomic包

      ①原子更新基本数字类型:AtomicInteger、AtomicLong、AtomicBoolean;

      ②原子更新数组:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray;

      ③原子更新引用:AtomicReference、AtomicMarkableReference、AtomicStampedReference;

      ④原子更新属性:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater;

     1 public static AtomicInteger count = new AtomicInteger(1);
     2 
     3 
     4 public static Student[] value = new Student[]{new Student(1),new Student2,…};
     5 public static AtomicReferenceArray arr = new AtomicReferenceArray(value);
     6 
     7 private static AtomicIntegerFieldUpdater<Student> updater = AtomicIntegerFieldUpdater.newUpdater(Student.class, "age");
     8 public volatile int age= 20;
     9 //student1是Student一个实例
    10 updater.compareAndSet(studen1, 100, 120)

    2. CAS/Unsafe

      【Unsafe】Java无法直接访问底层操作系统,而是通过本地方法(native)来访问的。JVM开启一个后门,JDK有一个类Unsafe,它提供了硬件级别的原子操作。这个类中的方法都是public的,但是只有授信的代码才能获得该类的实例。

       【CAS】Compare and Swap:比较并交换。设计并发算法时常用的一门技术,JUC完全建立在CAS上,当前处理器都支持CAS。

      CAS方法有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值与内存值相同时,将内存值修改为B并返回true。

      举例:

      AtomicInteger.getAndIncrement():

      Unsafe.getAndAddInt():var1是当前AtomicInteger对象,var2是内存值V,var5是旧的预期值A,var5+var4是要修改的值,此处var4=1.只有当var2=var5时,才会赋予新值。

       Unsafa.compareAndSwapInt():

       【ABA问题】如果一个变量V初次读取的时候是A值,并且在准备复制的时候检查它任然是A值,但是处理过程中被其他线程修改过A-B-A,CAS操作仍然认为他没有修改过=》ABA问题。

      解决方案:带有标记的原子引用类AtomicStampedReference,通过控制变量的版本来保证原子性,但是比较鸡肋,如果使用传统的互斥同步可能会使原子类更加高效。

    3.AtomicLong && LongAddr

      Java8推荐LongAddr替代AtomicInteger。AtomicInteger在高并发场景下compareAndSwapInt会不断试错,有性能问题。 

      LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。将待处理的数据,通过hash计算分散到Cell数组中分别处理,最后在汇总计算。注意casBase   ==>   !casBase(b = base, b + x);表示CAS更新操作;如果一个线程去CAS失败,那么表示正在有一个线程正在CAS操作,表示竞争激烈;

       LongAddr在高并发的时候,Cell数组的汇总计算会出错。在高并发获取全局唯一ID时,使用AtomicLong而不是LongAddr。

    4. AtomicBoolean

      使用场景——使某段代码只执行一次。首先我们要知道compareAndSet的作用,判断对象当时内部值是否为第一个参数,如果是则更新为第二个参数,且返回ture,否则返回false。那么默认初始化为false,则一个线程把他变为ture,compareAndSet返回ture,进入方法体执行逻辑,那么其他的任何线程进入该方法执行compareAndSet时第一个参数为false,而对象的内部值已经被修改为true,则永远过不了if。

    private static AtomicBoolean isHappened = new AtomicBoolean(false);
    
    private static void test() {
            if (isHappened.compareAndSet(false, true)) {//只执行一次
                log.info("execute");
            }
    }

    5. 锁

     【synchronized关键字】synchronized修饰的变量是不能被继承的,父类是synchronized,子类必须显式写出来,否则不是同步方法。

       【作用范围】修饰代码块、修饰静态方法、修饰非静态方法、修饰一个类。

       【原理】基于Monitor实现的。synchronized获取对象锁保证在执行共享数据的线程是互斥的,可以使用wait、notify、notifyAll进行线程协同工作。Class和Object都关联了一个Monitor。提供了两个高效的字节码指令monitorentermonitorexit实现同步代码块。同步加锁的是对象而不是代码。当一个线程访问Object的一个synchronized(this)同步代码块时,其他线程对Objecet中其他synchronized(this)同步代码块的访问将会被阻塞,只能访问非synchronized代码块。

     【锁-Lock】锁标记存放在对象头中的Mark Word中。

     【synchronized和Lock的区别

        ①Lock是一个接口,synchronized是一个关键字,由Java内置语言实现的。

        ②synchronized发生异常时,会自动释放线程占有的锁(独占锁,也是悲观锁),因此不会有死锁现象发生。Lock发生异常时,必须通过unLock()去释放锁(乐观锁),否则会产生死锁现象。通常将unLock放在finally中去释放锁。

        ③Lock会让等待的锁中断,而synchronized不会,因此会产生阻塞现象。

        ④通过Lock可以知道有么有成功获取锁,而synchronized不行。

        ⑤Lock可提高多线程进行读操作的效率。

    二、可见性

      【可见性】一个线程对主存的修改可被其他线程看到、

    1.导致共享变量不可见的原因

      ①线程交叉执行;

      ②重排序结合线程交叉执行;

      ③共享变量更新后没有及时地在工作内存和主内存之间更新;

    2.可见性-synchronized

      JMM关于synchronized的规定:

      ①线程解锁前,必须将共享变量的最新值刷回主存;(解锁-刷)

      ②线程加锁时,将清空工作内存中共享变量值,从而使用共享变量时需要从主内存重新读取最新的值。(加锁-取)

    3.可见性-volatile

      通过加入内存屏障禁止重排序优化来实现。

      ①对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量刷回主存中。

      ②对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主存中读取共享变量。

       volatile不能保证原子性:

    /*
    1.count       线程1,2读取最新值 count=1
    2.count++     线程1,2同时增加1  count=2
    3.count       线程1,2将count写回主存,少加一次1    count=1 正确结果应该是2;根本原因:count++不是一个原子性操作
    可以通过Atomic包实现原子性操作
    */ volatile int count = 1; count++;

      ③应用场景:适合用于修饰状态标记量

       使用条件:对变量的写操作不依赖于变量的当前值,或者确保只有单个线程更新变量值;该变量没有包含在具有其他变量的不变式中。

      ④volatile与synchronized区别

      volatile synchronized
    作用范围      变量   变量、方法、类
    保证可见性
    保证原子性 ×
    是否会造成阻塞 ×
    是都可被编译器优化 ×

     三、有序性

      【有序性】程序执行的顺序按照代码的顺序执行

      【重排序】见上章

      【happens-before规则】见上章

  • 相关阅读:
    简单的HelloWorld
    jsp获取绝对路径
    EasyUI validType属性
    Django meida(admin后台上传图片并可访问)
    postgresql char 与 varchar的区别
    git pull 源成分支遇到“There is no tracking information for the current branch.”错误
    Centos安装Pillow模块出错解决办法
    centos7网络配置
    表格排序插件tablesorter的初步使用介绍
    linux编译安装指定版本的python
  • 原文地址:https://www.cnblogs.com/qmillet/p/12081439.html
Copyright © 2011-2022 走看看