JUC并发编程(二)

95 阅读8分钟

volatile与JMM

  • volatile特点

    • 有序性
    • 可见性
  • volatile内存语义

    • 当写一个volatile变量时,JMM会把该线程对应的本地内存的共享变量值==立即==刷新回主内存中
    • 当读一个volatile变量时,JMM会把该线程对应的本地内存的共享变量值设置为无效,重新回到主内存中读取最新的共享变量
  • volatile四大内存屏障

    • 写屏障(Store Memory Barrier):在写指令之前插入写屏障,强制把缓存区中的数据刷新回到主内存中
    • 读屏障(Load Memory Barrier):在读指令之前插入读屏障,让工作内存或者CPU告诉缓存当中的缓存数据全部失效,重新回到主内存获取最新数据
    • 在这里插入图片描述
  • volatile变量的读写过程

    • 在这里插入图片描述

    • read:作用于主内存中,将变量的值从主内存传输到工作内存中,主内存到工作内存

    • load:作用于工作内存,将read从主内存传输的变量放入工作内存变量副本中,即数据加载

    • use:作用于工作内存,将工作内存的变量的值传输给执行引擎,每当JVM遇到需要该变量的字节码指令时会执行该操作

    • assign:作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM遇到需要该变量的字节码指令时会执行该操作

    • store:作用于工作内存,将工作内存中赋值完毕的工作内存变量写回给主内存中

    • write:作用于主内存,将store传输过来的变量值赋值给主内存中的变量

    • lock:作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程

    • unlock:作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用

CAS

概念及原理

  • compare and swap的缩写,中文翻译成比较和交换,实现并发算法时常用到的一种技术,它包含三个操作数——内存中的 值V、预期原值A及更新值B
  • 执行CAS的时候将==内存地址==的值与==预期原值==进行比较,如果相匹配,那么处理器会自动将该位置值更新为新值,如果不匹配,处理器不会做任何操作或者重试——自旋,多个线程同时执行CAS操作,只有一个会成功
  • 是通过硬件来保证原子性的

缺点

  • 循环时间长,开销很大
  • 引起ABA问题
    • 比如说一个线程1从内存位置V中取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作变成了B,然后线程2又将V位置的数据变回了A,这时候线程1进行CAS操作发现内存中仍然是A,预期OK,然后线程1操作成功。尽管线程1操作成功了,但是不代表这个过程是没有任何问题的
    • 解决
      • 版本号时间戳原子引用AtomicStampedReference

UnSafe类

  • UnSafe
    • 它是CAS的核心类,由于java的方法无法直接访问底层系统,需要通过native方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存中的数据,Unsafe类存在于sun.msic包下,其内部方法操作可以像C的指针一样直接操作内存,因为java中CAS操作的执行依赖于Unsafe类的方法

自旋锁

  • CAS是自旋锁的基础,是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试锁(底层就是采用do while循环实现,看Unsafe源码358行),当线程发现锁被占用时,会不断的判断锁的状态,直到获取,这样的好处是减少线程的上下文切换的消耗,缺点是循环会消耗CPU

  • public class SpinLockDemo {
    
        AtomicReference<Thread> atomicReference = new AtomicReference<>();
    
        public void lock() {
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + "---------->come in");
            while (!atomicReference.compareAndSet(null, thread)) {
    
            }
        }
    
        public void unLock() {
            Thread thread = Thread.currentThread();
            atomicReference.compareAndSet(thread, null);
            System.out.println(Thread.currentThread().getName() + "---------->task over, unlock ----");
        }
    
        public static void main(String[] args) {
            SpinLockDemo spinLockDemo = new SpinLockDemo();
            new Thread(() -> {
                spinLockDemo.lock();
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                spinLockDemo.unLock();
            }, "t1").start();
    
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
    
            new Thread(() -> {
                spinLockDemo.lock();
                spinLockDemo.unLock();
            }, "t2").start();
        }
    }
    
    //运行结果
    t1---------->come in
    t2---------->come in
    t1---------->task over, unlock ----
    t2---------->task over, unlock ----
    从结果可以看出,t1线程不释放锁,t2线程一直在循环等待
    

原子操作类

