返回顶部
首页 > 资讯 > 后端开发 > Python >详解Java并发编程基础之volatile
  • 284
分享到

详解Java并发编程基础之volatile

2024-04-02 19:04:59 284人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录一、volatile的定义和实现原理1、java并发模型采用的方式2、volatile的定义3、volatile的底层实现原理二、volatile的内存语义1、volatile的

一、volatile的定义和实现原理

1、Java并发模型采用的方式

a)线程通信的机制主要有两种:共享内存和消息传递。

①共享内存:线程之间共享程序的公共状态,通过写-读共享内存中的公共状态来进行隐式通信;

②消息传递:线程之间没有公共状态,线程之间 必须通过发送消息来显式通信。

b)同步:用于控制不同线程之间操作发生相对顺序。在

共享内存模型中,同步是显式的进行的,需要显示的指定某个方法或者代码块在线程执行期间互斥进行。

消息传递模型中,由于消息的发送必定在消息的接受之前,所以同步是隐式的进行的。

c)Java并发采用的是共享内存模型,线程之间通信总是隐式的进行,而且这个通信是对程序员透明的。那么我们需要了解的是这个隐式通信的底层工作机制。

2、volatile的定义

Java编程语言中允许线程访问共享变量,为了确保共享变量能够被准确和一致性的更新,线程应该确保通过排它单独获得这个变量。

3、volatile的底层实现原理

a)在编写多线程程序中,使用volatile修饰的共享变量在进行写操作的时候,编译器生成的汇编代码中会多出一条lock指令,这条lock指令的作用:

  • ①将当前处理器缓存行中的数据写回到系统内存
  • ②这个写回内存的操作会使得其他CPU里缓存了该内存地址的数据无效

b)参考下面的这张图理解

二、volatile的内存语义

1、volatile的特性

a)首先我们来看对单个变量的读/写的实现(单个变量的情况可以看做是对同一个锁对这个变量的读/写进行了同步),看下面的例子


package cn.JVM.test;

public class TestVolatile1 {

    volatile long var1 = 0L;

    public void set(long l) {
        // TODO Auto-generated method stub
        var1 = l;
    }

    public void getAndIncrement() {
        // TODO Auto-generated method stub
        var1 ++; //注意++操作
    }

    public long get() {
        return var1;
    }
}

上面的set和get操作在语义上和使用synchronized修饰后一样,即下面的这种写法


package cn.jvm.test;

public class TestVolatile1 {

    volatile long var1 = 0L;

    public synchronized void set(long l) {
        // TODO Auto-generated method stub
        var1 = l;
    }

    public synchronized long get() {
        return var1;
    }
}

b)但是在上面的用例中,我们使用的var1++操作,整体上没有原子性,所以如果使用多线程方粉getAndIncrement方法的话,会导致读出的数据和主存中不一致的情况。

c)volatile变量的特性

①可见性:对一个volatile变量的读操作,总是能够看到对这个volatile变量最后的写入

②原子性:对任意单个volatile变量的读写具有原子性,但是对于volatile变量的复合型操作并不具备原子性

2、volatile写-读建立的happens-before关系

a)看下面的代码实例


package cn.jvm.test;

public class TestVolatile2 {

    int a = 0;
    volatile boolean flag = false;

    public void writer() {
        a = 1;
        flag = true;
    }

    public void reader() {
        if(flag) {
            int i =a;
            //...其他操作
        }
    }
}

b)在上面的程序中,假设线程A执行write方法,线程B执行reader方法,根据happens-before规则有下面的关系:

程序次序规则:①happens-before②; ③happens-before④

volatile规则:②happens-before③

传递性规则:①happens-before④

所以可以得到下面的这个状态图

3、volatile的写/读内存语义

a)下面是volatile的写/读内存语义

①当写一个volatile变量时候,JMM会将线程对应的本地内存中的共享变量值刷新到主内存中

②当读一个volatile变量的时候,JMM会将线程对应的本地内存置为无效,然后从主内存中读取共享变量

b)还是参照上面的程序示例,参考视图的模型来进行说明

①写内存语义的示意图:假设线程A执行writer方法,线程B执行reader方法,初始状况下线程A和B中的变量都是初始状态

