Java并发编程原理与实战十二:深入理解volatile原理与使用

Wesley13
• 阅读 576

volatile:称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的。

可见:一个线程修改了这个变量的值,在另一个线程中能够读取到这个修改后的值。

synchronized除了线程之间互斥之外,还有一个非常大的作用,就是保证可见性。以下demo即保证a值的可见性。

首先来看demo:

Java并发编程原理与实战十二:深入理解volatile原理与使用

package com.roocon.thread.t7;public class Demo {    private int a = 1;    public int getA() {        return a;    }    public void setA(int a) {        try {            Thread.sleep(200);        } catch (InterruptedException e) {            e.printStackTrace();        }        this.a = a;    }    public static void main(String[] args) {        Demo demo = new Demo();        new Thread(new Runnable() {            @Override            public void run() {               demo.setA(10);            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                System.out.println(demo.getA());            }        }).start();        try {            Thread.sleep(500);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("最终结果:" + demo.getA());    }}

Java并发编程原理与实战十二:深入理解volatile原理与使用

运行结果:

1
最终结果:10

解释:线程1执行set操作,但是,线程2可能在线程1执行set操作成功之前就进行了get操作,这样,get得到的仍然是修改之前的a值

那么,如何保证线程1在执行set操作时,其他线程得到的a值是线程1修改后的值呢?采用同步锁可以实现效果。同步方法上锁的是同一个实例,因此,在执行set方法前,线程1获取了实例锁,那么,其他线程在执行get方法时,必须获得同一把实例锁才可以得到a的值,所以,必须等待线程1释放实例锁后,其他线程才可以继续执行同步的get方法。这样,就可以保证其他线程得到的a值一定是线程1修改后的值。

Java并发编程原理与实战十二:深入理解volatile原理与使用

package com.roocon.thread.t7;public class Demo {    private int a = 1;    public synchronized int getA() {        return a;    }    public synchronized void setA(int a) {        try {            Thread.sleep(200);        } catch (InterruptedException e) {            e.printStackTrace();        }        this.a = a;    }    public static void main(String[] args) {        Demo demo = new Demo();        new Thread(new Runnable() {            @Override            public void run() {               demo.setA(10);            }        }).start();        new Thread(new Runnable() {            @Override            public void run() {                System.out.println(demo.getA());            }        }).start();        try {            Thread.sleep(500);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("最终结果:" + demo.getA());    }}

Java并发编程原理与实战十二:深入理解volatile原理与使用

运行结果:

10
最终结果:10

当然,要清楚的一点是,这里的输出统一仅仅是基于线程1的set操作是先于线程2的get操作执行的。但是,要知道,两个线程并发执行,不一定是set操作一定优先get操作执行的。

同样的,volatile也可以保证可见性。因为synchronized是重量级锁,所以,使用volatile会更好。

Java并发编程原理与实战十二:深入理解volatile原理与使用

package com.roocon.thread.t7;


public class Demo {
    private volatile int a = 1;

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        new Thread(new Runnable() {
            @Override
            public void run() {
               demo.setA(10);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(demo.getA());
            }
        }).start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终结果:" + demo.getA());
    }
}

Java并发编程原理与实战十二:深入理解volatile原理与使用

运行结果:

10
最终结果:10

再来看个demo理解volatile的可见性:

Java并发编程原理与实战十二:深入理解volatile原理与使用

package com.roocon.thread.t7;

public class Demo2 {
    public volatile boolean run = false;

    public static void main(String[] args) {
        Demo2 demo2 = new Demo2();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i< 5; i++) {
                    System.out.println("执行了第" + i +"次");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                demo2.run = true;
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(!demo2.run){

                }
                System.out.println("线程2执行了");
            }
        }).start();
    }
}

Java并发编程原理与实战十二:深入理解volatile原理与使用

运行结果:

执行了第1次
执行了第2次
执行了第3次
执行了第4次
线程2执行了

这里需要注意的是,volatile只能保证线程的可见性,但是并不能保证原子性操作。如果volatile修饰的变量涉及到非原子操作,那么,我们需要使用synchronized来保证它的安全性。

Java并发编程原理与实战十二:深入理解volatile原理与使用

package com.roocon.thread.t7;


public class Demo {
    private volatile int a = 1;

    public synchronized int getA() {
        return a++;
    }

    public synchronized void setA(int a) {
        this.a = a;
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        new Thread(new Runnable() {
            @Override
            public void run() {
               demo.setA(10);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(demo.getA());
            }
        }).start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终结果:" + demo.getA());
    }
}

Java并发编程原理与实战十二:深入理解volatile原理与使用

运行结果:

10
最终结果:11

volatile:

在多处理器的系统上,它会执行一下步骤:

1.将当前处理器缓存行的内容写回到系统内存

2.这个写回到内存的操作会使得在其他CPU里缓存了该内存地址的数据失效

3.其他CPU缓存数据失效,则会重新去内存中读取值,也就是被修改的数据

这里要理解的一个概念是缓存行,缓存行是CPU缓存的一个基本单位。

硬盘---内存--CPU缓存,内存的读取速度比硬盘快,CPU缓存的读取速度比内存更高效。

volatile和synchronized的比较:

synchronized是完全可以替换volatile的,只是volatile相对synchronized是轻量级锁。

volatile是不可以完全替换synchronized的,因为volatile只能保证可见性,并不能保证操作的原子性。所以,很多情况下,两者会相互结合使用。

参考资料:

《java并发编程与实战》龙果学院

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
volatile实现可见性但不保证原子性
   volatile关键字:能够保证volatile变量的可见性不能保证volatile变量复合操作的原子性         volatile如何实现内存可见性:        深入来说:通过加入内存屏障和禁止重排序优化来实现的。对volatile变量执行写操作时,会在写操作后加入一条store屏
Wesley13 Wesley13
2年前
Volatile关键字
Volatile关键字①volatile的两个特点1保证线程(CPU)之间的可见性;(也就是保证数据一致性)简单解释一下:一个线程将一个值的数值改变时,另一个使用该数值的线程能看到这种改变;2禁止指令重排序(禁止乱序执行);这个和单例
Wesley13 Wesley13
2年前
Java并发(六):volatile的实现原理
synchronized是一个重量级的锁,volatile通常被比喻成轻量级的synchronizedvolatile是一个变量修饰符,只能用来修饰变量。volatile写:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存。volatile读:当读一个volatile变量时,JMM会把该线程对应的
Wesley13 Wesley13
2年前
Java多线程之volatile详解
目录:什么是volatile?JMM内存模型之可见性volatile三大特性之一:保证可见性volatile三大特性之二:不保证原子性volatile三大特性之三:禁止指令重排小结1.什么是volatile?答:volatile是java虚拟机提供的轻量级的同步机制(
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
Java多线程(二)
\恢复内容开始一,volatile关键字当多个线程操作共享数据时,可以保证内存中的数据可见性相较于synchronized关键字:1,不具备“互斥性”2,不能保证变量的原子性二,原子变量volatile保证内存可见性CAS(CompareAndSwap)算法保证数据的原子性内存值V预估值A更新值
Wesley13 Wesley13
2年前
Java 多线程:volatile关键字
概念volatile也是多线程的解决方案之一。\\volatile能够保证可见性,但是不能保证原子性。\\它只能作用于变量,不能作用于方法。当一个变量被声明为volatile的时候,任何对该变量的读写都会绕过高速缓存,直接读取主内存的变量的值。如何理解直接读写主内存的值:回到多线程生成的原因(Java内存模型与
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这