基本类型原子类

  • AtomicInteger

  • AtomicBoolean

  • AtomicLong

  • public class AtomicIntegerDemo {
        public static final int SIZE = 50;
        public static void main(String[] args) throws InterruptedException {
    
            MyNumber myNumber = new MyNumber();
            CountDownLatch countDownLatch = new CountDownLatch(SIZE);
    
            for (int i = 1; i <= SIZE; i++) {
                new Thread(()->{
                    try{
                        for (int i1 = 1; i1 <= 100; i1++) {
                            myNumber.addPlusPlus();
                        }
                    }finally {
                        countDownLatch.countDown();
                    }
    
                },String.valueOf(i)).start();
            }
    
            /*try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/
    
            countDownLatch.await();
    
            System.out.println(Thread.currentThread().getName()+"\t"+"result:"+myNumber.atomicInteger.get());
        }
    }
    
    
    class MyNumber{
        AtomicInteger atomicInteger = new AtomicInteger();
    
        public void addPlusPlus() {
            atomicInteger.getAndIncrement();
        }
    }
    

数据类型原子类

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

引用类型原子类

  • AtomicReference

  • AtomicStampedReference

    • 携带版本号的引用原子类,可以解决ABA问题,解决修改过几次
  • AtomicMarkableReference

    • 原子更新带有标记为的引用类型对象

    • 它的定义就是将状态戳转换为True|false

    • 只要被修改过,后续再去CAS的话,会产生无效

    • public class AtomicMarkableReferenceDemo {
          static AtomicMarkableReference markableReference = new AtomicMarkableReference(100, false);
      
          public static void main(String[] args) {
              new Thread(() -> {
                  boolean marked = markableReference.isMarked();
                  System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);
                  try {
                      TimeUnit.SECONDS.sleep(1);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
      
                  markableReference.compareAndSet(100, 1000, marked, !marked);
              }, "t1").start();
      
      
              new Thread(() -> {
                  boolean marked = markableReference.isMarked();
                  System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);
                  try {
                      TimeUnit.SECONDS.sleep(1);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
      
                  boolean b = markableReference.compareAndSet(100, 2000, marked, !marked);
                  System.out.println(Thread.currentThread().getName()+"\t"+b);
                  System.out.println(Thread.currentThread().getName()+"\t"+markableReference.isMarked());
                  System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference());
      
              }, "t2").start();
          }
      }
      //运行结果
      t1	默认标识:false
      t2	默认标识:false
      t2	false
      t2	true
      t2	1000
      

对象的属性修改

  • AtomicIntegerFieldUpdater

  • public class AtomicIntegerFieldUpdaterDemo {
        public static void main(String[] args) throws InterruptedException {
            BankAccount bankAccount = new BankAccount();
            CountDownLatch countDownLatch = new CountDownLatch(10);
            for (int i = 0; i < 10; i++) {
                new Thread(() -> {
                    try {
                        bankAccount.transferMoney(bankAccount);
                    } finally {
                        countDownLatch.countDown();
                    }
                }).start();
            }
            countDownLatch.await();
    
            System.out.println(Thread.currentThread().getName() + "\t" + "result:" + bankAccount.money);
        }
    }
    
    
    class BankAccount {
        String bankName = "CCB";
    
        public volatile int money = 0;//钱数
    
        public synchronized void add() {
            money++;
        }
    
        AtomicIntegerFieldUpdater<BankAccount> fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class, "money");
    
    
        public void transferMoney(BankAccount bankAccount) {
            fieldUpdater.getAndIncrement(bankAccount);
        }
    
    }
    
  • AtomicLongFieldUpdater

  • AtomicReferenceFieldUpdater

  • 以一种线程安全的方式操作非线程安全对象内的某些字段

  • 更新的对象属性必须是public volatile修饰符

原子操作增强类

  • LongAdder

    • 只能用来计算加法,且从0开始
    • 为什么这么快
      • LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同的线程会命中到不同的槽中,各个线程只对自己的槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就会少很多,如果要获取真正的long值,只要将各个槽中的变量值累加返回。

      • sum方法会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前的AtomicLong一个value的更新压力分散到多个value中,从而降级更新热点

      • LongAdder在无竞争的情况下,跟AtomicLong一样,对==同一个base==进行操作,当出现竞争关系时则采用==化整为零分散热点==的做法,空间换时间,用一个数组cesss,将一个value拆分金这个数组cesss,多个线程需要同时对value进行操作时,可以对线程id进行hash得到hash值,再根据hash值映射到cells数组的下标上,在对该下标所对应的值进行自增操作,当所有线程执行完毕,将cells数组的所有值和base值都加起来作为最终结果

      • 在这里插入图片描述

      • Striped64类中有一个base变量,还有一个Cell[]数组

        • base变量:低并发,直接累加到该变量上
        • Cell[]数组:高并发,累加到各个线程自己的槽Cell[i]中
    • 源码解析LongAdder.add方法
      • 在这里插入图片描述

      • 如果Cells表为空,尝试使用CAS更新base字段,成功则退出

      • 如果Cells表为空,CAS更新base值失败,出现竞争,uncontended为true,调用longAccumulate

      • 如果Cells表不为空,则当前线程映射的槽为空,uncontended为true,调用longAccumulate

      • 如果Cells表不为空,且当前线程映射的槽不为空,CAS更新cell的值,成功则返回,否则uncontended设为false,调用longAccumulate

  • LongAccumulator

    • 提供了自定义的函数操作
  • public class LongAdderDemo {
        public static void main(String[] args) {
            LongAdder longAdder = new LongAdder();
    
            longAdder.increment();
            longAdder.increment();
            longAdder.increment();
    
            System.out.println(longAdder.sum());
    
            LongAccumulator longAccumulator = new LongAccumulator((x,y)->x+y,1);
    
            longAccumulator.accumulate(1);
            longAccumulator.accumulate(3);
    
            System.out.println(longAccumulator.get());
        }
    
    
    }
    
  • Striped64类

    • /** Number of CPUS, to place bound on table size */	CPU数量,即Cells数组的最大长度
      static final int NCPU = Runtime.getRuntime().availableProcessors();
      
      /**
       * Table of cells. When non-null, size is a power of 2. 
       */Cells数组,为2的幂,2468,方便位运算
      transient volatile Cell[] cells;
      
      /**
       * Base value, used mainly when there is no contention, but also as
       * a fallback during table initialization races. Updated via CAS.
       */基础value值,当并发比较低时,只需要累加该值,用于没有竞争的情况,通过CAS更新
      transient volatile long base;
      
      /**
       * Spinlock (locked via CAS) used when resizing and/or creating Cells.
       */创建或者扩容Cells数组的时候使用自旋锁变量调整单元格大小,创建单元格时使用的锁
      transient volatile int cellsBusy;