首页 > 其他 > 详细

JUC与Volatile

时间:2021-04-03 09:43:01      阅读:12      评论:0      收藏:0      [点我收藏+]

JMM

JMM:Java内存模型,不存在的东西,概念!约定!

关于JMM的一些同步的约定:

1、线程解锁前,必须把共享变量立刻刷回主存

2、线程加锁前,必须读取主存的最新值到工作内存中!

3、加锁和解锁是同一把锁

线程 工作内存主内存

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于 double 和 long 类型的变量来说,load、store、read 和 write 操作在某些平台上允许例外)

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态

  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定

  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用

  • load (载入):作用于工作内存的变量,它把 read 操作从主存中变量放入工作内存中

  • use(使用): 作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令

  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中

  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的 write 使用

  • write (写入):作用于主内存中的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

  • 不允许 read 和 load、store 和 write 操作之一单独出现。即使用了 read 必须 load,使用了 store 必须 write

  • 不允许线程丢弃他最近的 assign 操作,即工作变量的数据改变了之后,必须告知主存

  • 不允许一个线程将没有 assign 的数据从工作内存同步回主内存

  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个末被初始化的变量。就是怼变量实施 use、store 操作之前,必须经过 assign 和 load 操作

  • 一个变量同一时间只有一个线程能对其进行 lock。多次 lock 后,必须执行相同次数的 unlock 才能解锁

  • 如果对一个变量进行 lock 操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新 load 或 assign 操作初始化变量的值

  • 如果一个变量没有被 lock,就不能对其进行 unlock 操作。也不能 unlock 一个被其他线程锁住的变量

  • 对一个变量进行 unlock 操作之前,必须把此变量同步回主内存

Volatile

1、保证可见性

public class JMMDemo {
//不加 volatile 程序就会死循环
//加 volatile 可以保证可见性
private volatile static int num = 0;
public static void main(String[] args) {//main


new Thread(()->{
while (num==0){//线程1 对主内存的变化是不知道的

}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}

2、不保证原子性

原子性:不可分割

线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败

// volatile 不保证原子性
public class VDemo02 {
// volatile 不保证原子性
// private volatile static int num = 0;
// public static void add(){
// num++;//不是一个原子性操作
// }
//AtomicInteger 原子类
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
num.getAndIncrement();//AtomicInteger +1 方法 CAS
}

public static void main(String[] args) {
//理论上num结果应该为2万
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}

这些类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!

什么是 指令重排:你写的程序,计算机并不是按照你写的那样去执行的

源代码-->编译器优化的重排-->指令并行也可能会重排-->内存系统也会重排-->执行

处理器在进行指令重排的时候,考虑:数据之间的依赖性!

volatile可以避免指令重排

内存屏障。CPU指令。作用:

1、保证特定的操作的执行顺序!

2、可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)

Volatile 是可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

单例模式

//饿汉式单例
public class Hungry {
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
//懒汉式单例
//道高一尺,魔高一丈!
public class LazyMan {
private static boolean qinjiang = false;
private LazyMan(){

synchronized (LazyMan.class){
if (qinjiang == false){
qinjiang = true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
//System.out.println(Thread.currentThread().getName() + "OK");
}
private static LazyMan lazyMan;
//双重检测锁模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan = new LazyMan();//不是一个原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
// LazyMan instance = LazyMan.getInstance();
Field qinjiang = LazyMan.class.getDeclaredField("qinjiang");
qinjiang.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance = declaredConstructor.newInstance();
qinjiang.set(instance,false);
LazyMan instance1 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 123
* 132 A
* B//此时lazyMan还没有完成构造
*/
//静态内部类   不安全的
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
//enum 是一个什么? 本身也是一个Class类  枚举
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
//NoSuchMethodException: com.liu.single.EnumSingle.<init>()//没有空参构造器
System.out.println(instance1);
System.out.println(instance2);
}
}

cmd反编译源码 jad -sjava +类名 

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingle.java
package com.lin.single;
public final class EnumSingle extends Enum {
public static EnumSingle[] values() {
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name) {
return (EnumSingle)Enum.valueOf(com/lin/single/EnumSingle, name);
}
private EnumSingle(String s, int i) {
super(s, i);
}
public EnumSingle getInstance() {
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static {
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}

CAS

CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!

缺点:

1、循环会耗时

2、一次性只能保证一个共享变量的原子性

3、ABA问题

public class CASDemo {
//CAS compareAndSet : 比较并交换!
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//对于我们平时写的SQL:乐观锁!
//期望、更新
//public final boolean compareAndSet(int expect, int update)
//如果我期望的值达到了,那么就更新,否则,就不更新,CAS是CPU的并发原语!
//==============捣乱的线程================
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
//==============期望的线程================
System.out.println(atomicInteger.compareAndSet(2020, 6666));
System.out.println(atomicInteger.get());
}
}

原子引用

解决ABA问题,引入原子引用!对应思想,乐观锁!

带版本号的原子操作!

注意:

Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法 valueOf获取对象实例,而不是new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间

public class CASDemo {
//CAS compareAndSet : 比较并交换!
//int Integer 包装类-128~127 AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题
//正常在业务操作,这里面比较的都是一个个对象
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
public static void main(String[] args) {
// AtomicInteger atomicInteger = new AtomicInteger(2020);
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("a1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Version + 1
System.out.println(atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a2=>" + atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3=>" + atomicStampedReference.getStamp());
},"a").start();
//乐观锁原理相同
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("b1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 6,
stamp, stamp + 1));
System.out.println("b2=>" + atomicStampedReference.getStamp());
},"a").start();
}
}

JUC与Volatile

原文:https://www.cnblogs.com/linhtx212318293/p/14598765.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!