1、JVM
请谈谈你对Volatile的理解
Volatile 是 java虚拟机提供轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
什么是JVM
JVM:java内存模型,不存在的东西,概念!约定!
关于JVM的一些同步约定:
- 线程解锁前,必须读取主存中的最新值到工作内存中
- 线程解锁前,必须把共享变量立刻刷回主存
- 加锁和解锁是同一把锁
线程 主内存 工作内存
关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下8种操作来完成:
- lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
- unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
- write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:
- 如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按- 顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
- 不允许read和load、store和write操作之一单独出现
- 不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
- 一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
- 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
- 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。
2、Volatile
保证可见性
public class JMMDemo {
private static volatile int num = 0;
public static void main(String[] args) {
new Thread(()->{
while(num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//需要让上面的线程知道num的值已经为1
num=1;
System.out.println(num);
}
}
2、不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰不能被分割,同时成功或同时失败。
//不保证原子性
//不保证原子性
public class VDemo02 {
//volatile 不保证原子性
//原子类AtomicInteger
private static AtomicInteger num=new AtomicInteger(0);
public static void add(){
// num++; //不是原子性操作
num.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2){
Thread.yield();
}
//理论上结果为两万
System.out.println(num);
}
}
加Volatile结果一直小于两万
不加lock 和 synchronize 怎么保证原子性,使用原子类
java.util.concurrent.atomic
Class AtomicInteger
3、指令重排
什么是指令重排:你写的程序,计算机并不是按照你写的那样执行的
源代码–>编译器优化的重排,–>指令并行也可能重排—>内存系统也会重排,–>执行
处理器会考虑数据之间的依赖性
a b x y 默认值都是0
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常结果:x=0,y=0
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
指令重排导致的结果:x=2 y=1
volatiile:可以避免指令重排
内存屏障:CPU指令,作用:
- 保证特定的操作的执行顺序
- 可以保证某些变量的内存可见性(利用这些特性volatile 实现了可见性)
Volatile 是可以保持 可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生
3、彻底玩转单例模式
饿汉式,DCL懒汉式
//懒汉式单例
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+" ok");
synchronized (LazyMan.class){
if(lazyMan!=null){
throw new RuntimeException("不要使用反射");
}
}
}
private volatile static LazyMan lazyMan;
//双重检查锁模式 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
//不是原子性操作,所以LazyMan前面要加volatile
lazyMan=new LazyMan();
}
}
}
return lazyMan;
//单线程下没有问题
}
public static void main(String[] args) throws Exception {
// for (int i = 0; i < 10; i++) {
// new Thread(()->{
// LazyMan.getInstance();
// }).start();
// }
//反射
// LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
LazyMan lazyMan = declaredConstructor.newInstance();
LazyMan lazyMan2 = declaredConstructor.newInstance();
// System.out.println(instance);
System.out.println(lazyMan);
System.out.println(lazyMan2);
}
}
//enum 本身也是一个class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
EnumSingle instance = EnumSingle.INSTANCE;
// Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
/**
* Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
* at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
* at com.single.Test.main(EnumSingle.java:20)
*/
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance();
System.out.println(enumSingle);
}
}
4、深入理解CAS
什么是CAS
CAS:
自旋锁:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
CAS:比较当前工作内存中的中和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
CAS:ABA问题(狸猫换太子)
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//CAS 比较并交换
//如果是我的期望值,就更新,否则不更新
System.out.println(atomicInteger.compareAndSet(2020,2022));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2022,2020));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020,2021));
System.out.println(atomicInteger.get());
}
}
true
2022
true
2020
true
2021
乐观锁可以检查出中间是否有过改动
如何解决
原子引用
5、原子引用
解决ABA问题
public class CASDemo {
public static void main(String[] args) {
//原子引用
//注意,如果泛型是一个包装类,注意对象的引用
// Integer -128 ~ 127 之间的是缓存中的,会复用Integer对象,如果超过就不会复用
//由于底层用的是 == 比较 ,所以要复用对象才行,所以要在该范围之内
AtomicStampedReference atomicStampedReference = new AtomicStampedReference<Integer>(20,1);
new Thread(()->{
//版本号
int stamp = atomicStampedReference.getStamp();
System.out.println("a => "+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(20, 22, stamp, stamp + 1));
stamp = atomicStampedReference.getStamp();
System.out.println("a => "+stamp);
System.out.println(atomicStampedReference.compareAndSet(22, 20,stamp, stamp + 1));
stamp = atomicStampedReference.getStamp();
System.out.println("a => "+stamp);
System.out.println("=== 狸猫换太子 ===");
},"a").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("b => "+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(20, 66, stamp, stamp + 1));
System.out.println("b => "+atomicStampedReference.getStamp());
},"b").start();
}
}
6、各种锁的理解
1、公平锁。非公平锁
非公平锁:(默认)
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2、可重入锁
递归锁
synchronize 版:
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+" sms");
call();
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName()+" call");
}
}
lock 版:
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone2{
Lock lock = new ReentrantLock();
public void sms(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+" sms");
call();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+" call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3、自旋锁
原子类中的自旋锁:
AtomicInteger . getAndIncrement();
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
unsafe类:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
//自旋锁
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
7、死锁
public class DeadLockDemo {
public static void main(String[] args) {
String lockA="A";
String lockB="B";
new Thread(new MyThread(lockA, lockB),"T1").start();
new Thread(new MyThread(lockB, lockA),"T2").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+" "+lockA+" get "+lockB);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+" "+lockB+" get "+lockA);
}
}
}
}