②写内存语义的示意图:

三、volatile内存语义的实现

我们上面说到的基本上从宏观上而言都是说明了volatile保证内存可见性问题,volatile的另一个语义就是禁止指令重排序的优化。下面说一下volatile禁止指令重排序的实现细节

1、volatile重排序规则

①当第二个操作是volatile写的时候,不管第一个操作是什么,都不能进行指令重排序。这个规则确保volatile写之前的操作都不会被重排序到volatile写之后。也是为了保证volatile写对其他线程可见

②当第一个操作为volatile读的时候,不管第二个操作是什么,都不能进行重排序。确保volatile读之后的操作不会被重排序到volatile读之前

③当第一个操作是volatile写,第二个操作是volatile读的时候,不能进行重排序

如下所示,上面的是下表中的总结

2、内存屏障  

编译器在生成字节码的时候,会在指令序列中插入内存屏障来禁止对特定类型的处理器重排序。下面是集中策略,后面会说明这几种情况

①在每个volatile写操作之前插入StoreStore屏障

②在每个volatile写操作之后插入StoreLoad屏障

③在每个volatile读操作之后插入LoadLoad屏障

④在每个volatile读操作之后插入LoadStore屏障

3、内存屏障示例

a)volatile写插入内存屏障之后的指令序列图

b)volatile读插入内存屏障后的指令序列图

四、volatile与死循环问题

1、先看下面的示例代码,观察运行结果,当共享变量isRunning没有被声明为volatile的时候,main线程会在2秒之后将共享变量isRunning置为false并且输出修改信息,这样新建的线程应该结束运行,但是实际上并没有,控制台中会一直保持运行的状态,并且不会打印线程结束执行;如下所示


package cn.jvm.test;

class ThreadDemo extends Thread {
    private  boolean isRunning = true;
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 开始执行");
        while(isRunning) {

        }
        System.out.println(Thread.currentThread().getName() + " 结束执行");
    }
    public boolean isRunning() {
        return isRunning;
    }
    public void SetIsRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }
}

public class TestVolatile4 {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        td.start();
        try {
            Thread.sleep(2000);
            td.SetIsRunning(false);
            System.out.println(Thread.currentThread().getName() + " 线程将共享变量值修改为false");
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

2、分析出现上面结果的原因

在启动线程ThreadDemo之后,变量isRunning被存在公共堆栈以及线程的私有堆栈中,后//续中线程一直在私有堆栈中取出isRunning的值,虽然main线程执行SetIsRunning方法修改了isRunning的值,但是这个值并没有被Thread-//0线程所知,就像上面说的Thread-0取得值一直都是私有堆栈中的,所以不会知道isRunning被修改,也就不会退出循环

3、按照上面的原因分析一下执行的时候的工作内存和主内存的情况,按照下面的分析我们很容易得出结论

上面的问题就是因为工作内存(私有堆栈)和主内存(公共堆栈)中的值不同步。而按照我们上面说到的volatile使得单个变量保证线程可见性,就可以对程序修改保证共享变量在main线程中的修改对Thread-0线程可见(结合volatile的实现原理)

4、修改之后的结果


package cn.jvm.test;

class ThreadDemo extends Thread {
    private volatile boolean isRunning = true;
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 开始执行");
        while(isRunning) {
            
        }
        System.out.println(Thread.currentThread().getName() + " 结束执行");
    }
    public boolean isRunning() {
        return isRunning;
    }
    public void SetIsRunning(boolean isRunning) {
        this.isRunning = isRunning;
    }
}

public class TestVolatile4 {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        td.start();
        try {
            Thread.sleep(2000);
            td.SetIsRunning(false);
            System.out.println(Thread.currentThread().getName() + " 线程将共享变量值修改为false");
        } catch (Exception e) {
            // TODO: handle exception
            e.printStackTrace();
        }
    }
}

五、volatile对于复合操作非原子性问题

1、volatile能保证对单个变量在多线程之间的可见性问题,但是对于单个变量的复合操作不能保证原子性,如下代码示例,运行结果为

当然这个结果是随机的,但是不能保证运行结果是100000

在没有使用同步操作之前,虽然count变量是volatile的,但是由于count++操作是个复合操作

