对共享可变数据不加同步,虚拟机可能会搞死你

简介: 大家都知道,在Java中,除非变量的类型是 long 或 double,否则对于变量的读取和写入操作都是原子性的。也就是说,即使多个线程同时访问并修改变量,也不会有线程安全的问题。

大家都知道,在Java中,除非变量的类型是 long 或 double,否则对于变量的读取和写入操作都是原子性的。也就是说,即使多个线程同时访问并修改变量,也不会有线程安全的问题。


于是,有人可能就会觉得,既然对变量(除long或double)的读取和写入操作是原子性的,那我们是不是在多线程情况下,也可以不考虑使用同步,直接对这些变量就行读写操作呢,因为如果要对这些简单的操作加同步,反而会影响代码性能。


这种想法是不是对的呢?我们来看看下面的代码:


public class StopThread {    private static boolean stopRequested;
    public static void main(String[] args) throws InterruptedException {        Thread backgroundThread = new Thread(() -> {          int i = 0;          while (!stopRequested)              i++;        });
        backgroundThread.start();        TimeUnit.SECONDS.sleep(1);        stopRequested = true;    }}


上面的代码实现的功能是:主线程启动backgroundThread 线程,然后自己停止一秒钟,之后主线程将 stopRequested 设置为 true,从而导致后台线程的循环终止,程序结束。


代码看上去没有上面问题,然而,在我的机器上,程序永远不会终止:后台线程永远循环!


那么问题出在哪里呢?我们在主线程中做了stopRequested = true;,变成true之后,backgroundThread 线程在while循环中判断到这一变化不是应该停止循环结束程序吗?怎么就能死循环了呢?


其实问题在于在缺乏同步的情况下,无法保证后台线程何时(如果有的话)看到主线程所做的 stopRequested 值的更改。在缺乏同步的情况下,虚拟机会优化backgroundThread 中的代码,将:


while (!stopRequested)    i++;


优化成:


if (!stopRequested)    while (true)        i++;


这种操作可以说简直是丧心病狂……,我的天,由于stopRequested的判断被提到了while循环之外,导致了我们的程序进入死循环无法停止。罪魁祸首居然是因为虚拟机的优化导致的问题。而这点确实很不容易发现。


那么怎么觉得这个问题呢?既然是因为缺乏同步导致的主线程stopRequested修改无法被backgroundThread 线程发现,那么我们就给他加上同步,代码如下:


public class StopThread {    private static boolean stopRequested;
    private static synchronized void requestStop() {        stopRequested = true;    }
    private static synchronized boolean stopRequested() {        return stopRequested;    }
    public static void main(String[] args) throws InterruptedException {        Thread backgroundThread = new Thread(() -> {          int i = 0;          while (!stopRequested())            i++;        });
        backgroundThread.start();        TimeUnit.SECONDS.sleep(1);        requestStop();    }}


我们通过对stopRequested的读写添加了同步,这样,就可以让主线程对stopRequested的修改被backgroundThread 线程看到,同时虚拟机也不再会出现上面的优化代码,而是按照正常的逻辑走,每次访问stopRequested或修改都需要获取到锁才能操作。


通过上面的修改后,强哥的机器上代码终于能够正常的终止了。可是要去修改一个变量就要去手动编写对这个变量读写的加锁代码也好麻烦,有没有更好的方法呢?


当然有了,如果 stopRequested声明为 volatile,则可以省略 stopRequested的第二个版本中的锁。它不那么冗长,而且性能可能更好。虽然 volatile 修饰符不执行互斥,但它保证任何读取字段的线程都会看到最近写入的值


public class StopThread {    private static volatile boolean stopRequested;
    public static void main(String[] args) throws InterruptedException {        Thread backgroundThread = new Thread(() -> {            int i = 0;            while (!stopRequested)                i++;        });            backgroundThread.start();        TimeUnit.SECONDS.sleep(1);        stopRequested = true;    }}


所以,对于共享可变数据,虽然对它的读写操作是原子性的,但是却不能保证由一个线程编写的值对另一个线程可见。因此,如果出现线程之间能可靠通信以及实施互斥,同步是所必需的。

相关文章
|
5月前
|
存储 安全 测试技术
网络奇谭:虚拟机中的共享、桥接与Host-Only模式解析
网络奇谭:虚拟机中的共享、桥接与Host-Only模式解析
73 0
|
6月前
|
Ubuntu 虚拟化 Windows
如何优雅的实现主机与虚拟机文件共享?
如何优雅的实现主机与虚拟机文件共享?
|
6月前
|
存储 安全 测试技术
网络奇谭:虚拟机中的共享、桥接与Host-Only模式解析
网络奇谭:虚拟机中的共享、桥接与Host-Only模式解析
119 0
|
网络协议 虚拟化 Windows
75Linux - VMware虚拟机三种联网方法( Host-Only私有网络共享主机:默认使用VMnet1 )
75Linux - VMware虚拟机三种联网方法( Host-Only私有网络共享主机:默认使用VMnet1 )
128 0
|
Ubuntu Windows
virtualbox虚拟机环境搭建之二---Virtualbox主机与虚拟机设置文件共享
virtualbox虚拟机环境搭建之二---Virtualbox主机与虚拟机设置文件共享
908 0
|
Linux 测试技术 开发工具
VMware Linux虚拟机与WIN7操作系统共享无线网络上网配置
VMware Linux虚拟机与WIN7操作系统共享无线网络上网配置
126 0
|
人工智能 Ubuntu Linux
RK3568开发笔记(三):RK3568虚拟机基础环境搭建之更新源、安装网络工具、串口调试、网络连接、文件传输、安装vscode和samba共享服务
RK3568开发笔记(三):RK3568虚拟机基础环境搭建之更新源、安装网络工具、串口调试、网络连接、文件传输、安装vscode和samba共享服务
RK3568开发笔记(三):RK3568虚拟机基础环境搭建之更新源、安装网络工具、串口调试、网络连接、文件传输、安装vscode和samba共享服务
|
虚拟化
Vmware虚拟机RAC集群绑定共享磁盘方法
Vmware虚拟机RAC集群绑定共享磁盘方法
653 0
Vmware虚拟机RAC集群绑定共享磁盘方法
|
存储 算法 Java
【Android 内存优化】Java 内存模型 ( Java 虚拟机内存模型 | 线程私有区 | 共享数据区 | 内存回收算法 | 引用计数 | 可达性分析 )
【Android 内存优化】Java 内存模型 ( Java 虚拟机内存模型 | 线程私有区 | 共享数据区 | 内存回收算法 | 引用计数 | 可达性分析 )
255 0
|
10天前
|
存储 SQL 数据库
虚拟化数据恢复—Vmware虚拟机误还原快照的数据恢复案例
虚拟化数据恢复环境: 一台虚拟机从物理机迁移到ESXI虚拟化平台,迁移完成后做了一个快照。虚拟机上运行了一个SQL Server数据库,记录了数年的数据。 ESXI虚拟化平台上有数十台虚拟机,EXSI虚拟化平台连接了一台EVA存储,所有的虚拟机都存放在EVA存储上。 虚拟化故障: 工组人员误操作将数年前迁移完成后做的快照还原了,也就意味着虚拟机状态还原到数年前,近几年数据都被删除了。 还原快照相当于删除数据,意味着部分存储空间会被释放。为了不让这部分释放的空间被重用,需要将连接到这台存储的所有虚拟机都关掉,需要将不能长时间宕机的虚拟机迁移到别的EXSI虚拟化平台上。
88 50