说起并发的底层,不得不提volatile,CAS,AQS,本文就是揭露它们神秘的面纱
一.volatile
为了更好的理解volatile,我们需要知道以下几个概念
JMM (java内存模型)
关于不同的线程操作共享资源的步骤如下:
比如 i++
并发的三大特征: (按照上图解释)
有了前面的知识,我们来谈谈对volatile的理解?
关于避免指令重排?
如何解决volatile不保证原子性?
为啥不用synchronized?
开发中什么时候用到volatile?
适用场合?
值得说明的是:对 volatile 变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证 i这种操作的原子性,因为本质上 i是读、写两次操作。
对变量的写操作不依赖于当前值(比如 i++),或者说是单纯的变量赋值(boolean flag = true)
该变量没有包含在具有其他变量的不变式中,也就是说不同的 volatile 变量之间,不能互相依赖。 只有在状态真正独立于程序内其他内容时才能使用 volatile。
二.CAS(1)
CAS是一条并发原语,属于操作系统级别的命令。原语的执行必须是连续的,不允许中断,,也就是CAS是原子指令,不会造成数据不一致的问题。
全称为Compare And Swap,比较并交换。
它的功能是判断某个位置的值是否为预期值,如果是则改为新的值,这个过程是原子性的
(线程工作内存 读取 主内存值后,进行操作之后,准备写入主内存时,进行CAS判断,如果主内存中的值还是刚才复制之前的值,才刷新主内存的值,否则继续循环获得主内存的值)
CAS有三个操作数,分别是:
期望值:通过对象地址可以获取
真实值:通过对象地址,使用Unsafe类可以获取,写入主内存时的真实值
更新值:期望值==真实值时,更新数据
应用:原子性操作对象保证原子性就是使用CAS思想。
AtomicInteger atomicInteger = new AtomicInteger(1);
atomicInteger.getAndIncrement();//相当于 i++
上面代码追溯源码:
注意:
CAS的缺点:
简称:狸猫换太子
什么是ABA问题(ABA问题是怎么产生的)?
ABA问题解决方案?
先解释什么是原子引用
什么是时间戳原子引用?
public classABADemo {
static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static voidmain(String[] args) {
System.out.println("=====以下是ABA问题的产生=====");
new Thread(() ->{
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "Thread 1").start();
new Thread(() ->{
try{
//保证线程1完成一次ABA操作
TimeUnit.SECONDS.sleep(1);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" +atomicReference.get());
}, "Thread 2").start();
try{
TimeUnit.SECONDS.sleep(2);
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("=====以下时ABA问题的解决=====");
new Thread(() ->{
int stamp =atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号" +stamp);
try{
TimeUnit.SECONDS.sleep(2);
} catch(InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第2次版本号" +atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第3次版本号" +atomicStampedReference.getStamp());
}, "Thread 3").start();
new Thread(() ->{
int stamp =atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号" +stamp);
try{
TimeUnit.SECONDS.sleep(4);
} catch(InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改是否成功" + result + "\t当前最新实际版本号:" +atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName() + "\t当前最新实际值:" +atomicStampedReference.getReference());
}, "Thread 4").start();
}
}
** 三.AQS**
定义了一套多线程访问共享资源的同步器框架(就是一个抽象类),许多同步类实现都依赖于它, 如常用的ReentrantLock/Semaphore/CountDownLatch...。
volatile修饰的state标识状态+队列,至于线程的排队、等待、唤醒等,底层的AQS都已经实现好了,我们不用关心。
AQS定义两种资源共享方式
Exclusive (独占,只有一个线程能执行,如ReentrantLock)
Share (共享,多个线程可同时执行,如Semaphore/CountDownLatch)
源码讲解
Node:内部类,队列的节点,存放Thread。
state:0代表未锁定,N代表锁定了几个资源,比如CountDownLatch,每次-1,state-1.
获得锁:
acquire():独占锁获得锁的入口
tryAcquire():尝试获得资源,失败返回false,成功直接执行任务。不公平锁的体现
addWaiter(Node):没有获得资源,当前线程加入到等待队列的队尾,并返回当前线程所在的结点
enq(Node):上述方法没能成功,使用该方法直接放在队尾
acquireQueued(Node, int):进入等待状态休息,直到其他线程彻底释放资源后唤醒自己
释放锁:
release(int):释放资源的入口
tryRelease(int):尝试释放指定量的资源
unparkSuccessor(Node):唤醒等待队列中最前边的那个未放弃线程
共享模式下acquireShared()和releaseShared()与上述一样
寄语:一定要努力,堵住那悠悠众口
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://www.cnblogs.com/monkey-xuan/p/15864933.html
内容来源于网络,如有侵权,请联系作者删除!