①从内存中取出count的值

②计算count的值

③将count的值写到内存中

这个复合操作由于volatile不能保证原子性,所以就会出现错误


package cn.jvm.test;

import java.util.ArrayList;
import java.util.List;

public class TestVolatile5 {
    volatile int count = 0;
     void m(){
        for(int i = 0; i < 10000; i++){
            count++;
        }
    }

    public static void main(String[] args) {
        final TestVolatile5 t = new TestVolatile5();
        List<Thread> threads = new ArrayList<>();
        for(int i = 0; i < 10; i++){
            threads.add(new Thread(new Runnable() {
                @Override
                public void run() {
                    t.m();
                }
            }));
        }
        for(Thread thread : threads){
            thread.start();
        }
        for(Thread thread : threads){
            try {
                thread.join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println(t.count);
    }
}

2、下面按照JVM的内存工作来分析一下,即当前一个线程在计算count变量的时候,另一个线程已经修改了count变量的值,这样就必然会出现错误。所以对于这种复合操作就需要使用原子类或者使用synchronized来保证原子性(保证同步)

3、修改后的synchronized和使用原子类如下所示


package cn.jvm.test;
 
 import java.util.ArrayList;
 import java.util.List;
 
 public class TestVolatile5 {
     int count = 0;
     synchronized void m(){
         for(int i = 0; i < 10000; i++){
             count++;
         }
     }
 
     public static void main(String[] args) {
         final TestVolatile5 t = new TestVolatile5();
         List<Thread> threads = new ArrayList<>();
         for(int i = 0; i < 10; i++){
             threads.add(new Thread(new Runnable() {
                 @Override
                 public void run() {
                     t.m();
                 }
             }));
         }
         for(Thread thread : threads){
             thread.start();
         }
         for(Thread thread : threads){
             try {
                 thread.join();
             } catch (InterruptedException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
         }
         System.out.println(t.count);
     }
 }

package cn.jvm.test;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
 public class TestVolatile5 {
     AtomicInteger count = new AtomicInteger(0);
     void m(){
         for(int i = 0; i < 10000; i++){
             count.getAndIncrement();
         }
     }
 
     public static void main(String[] args) {
         final TestVolatile5 t = new TestVolatile5();
         List<Thread> threads = new ArrayList<>();
         for(int i = 0; i < 10; i++){
             threads.add(new Thread(new Runnable() {
                 @Override
                 public void run() {
                     t.m();
                 }
             }));
         }
         for(Thread thread : threads){
             thread.start();
         }
         for(Thread thread : threads){
             try {
                 thread.join();
             } catch (InterruptedException e) {
                 // TODO Auto-generated catch block
                 e.printStackTrace();
             }
         }
         System.out.println(t.count);
     }
 }

以上就是详解Java并发编程基础之volatile的详细内容,更多关于Java 并发编程 volatile的资料请关注编程网其它相关文章!

--结束END--

本文标题: 详解Java并发编程基础之volatile

本文链接: https://lsjlt.com/news/128036.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 详解Java并发编程基础之volatile
    目录一、volatile的定义和实现原理1、Java并发模型采用的方式2、volatile的定义3、volatile的底层实现原理二、volatile的内存语义1、volatile的...
    99+
    2024-04-02
  • JUC并发编程之volatile详解
    目录   1. volatile 1.1 volatile关键字的作用 1.1.1 变量可见性 1.1.2 禁止指令重排序 1.2 volatile可见性案例 1.3 volatile非原子性案例 1.4 volatile 禁止重排序 1....
    99+
    2023-09-01
    java jvm 开发语言
  • 详解Java并发编程之volatile关键字
    目录1、volatile是什么?2、并发编程的三大特性3、什么是指令重排序?4、volatile有什么作用?5、volatile可以保证原子性?6、volatile 和 sy...
    99+
    2024-04-02
  • Java并发编程之Volatile变量详解分析
    目录一、volatile变量的特性1.1、保证可见性,不保证原子性1.2、禁止指令重排二、内存屏障三、happens-beforeVolatile关键字是Java提供的一种轻量级的同...
    99+
    2024-04-02
  • Java基础面试题之volatile详解
    目录1、volatile保证可见性1.1、什么是JMM模型?1.2、volatile保证可见性的代码验证1.2.1、无可见性代码验证1.2.1、volatile保证可见性验证2、vo...
    99+
    2024-04-02
  • java 多线程与并发之volatile详解分析
    目录CPU、内存、缓存的关系CPU缓存什么是CPU缓存为什么要有多级CPU CacheJava内存模型(Java Memory Model,JMM)JMM导致的并发安全问题可见性原子...
    99+
    2024-04-02
  • python基础之并发编程(一)
    目录一、进程(Process)二、线程(Thread)三、并发编程解决方案:四、多线程实现 (两种)1、第一种 函数方法2、第二种 类方法包装五、守护线程与子线程1、线程在分法有:2...
    99+
    2024-04-02
  • python基础之并发编程(二)
    目录一、多进程的实现方法一方法二:二、使用进程的优缺点1、优点2、缺点三、进程的通信1、Queue 实现进程间通信2、Pipe 实现进程间通信(一边发送send(obj),一边接收(...
    99+
    2024-04-02
  • python基础之并发编程(三)
    目录一、协程定义和作用1、使用协程的优点2、使用协程的缺点二、Greenlet 的使用三、Gevent的使用四、async io 异步 IO1、asyncio中的task的使用五、总...
    99+
    2024-04-02
  • Java并发编程之关键字volatile的深入解析
    目录前言一、可见性二、有序性总结前言 volatile是研究Java并发编程绕不过去的一个关键字,先说结论: volatile的作用:       &n...
    99+
    2024-04-02
  • Java并发编程之ThreadLocal详解
    目录一、什么是ThreadLocal?二、ThreadLocal的使用场景三、如何使用ThreadLocal四、数据库连接时的使用五、ThreadLocal工作原理六、小结七、注意点...
    99+
    2024-04-02
  • Java高并发编程基础之如何使用AQS
    本篇内容主要讲解“Java高并发编程基础之如何使用AQS”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java高并发编程基础之如何使用AQS”吧! 引言曾经有一道比较比较经典的面试题“...
    99+
    2023-06-15
  • Java并发编程之LockSupport类详解
    目录一、LockSupport类的属性二、LockSupport类的构造函数三、park(Object blocker)方法 和 park()方法分析四、parkNanos(Obje...
    99+
    2024-04-02
  • 详解Java高并发编程之AtomicReference
    目录一、AtomicReference 基本使用1.1、使用 synchronized 保证线程安全性二、了解 AtomicReference2.1、使用 AtomicReferen...
    99+
    2024-04-02
  • Java并发编程之详解ConcurrentHashMap类
    前言 由于Java程序员常用的HashMap的操作方法不是同步的,所以在多线程环境下会导致存取操作数据不一致的问题,Map接口的另一个实现类Hashtable 虽然是线程安全的,但是...
    99+
    2024-04-02
  • Java并发编程之Executors类详解
    一、Executors的理解 Executors类属于java.util.concurrent包; 线程池的创建分为两种方式:ThreadPoolExecutor ...
    99+
    2024-04-02
  • Java并发编程之关键字volatile知识总结
    目录一、作用二、可见性三、有序性四、happens-before五、与 Synchronized 对比一、作用 被 volatile 修饰的变量 1.保证了不同线程对该变量操作的内存...
    99+
    2024-04-02
  • python基础之什么是并发编程
    本篇内容介绍了“python基础之什么是并发编程”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、协程定义和作用协程(coroutine),...
    99+
    2023-06-25
  • 6. `Java` 并发基础之`ReentrantReadLock`
    前言:随着多线程程序的普及,线程同步的问题变得越来越常见。Java中提供了多种同步机制来确保线程安全,其中之一就是ReentrantLock。ReentrantLock是Java中比较常用的一种同...
    99+
    2023-09-21
    java 开发语言
  • Java并发编程之volatile与JMM多线程内存模型
    目录一、通过程序看现象二、为什么会产生这种现象(JMM模型)?三、MESI 缓存一致性协议 一、通过程序看现象 在开始为大家讲解Java 多线程缓存模型之前,我们先看下面的这一段代码...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作