Juc12_Volatile的可见性、不保证可见性、有序性、使用、内存屏障四大指令StoreStore、StoreLoad 、LoadLoad、LoadStore(三)

简介: ③. volatile特性①. 保证可见性②. 不保证原子性

③. volatile特性


①. 保证可见性


  • ①. 保证不同线程对这个变量进行操作时的可见性,即变量一旦改变所有线程立即可以看到


  • ②. 代码展示
    不加volatile,没有可见性,程序无法停止
    加了volatile,保证可见性,程序可以停止


/*
验证volatile的可见性:
1.加入int number=0; number变量之前没有添加volatile关键字修饰,没有可见性
2.添加了volatile,可以解决可见性问题
 * */
class Resource{
   //volatile int number=0;
   volatile int number=0;
   public void addNumber(){
       this.number=60;
   }
}
public class Volatile_demo1 {
   public static void main(String[] args) {
       Resource resource=new Resource();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t coming ");
            try {TimeUnit.SECONDS.sleep(4);}catch (InterruptedException e){e.printStackTrace();}
            resource.addNumber();
            System.out.println(Thread.currentThread().getName()+"\t update "+resource.number);
        },"线程A").start();
        //如果主线程访问resource.number==0,那么就一直进行循环
        while(resource.number==0){
        }
        //如果执行到了这里,证明main现在通过resource.number的值为60
       System.out.println(Thread.currentThread().getName()+"\t"+resource.number);
   }
}


③. 上述代码原理解释


没有添加volatile关键字,线程A对共享变量改变了以后(number=60),主线程(这里的线程B)访问number的值还是0,这就是不可见


添加volatile之后,线程A对共享数据进行了改变以后,那么main线程再次访问,number的值就是改变之后的number=60


微信图片_20220108124722.png


②. 不保证原子性


  • ①. 代码展示
    (我们对20个线程进行循环100次的操作)


public class Volatile_demo3 {
    public static void main(String[] args) {
       /* System.out.println(Thread.activeCount());*/
        AutoResource autoResource=new AutoResource();
        //20个线程每个线程循环100次
        for (int i = 1; i <=20; i++) {
            new Thread(()->{
                for (int j = 1; j <=100; j++) {
                    autoResource.numberPlusPlus();
                    autoResource.addAtomicInteger();
                }
            },String.valueOf(i)).start();
        }
        //需要等待上面20个线程都全部计算完后,再用main线程取得的最终的结果值是多少
        //默认有两个线程,一个main线程,二是后台gc线程
         while(Thread.activeCount()>2){
             Thread.yield();
         }
        System.out.println(Thread.currentThread().getName()+"\t int type"+autoResource.number);
        System.out.println(Thread.currentThread().getName()+"\t AutoInteger type"+autoResource.atomicInteger.get());
    }
}
class AutoResource{
    volatile int number=0;
    public void numberPlusPlus(){
        number++;
    }
    //使用AutoInteger保证原子性
    AtomicInteger atomicInteger=new AtomicInteger();
    public void addAtomicInteger(){
        atomicInteger.getAndIncrement();
    }
}


②. 对于一读一写操作,不会有数据问题

(假设主内存的共享变量number=1,需要对主内存的number++处理,对于两个线程t1、t2如果是一读一写的操作(不会有数据丢失的情况),某一时刻,t1抢到CPU的执行权,将共享数据1读回t1的工作内存,进行number++的操作,这个时候number=2,将2从工作内存写回到主内存中。写回后马上通知t2线程,将number=2读到t2的工作线程)


③. 对于两个写,会出现数据问题


(假设主内存的共享变量number=0,需要对主内存进行10次的number++处理,最终的结果就是10,对于两个线程t1、t2如果是两个写的操作(会造成数据丢失的情况),t1和t2将主内存的共享数据读取到各自的工作内存去,某一时刻,t1线程抢到CPU的执行权,进行

number++的处理,将工作内存中的number=1写回到主内存中,就在这一刻,t2也抢到CPU执行权,进行number++的处理,这个时候number++后的结果也等于1,t1将number=1写回到主内存中去,并通知t2线程,将主内存中的number=1读到t2的工作内存中去,这个时候对于t2,它之前也进行了一次number++的操作将会无效,回重新进行一次number++的操作。这也数据也就写丢了一次,那么10次number++后的结果也就不会等于10)


read-load-use 和 assign-store-write 成为了两个不可分割的原子操作,但是在use和assign之间依然有极小的一段真空期,有可能变量会被其他线程读取,导致写丢失一次.


微信图片_20220108124758.png




相关文章
|
1月前
|
编译器 C++
理解内存序,指令重排与内存模型
理解内存序,指令重排与内存模型
57 0
|
10天前
汇编语言(第四版) 实验一 查看CPU和内存,用机器指令和汇编指令编程
汇编语言(第四版) 实验一 查看CPU和内存,用机器指令和汇编指令编程
|
1月前
|
存储 程序员 数据处理
【汇编】mov和add指令、确定物理地址的方法、内存分段表示法
【汇编】mov和add指令、确定物理地址的方法、内存分段表示法
273 1
【汇编】mov和add指令、确定物理地址的方法、内存分段表示法
|
1月前
|
存储
【汇编】数据在哪里?有多长、div指令实现除法、dup设置内存空间
【汇编】数据在哪里?有多长、div指令实现除法、dup设置内存空间
|
10月前
|
缓存 安全 Java
volatile底层的实现原理:volatile关键字的作用、内存模型、JMM规范和CPU指令
volatile底层的实现原理:volatile关键字的作用、内存模型、JMM规范和CPU指令
130 0
|
11月前
持久内存指令(PMDK)简介
持久内存指令(PMDK)简介
272 0
|
11月前
|
存储 安全
4.3 x64dbg 搜索内存可利用指令
发现漏洞的第一步则是需要寻找到可利用的反汇编指令片段,在某些时候远程缓冲区溢出需要通过类似于`jmp esp`等特定的反汇编指令实现跳转功能,并以此来执行布置好的`ShellCode`恶意代码片段,`LyScript`插件则可以很好的完成对当前进程内存中特定函数的检索工作。在远程缓冲区溢出攻击中,攻击者也可以利用汇编指令`jmp esp`来实现对攻击代码的执行。该指令允许攻击者跳转到堆栈中的任意位置,并从那里执行恶意代码。
136 0
|
13天前
|
消息中间件 存储 Kafka
实时计算 Flink版产品使用问题之 从Kafka读取数据,并与两个仅在任务启动时读取一次的维度表进行内连接(inner join)时,如果没有匹配到的数据会被直接丢弃还是会被存储在内存中
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
6天前
|
存储 Java C++
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据,如局部变量和操作数;本地方法栈支持native方法;堆存放所有线程的对象实例,由垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息和常量;运行时常量池是方法区一部分,保存符号引用和常量;直接内存非JVM规范定义,手动管理,通过Buffer类使用。Java 8后,永久代被元空间取代,G1成为默认GC。
17 2
|
9天前
|
存储
数据在内存中的存储(2)
数据在内存中的存储(2)
22 5