线程的volatile关键字详解

系列文章目录


简述

关键字 volatile 的主要作用是使变量在多个线程间可见

解决死循环

示例

class ThreadTest7 implements Runnable{ 
    private boolean isRunnning = true; 
    @Override 
    public void run() { 
        System.out.println("Thread begin: "+Thread.currentThread().getName());   
        while (isRunnning == true){    
            
        }      
        System.out.println("Thread end: "+Thread.currentThread().getName());  
    }   
    public boolean isRunnning() {    
        return isRunnning; 
    } 
    public void setRunnning(boolean runnning) { 
        isRunnning = runnning;
    }   
    public static void main(String[] args) throws InterruptedException {      
        ThreadTest7 printString = new ThreadTest7(); 
        Thread thread = new Thread(printString,"Thread-A");    
        thread.start();      
        Thread.sleep(100); 
        printString.setRunnning(false);
        System.out.println(" 我要停止它!" + Thread.currentThread().getName());    
    }
}

运行结果:
Thread begin: Thread-A
我要停止它!main

解析代码:isRunnning变量被主线程做了修改,因此thread线程取到的值,理应为false,而线程也应该会停止,但是从运行结果看了,线程没有运行结束,仍旧在死循环。

分析

是什么原因?为什么线程没有停下来?

JVM 有 Client 和 Server 两种模式,我们可以通过运行:java -version 来查看 jvm 默认工作在什么模式。我们在 IDE 中把 JVM 设置为在 Server 服务器的环境中,具体操作只需配置运行参数为 -server。

通过在cmd上使用java -version,可以看到我的jvm运行模式如下:
C:\Users\59357>java -version
java version “1.8.0_181”
Java™ SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot™ 64-Bit Server VM (build 25.181-b13, mixed mode)

可以看到,我当前jvm的运行模式是Server,上面的代码运行起来没毛病的,会出现死循环。(注意:在Client 模式下,线程会被停止,我们在 IDE 中把 JVM 设置为在 Server 服务器的环境中,具体操作只需配置运行参数为 -client 。)

是什么样的原因造成将 JVM 设置为 -server 就出现死循环呢?
在启动 thread 线程时,变量 boolean isContinuePrint = true; 存在于公共堆栈及线程的私有堆栈中。

在 JVM 设置为 -server 模式时为了线程运行的效率,线程一直在私有堆栈中取得 isRunning 的值是 true。

而代码 thread.setRunning(false); 虽然被执行,更新的却是公共堆栈中的 isRunning 变量值 false,所以一直就是死循环的状态。
这个问题其实就是私有堆栈中的值和公共堆栈中的值不同步造成的。解决这样的问题就要使用 volatile 关键字了,它主要的作用就是当线程访问 isRunning 这个变量时,强制性从公共堆栈中进行取值。

解决的办法是使用 volatile 关键字。
关键字 volatile 的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

注:当循环中打印一句话时,循环会结束,具体原因未知

将代码更改如下:

volatile private boolean isRunnning = true;
线程就会从公共堆栈中取值。

不能保证原子性

与synchronized比较
关键字 volatile 是线程同步的轻量级实现,所以 volatile 性能肯定比 synchronized 要好,并且 volatile 只能修饰于变量,而 synchronized 可以修饰方法,以及代码块。随着 JDK 新版本的发布,synchronized 关键字在执行效率上得到很大提升,在开发中使用 synchronized 关键字的比率还是比较大的。

多线程访问 volatile 不会发生阻塞,而 synchronized 会出现阻塞。

volatile 能保证数据的可见性,但不能保证原子性;而 synchronized 可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。

再次重申一下,关键字 volatile 解决的是变量在多个线程之间的可见性;而 synchronized 关键字解决的是多个线程之间访问资源的同步性。

线程安全包含原子性和可见性两个方面,Java 的同步机制都是围绕这两个方面来确保线程安全的。
volatile 非原子性的特征

class ThreadTest8 extends Thread { 
    volatile private static int count; 
    @Override
    public void run() { 
        addCount(); 
    }  
    private void addCount() { 
        for (int i = 0;i<100;i++){        
            count++;      
        }  
        System.out.println(count);  
    }   
    public static void main(String[] args) {      
        int size = 100;  
        ThreadTest8[] myThreads = new ThreadTest8[size];    
        for (int i=0;i<size;i++){
            myThreads[i] = new ThreadTest8();  
        }    
        for (int i=0;i<size;i++){    
            myThreads[i].start();    
        }  
    }
}

运行结果:
…825383538153805378757675
在 addCount 方法上加入 synchronized 同步关键字与 static 关键字,达到同步的效果。
再次运行结果:
…960097009800990010000
关键字 volatile 提示线程每次从共享内存中读取变量,而不是从私有内存中读取,这样就保证了同步数据的可见性。但在这里需要注意的是:如果修改实例变量中的数据,比如 i++,也就是比
i=i+1,则这样的操作其实并不是一个原子操作,也就是非线程安全。表达式 i++ 的操作步骤分解为下面三步:
• 从内存中取 i 的值;
• 计算 i 的值;
• 将 i 值写入到内存中。
假如在第二步计算 i 值的时候,另外一个线程也修改 i 的值,那么这个时候就会脏数据。解决的方法其实就是使用 synchronized 关键字。所以说 volatile 关键字本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值