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的幂,2,4,6,8,方便位运算 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;
-