volatile关键字

先来分享一篇写的非常好的关于 volatile 博客《Java并发编程:volatile关键字解析》,里面也详细的介绍了原子性、可见性和有序性,再来谈一谈自己的一些理解:

先来说一下 JMM:

JMM

JMM ( Java内存模型 Java Memory Model)本身是一种抽象的概念,他并不真实存在;他描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括 实例字段,静态字段和构成数组对象的元素)的访问方式。

速度:
硬盘 < 内存 < CPU(会有高速缓存区)

CPU的速度会远大于内存的读写速度,CPU 直接读取内存的数据,就会导致CPU 会有部分时间闲置,导致资源的浪费;所以CPU会有一个高速缓存区。

JMM关于同步的规定:

1、线程解锁前,必须把共享变量的值刷新回主内存;
2、线程加锁前,必须读取主内存的最新值到自己的工作内存;
3、加锁解锁是同一把锁。

由于JVM 运行程序的实体是线程,而每个线程创建时 JVM 都会为其创建一个工作内存,工作内存是每个线程的私有数据区域,而 Java 内存模型中规定的所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回到主内存,不能直接操作主内存中的变量,不同的线程间是无法访问对方的工作内存的,线程间的通信必须通过主内存来完。

volatile关键字

并发编程中又是那个概念:原子性、可见性、有序性;并发程序如果想要正确地执行,必须要保证原子性、可见性以及有序性。

volatile 是 Java 虚拟机提供的轻量级的同步机制,它能够保证【有序性】和【可见性】,但是不能够保证【原子性】,所以是无法替代 synchronized 的。

在性能方面,volatile 是一种非锁机制,这种机制可以避免锁机制引起的线程上下文切换和调度问题。因此,volatile 的执行成本比 synchronized 更低。
Java 中,可以使用 synchronized 和 Lock 保证【原子性】和【可见性】,synchronized 和 Lock 能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中,所以不存在会被其他线程中途获取结果的问题。

一、可见性

假设线程1、线程2同时从主内存中拿到了 i 的值为 10,并都对 i 进行 i = i + 5 的操作;当线程1 修改了 i 的值为15 并将 15 写入主内存中,此时 线程2 并不知道 i 的值已经被修改,他的工作内存中的 i 还是10,所以此时线程2对 i 执行 +5 的操作也会返回 15 并写入到主内存,所以最终两个线程执行完毕后主内存 i 的值将仍然是 15;如果执行完毕后,可以通知线程2,线程2拿来线程1的结果再进行 +5 的操作,就可以得到正确的执行结果。

可见性,即多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

Java 中,volatile 关键字会强制将修改的值立即写入主存,如果有其他线程需要读取时,它会从内存中读取新值;普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值。

被 volatile 关键字修饰的共享变量在转换成汇编语言时,会加上一个以 lock 为前缀的指令,当 CPU 发现这个指令时,立即做两件事:
1.将当前内核高速缓存行的数据立刻回写到内存;
2.使在其他内核里缓存了该内存地址的数据无效。

public static void main(String[] args ){
    Test test = new Test();

    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "开始执行!");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        test.addFive();
        System.out.println(Thread.currentThread().getName() + " y的值为:" + test.y);
        System.out.println(Thread.currentThread().getName() + "执行结束!");
    },"test01").start();

    while (5 == test.y){

    }
    System.out.println(Thread.currentThread().getName() + " y的值为:" + test.y);
}

private static class Test{
    int y = 5;

    public void addFive(){
        y = y + 5;
    }
}

如果 y 不加 volatile 修饰,while 将会一直循环:

test01开始执行!
test01 y的值为:10
test01执行结束!

加上 volatile 后,执行结果为:

test01开始执行!
test01 y的值为:10
test01执行结束!
main y的值为:10

二、原子性

原子性,即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

Java中,基本数据类型变量的读取和赋值操作是原子性操作,例如:x = 10 ,就是直接将 10 赋值给 x,是原子操作 ,写入当前线程的工作内存中,再同步给主存;x++ ; y = x 等是不具有原子性的;

public static void main(String[] args ){
    Test test = new Test();
    
    for (int i = 1;i <= 20;i++){
        new Thread(() -> {
            for (int y = 1;y <= 1000;y++){
                test.addOne();
            }
        },String.valueOf(i)).start();
    }

    while (Thread.activeCount() > 2){
        // 2 表示 一个 main 线程,一个 gc 线程,表示其他线程全部执行结束
        Thread.yield();
    }

    System.out.println(Thread.currentThread().getName() + " y的值为:" + test.y);
}

private static class Test{
    volatile int y = 0;

    public void addOne(){
        y++;
    }
}

