文章目录
1:谈谈你对volatile的理解
volatile三大特性
volatile是Java虚拟机提供的轻量级的同步机制,有三大特性:保证可见性、不保证原子性、禁止指令重排。
volatile int num=0;
什么是可见性(由JMM引)?
JMM(Java内存模型),本身是一种抽象的概念并不真实存在,它描述的是一种规范或者规则,通过这组规范定义了程序中的各个变量的访问方式。
JMM要满足可见性、原子性、有序性。满足这三个即线程安全!
JMM关于同步的规定:
- 线程解锁前,必须把共享变量的值刷新回主内存
- 线程加锁前,必须读取主内存的最新值到自己的工作内存
- 加锁解锁是同一把锁
可见性?
Java规定所有变量都存在主内存当中,主内存是共享内存区域,线程对变量的操作只能在工作内存中操作,有三个线程想取出来主内存中的一个东西,第一步先将变量从主内存中拷贝到自己的工作空间,然后对变量进行操作,操作完成后再将变量写回主内存。而线程之间的通讯(传值)是需要通过主内存来进行的,不同的线程无法访问彼此的工作内存。
如果,三个线程同时取出,而线程1将变量更改了放了回去,要让其余线程知道其已经被更改。这就是可见性。
什么是不保证原子性?
原子性指的是?
不可分割、完整性、某个线程正在做某个业务时,中间不可以被加塞或者是分割,需要整体完成。
不保证原子性就会出现写丢失的现象
不保证原子性怎么理解?
假如三个线程,线程1、2是给值加1(加1的那个用volatile修饰了),那么运行后,主内存中的值应该是2,由于多线程竞争的调度关系,某一时间段,线程1、2从主内存获取到值都是0,各自在各自的工作空间加1后,往主内存写的时候,将会出现某一时间段1号线程刚要写这个1的时候被挂起了,线程2将1写入了进去,这时volatile通知别的线程,在没有反应过来的情况下,线程1将自己的那个1写入,将这个1覆盖了。
如何解决:
第一:添加synchronized关键字,不过这样有点杀鸡用牛刀
第二:使用juc下的AtomicInteger(接CAS)
什么是禁止指令重排?
执行源代码流程:
源代码–编译器优化的重排–指令并行的重排–内存系统的重排–最终执行
volatile实现了禁止指令重排,从而避免多线程环境下程序出现乱序执行的现象
Synchronized关键字
用来加锁
在哪些地方用到过volatile?
单例模式DCL代码(双端检索)机制不一定线程安全,原因是底层存在指令重排的可能,需要volatile可以禁止指令重排
2:CAS你知道吗?
CAS是什么?
CAS是一条CPU并发原语,功能是判断内存某个位置的值是否为预期值,如果是则改为新的值,这个过程是原子性的。(比较并交换)
假如在主物理内存存在一个5,有两个线程,线程1、2首先获取到主物理内存中的5,再带着获取到的这个值之后和主物理内存中的值进行对比,如果成功,则给主物理内存返回线程所修改的值。线程1,获取到的值和主物理内存中的值进行对比,一样后,就给主物理内存返回一个2019。线程2,获取到的值和主物理内存中的值对比,对比不上(5不等于2019),所以修改不了。这个对比修改就是CAS。
//代码示例
public static void main(String[] args){
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5,2019)+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5,2099)+atomicInteger.get());
}
//输出结果
true 2019
false 2019
CAS底层原理,谈谈你对UnSafe的理解?
CAS的底层思想:
CAS就是比较当前工作内存中的值和主内存中的值,如果相同就执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止
CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B
当且仅当预期值A与内存值V相同时,将内存值V修改为B,否则什么都不做,在进行自旋的比较,直到成功为止
UnSafe类:
之所以原子整型下不用添加Synchronized关键字依然保证原子性,就是因为UnSafe类。是CAS的核心类,相当于一个后门,基于该类可以直接操作特定内存的数据,Java中的CAS操作的执行依赖于UnSafe类的方法。
CAS缺点?
循环时间长,开销大
只能保证一个共享变量的原子操作(对于多个的,那就只能通过加锁了)
引出来ABA问题
3:原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?
ABA是什么?
有两个线程,线程1的执行时间是10秒,线程2的执行时间是2秒。线程1、2同时从主物理内存中取出来一个2放到自己的工作内存,线程2两秒后传入一个200,后线程2再过两秒,又将200改为2。就这样,线程1执行的时候,工作空间和主物理内存中的值虽然也是一样的,也可以执行。但这10秒中,那个值换了很多次。
尽管线程1操作时成功的,但不代表过程没有问题
AtomicReference原子引用
根据下面代码解释。刚开始其中的值是z3和22岁。但通过cas之后,其内的值就变成了li4和25岁。
这就是原子引用,先定义一个类,其内参数定义好。就比如,当其内是z3的时候,不但将z3换成li4,还将年龄改变。
class User{
String userName;
int age;
}
public class AtomicReferenceDemo{
public static void main(String[] args){
User z3=new User(z3,22);
User li4=new User(li4,25);
AtomicReference<User> atomicReference=new AtomicReference<>();
atomicReference.set(z3); //这就是说明其内的初始值是z3和22岁
System.out.println(atomicReference.compareAndSet(z3,li4)+atomicReference.get.toString());
}
}
AtomicStampedReference版本号原子引用
新增一种机制(版本号),类似于乐观锁
线程1、2从主物理内存中取东西时,加上版本号,每次修改主物理内存中的值时,不但要对比值是否一样,还要对比版本号是否相同。修改一次,版本号会加1,这样如果发生了ABA问题,即使其中的值一样,但版本号不一样。这样可以有效解决ABA问题。
解决ABA问题
加入版本号即可