多线程之volatile关键字

简介: 每个线程都运行在java栈内存中,每个线程都有自己的工作内存。线程的计算一般是通过工作内存进行交互的。如图所示: 从上图中我们可以看到,线程在初始化时从主内存中加载所需的变量值到工作内存中,然后在线程运行时,如果是读取,则直接从工作内存中读取,如果是写入则先写到工作内存中,之后再刷新到主内存中,这个可以看做是JVM的一个简单的内存模型,但是这样的结构在多线程
每个线程都运行在java栈内存中,每个线程都有自己的工作内存。线程的计算一般是通过工作内存进行交互的。如图所示:

从上图中我们可以看到,线程在初始化时从主内存中加载所需的变量值到工作内存中,然后在线程运行时,如果是读取,则直接从工作内存中读取,如果是写入则先写到工作内存中,之后再刷新到主内存中,这个可以看做是JVM的一个简单的内存模型,但是这样的结构在多线程的情况下有可能会出问题。比如:A线程修改变量的值,也刷新到主内存中了,但是此时B、C线程读取的还是本线程的工作内存,也就是它们读取的不是最新的值,此时就会出现不同线程持有的公共资源不同步的情况。
对于此类问题:我们可以使用synchronized关键字来同步代码块,也可以使用Lock锁来解决该问题,不过Java可以使用volatile关键字更简单的解决此类问题。比如在一个变量前加上volatile关键字。。可以确保每个线程对本地变量的访问和修改都是直接与主内存交互的,而不是与本线程的工作内存交互的。保证每个线程都能获得最“新鲜”的变量值。如下图所示:

但是此时我们需要注意的是:volatile变量只能保证线程取的是最新的值,但是并不能保证数据的同步性。两个线程同时修改一个volatile,有可能会产生脏数据。请看代码的例子:
package com.zkn.newlearn.thread;

/**
 * Created by wb-zhangkenan on 2016/11/4.
 */
public class ThreadTestVolatile01 {

    public static void main(String[] args){
        int value = 100;
        //控制循环次数
        int loops = 0;
        //获取线程组
        ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
        while(loops < 1000){
            //让多线程共享TestVolatile中count值
            TestVolatile testVolatile = new TestVolatile();
            for(int i=0;i<value;i++){
                new Thread(testVolatile).start();
            }
            do{
                try {
                    //让线程休眠15秒
                    Thread.sleep(15);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }while (threadGroup.activeCount() != 2);
            threadGroup.list();
            if(testVolatile.getCount() != value){
                System.out.println("循环到第"+loops+"遍时,出现线程不安全的情况!!");
                System.out.println("此时 count值为:"+testVolatile.getCount());
                System.exit(0);
            }
            loops++;
        }
    }
}

class TestVolatile implements Runnable{

    private volatile int count;

    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            Math.hypot(Math.pow(92456789,i),Math.cos(i));
        }
        count++;
    }

    public int getCount() {

        return count;
    }
}
这段代码的逻辑是这样的:
启动1000个线程,修改共享资源count的值。
休眠15毫秒,让活动线程数变为1(这里有一个Monitor Ctrl Break线程)。
判断实际值和理想值是否一致,如果不一致则此时出现脏数据
我们先来看看自加的操作:count++这句话可以分为两部分,先取出count的值,再执行加1的操作。所以在某两个紧邻的时间片段内可能会出现下面的事情:
1、第一个时间片段:
A线程获得执行机会,因为有关键字volatile修饰,所以它从主内存中获得count的最新值98,记下来的事情又分为两种类型:
如果是单CPU,此时调度器暂停A线程,让出执行几乎给B线程,于是B线程也获得了count的最新值98。
如果是多CPU,此时线程A继续执行,而线程B也同时获得count的最新值98。
2、第二个时间片段:
如果是单CPU,B线程执行完加1动作(原子操作),count的值为99:。由于是volatile类型的变量,所以直接写入主内存,然后A线程继续执行,计算的结果也是99:,重新写入主内存中。
如果是多CPU,A线程执行完加1动作后修改主内存的变量count为99:,线程B执行完毕后也修改住内存中的变量为99:
当这两个时间片执行完毕后,原本期望的结果为100,单运行后的值却为99:,这表示出现了线程不安全的情况,这也证明了,volatile关键字并不能保证线程安全,它只是能保证当线程需要该变量的值时能够获取到最新的值,不能保证多个线程修改的安全性。
参考自:编写高质量代码 改善Java程序的151个建议。

相关文章
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
Java并发编程实战:使用synchronized关键字实现线程安全
176 0
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
210 0
|
10月前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
217 7
|
11月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
127 4
|
12月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
【10月更文挑战第6天】在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
82 2
|
缓存 Java 编译器
【多线程-从零开始-伍】volatile关键字和内存可见性问题
【多线程-从零开始-伍】volatile关键字和内存可见性问题
143 0
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
224 1
|
缓存 Java
【多线程面试题二十三】、 说说你对读写锁的了解volatile关键字有什么用?
这篇文章讨论了Java中的`volatile`关键字,解释了它如何保证变量的可见性和禁止指令重排,以及它不能保证复合操作的原子性。
|
微服务
多线程内存模型问题之在单例模式中,volatile关键字的作用是什么
多线程内存模型问题之在单例模式中,volatile关键字的作用是什么
|
5月前
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
196 0

热门文章

最新文章