返回结果:

main y的值为:19563

所以,volatile 是不保证原子性的,此处如果想保证原子性,可以使用 synchronized 或者是使用:

AtomicInteger y = new AtomicInteger();
public void addOne(){
     y.addAndGet(1);
 }

三、有序性

有序性:即程序执行的顺序按照代码的先后顺序执行。

在Java内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,因为对有依赖性的内容并不会进行重排,例如:

int y = 10; 
int x = y;	// x 依赖于 y,所以不会对这两行代码进行重排序

但是多线程并发执行是无法保证正确性的。

jdk1.5 中,对 volatile 的语义进行了增强,提供了可靠的有序性的支撑,加上 volatile 是添加一个内存屏障(是一个 CPU 指令,可以保证特定操作的执行顺序;保证某些变量的内存可见性。),禁止了重排序,在写操作后加一条 store 屏障指令,将工作内存中的共享变量的值刷新回主内存;在读操作前添加 load 屏障指令,从主内存中读取共享变量。

说到有序性,还有一个 happens-before 规则 也需要了解一下,我们可以通过 happens-before 规则推断出是否能够满足有序性。

happens-before 规则

因为 Jvm 会对代码进行编译优化,指令会出现重排序的情况,为了避免编译优化对并发编程安全性的影响,需要 happens-before 规则定义一些禁止编译优化的场景,保证并发编程的正确性。
1、程序的顺序规则:
一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;编译器仍然会对代码机型重排序,但是保证结果一定等于顺序推演的结果。
2、volatile规则:
如果一个线程先去写一个 volatile 变量,然后一个线程去读这个变量,那么这个写操作的结果一定对读的这个线程可见。
3、传递性规则:
就是happens-before原则具有传递性,即 A happens-before B , B happens-before C,那么 A happens-before C。
4、管程中的锁规则:
无论是在单线程环境还是多线程环境,对于同一个锁来说,一个线程对这个锁解锁之后,另一个线程获取了这个锁都能看到前一个线程的操作结果!(管程是一种通用的同步原语,synchronized就是管程的实现)。
5、线程 start() 规则:
在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见。
6、线程 join() 规则:
在主线程A执行过程中,子线程B终止,那么线程B在终止之前对共享变量的修改结果在线程A中可见。
7、线程中断规则:
对线程 interrupt() 方法的调用 先行发生于 被中断线程代码检测到中断事件的发生,可以通过 Thread.interrupted() 检测到是否发生中断。
8、对象终结规则
一个对象的初始化完成先行发生于他的 finalize() 方法的开始。

int a = 0;
volatile boolean flag = false;

public void writer() {
    a = 1;              // 1 线程A修改共享变量为 1
    flag = true;        // 2 线程A修改volatile变量为 true
} 

public void reader() {
    if (flag) {         // 3 线程B读同一个volatile变量 为 true
    	int i = a;          // 4 线程B读共享变量 i = 1
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
**数字乡村解决方案摘要** **国家战略与乡村振兴** 中国正实施国家大数据战略,以加快数字中国的建设,整合数据资源,保障数据安全,并推动产业升级。2023年中央一号文件强调了乡村振兴战略,旨在通过产业兴旺、生态宜居、乡风文明、治理有效和生活富裕五大方面,实现农业农村现代化。 **数字乡村建设的重要性** 数字乡村建设是乡村振兴战略的重要组成部分,通过整合资源数据,推动物联网和遥感技术在农业中的应用,促进农业现代化。它被视为促进乡村转型发展的新方向,是数字化在农业农村经济社会发展中的应用。 **关键要素与效益** 数字乡村建设的关键要素包括数据资源整合、产业促进、农业发展规模化和标准化,以及改善乡村生活服务体系。预期效益包括提升国家治理能力,实现政府决策科学化和社会治理精准化,同时推动公共服务高效化。 **体系架构与数据融合** 数字乡村的体系架构包括乡村生态、基层治理、慧治、慧享、慧融、慧美、慧智和慧图等多个方面,实现城乡、政企、农户各级平台及服务的融合。数据融合是核心,整合乡村人口、产值、环境监测等多方面数据,为乡村治理和产业发展提供支撑。 **多业务协同与智慧治理** 数字乡村通过多业务协同应用平台,实现乡村物联网的布局和触达,涵盖农业生产、农资厂商、消费者等环节。区块链技术在农产品质量溯源中的应用,确保了产品全过程可追溯。乡村智慧治理通过村务管理、财务管理等方面提升治理效能,实现绿色发展理念。综合服务体系通过“互联网+服务”模式,提供便民服务和乡村经济发展支持,推动乡村全面振兴。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值