上一篇,我们谈了谈如何通过同步来保证共享变量的原子性(一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行),本篇我们来谈一谈如何保证共享变量的可见性(多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值)。

我们使用同步的目的不仅是,不希望某个线程在使用对象状态时,另外一个线程在修改状态,这样容易造成混乱;我们还希望某个线程修改了对象状态后,其他线程能够看到修改后的状态——这就涉及到了一个新的名词:内存(可省略)可见性。

要了解可见性,我们得先来了解一下 Java 内存模型。

Java 内存模型(Java Memory Model,简称 JMM)描述了 Java 程序中各种变量(线程之间的共享变量)的访问规则,以及在 JVM 中将变量存储到内存→从内存中读取变量的底层细节。

要知道,所有的变量都是存储在主内存中的,每个线程会有自己独立的工作内存,里面保存了该线程使用到的变量副本(主内存中变量的一个拷贝)。见下图。

也就是说,线程 1 对共享变量 chenmo 的修改要想被线程 2 及时看到,必须要经过 2 个步骤:

1、把工作内存 1 中更新过的共享变量刷新到主内存中。
2、将主内存中最新的共享变量的值更新到工作内存 2 中。

那假如共享变量没有及时被其他线程看到的话,会发生什么问题呢?

public class Wanger {
    private static boolean chenmo = false;     public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (!chenmo) {
                }
            }
        });
        thread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        chenmo = true;     } }

这段代码的本意是:在主线程中创建子线程,然后启动它,当主线程休眠 500 毫秒后,把共享变量 chenmo 的值修改为 true 的时候,子线程中的 while 循环停下来。但运行这段代码后,程序似乎进入了死循环,过了 N 个 500 毫秒,也没有要停下来的意思。

为什么会这样呢?

因为主线程对共享变量 chenmo 的修改没有及时通知到子线程(子线程在运行的时候,会将 chenmo 变量的值拷贝一份放在自己的工作内存当中),当主线程更改了 chenmo 变量的值之后,但是还没来得及写入到主存当中,那么子线程此时就不知道主线程对 chenmo 变量的更改,因此还会一直循环下去。

换句话说,就是:普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主内存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

那怎么解决这个问题呢?

使用 volatile 关键字修饰共享变量 chenmo。

因为 volatile 变量被线程访问时,会强迫线程从主内存中重读变量的值,而当变量被线程修改时,又会强迫线程将最近的值刷新到主内存当中。这样的话,线程在任何时候总能看到变量的最新值。

我们来使用 volatile 修饰一下共享变量 chenmo。

private static volatile boolean chenmo = false;

再次运行代码后,程序在一瞬间就结束了,500 毫秒毕竟很短啊。在主线程(main 方法)将 chenmo 修改为 true 后,chenmo 变量的值立即写入到了主内存当中;同时,导致子线程的工作内存中缓存变量 chenmo 的副本失效了;当子线程读取 chenmo 变量时,发现自己的缓存副本无效了,就会去主内存读取最新的值(由 false 变为 true 了),于是 while 循环也就停止了。

也就是说,在某种场景下,我们可以使用 volatile 关键字来安全地共享变量。这种场景之一就是:状态真正独立于程序内地其他内容,比如一个布尔状态标志(从 false 到 true,也可以再转换到 false),用于指示发生了一个重要的一次性事件

至于 volatile 的原理和实现机制,本篇不再深入展开了(小编自己没搞懂,尴尬而不失礼貌的笑一笑)。

需要再次强调地是:

volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 相比,volatile 变量运行时地开销比较少,但是它所能实现的功能也仅是 synchronized 的一部分(只能确保可见性,不能确保原子性)。

原子性我们上一篇已经讨论过了,增量操作(i++)看上去像一个单独操作,但实际上它是一个由“读取-修改-写入”组成的序列操作,因此 volatile 并不能为其提供必须的原子特性。

除了 volatile 和 synchronized,Lock 也能够保证可见性,它能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。关于 Lock 的更多细节,我们后面再进行讨论。

好了,共享变量的可见性就先介绍到这。希望本篇文章能够对大家有所帮助,谢谢大家的阅读。

上一篇:如何保证共享变量的原子性?

下一篇:如何保证对象的线程安全性

Java 并发编程(三):如何保证共享变量的可见性?的更多相关文章

  1. Java并发编程三个性质:原子性、可见性、有序性

      并发编程 并发程序要正确地执行,必须要保证其具备原子性.可见性以及有序性:只要有一个没有被保证,就有可能会导致程序运行不正确  线程不安全在编译.测试甚至上线使用时,并不一定能发现,因为受到当时的 ...

  2. 【Java并发编程三】闭锁

    1.什么是闭锁? 闭锁(latch)是一种Synchronizer(Synchronizer:是一个对象,它根据本身的状态调节线程的控制流.常见类型的Synchronizer包括信号量.关卡和闭锁). ...

  3. JAVA 并发编程-线程范围内共享变量(五)

    线程范围内共享变量要实现的效果为: 多个对象间共享同一线程内的变量 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsi ...

  4. Java并发编程 (三) 项目准备

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.案例环境初始化 1.环境搭建与准备 Spring Boot 项目,https://start.spr ...

  5. Java并发编程(三):ReentrantLock

    ReentrantLock是可以用来代替synchronized的.ReentrantLock比synchronized更加灵活,功能上面更加丰富,性能方面自synchronized优化后两者性能没有 ...

  6. Java 并发编程(四):如何保证对象的线程安全性

    01.前言 先让我吐一句肺腑之言吧,不说出来会憋出内伤的.<Java 并发编程实战>这本书太特么枯燥了,尽管它被奉为并发编程当中的经典之作,但我还是忍不住.因为第四章"对象的组合 ...

  7. Java 并发编程(二):如何保证共享变量的原子性?

    线程安全性是我们在进行 Java 并发编程的时候必须要先考虑清楚的一个问题.这个类在单线程环境下是没有问题的,那么我们就能确保它在多线程并发的情况下表现出正确的行为吗? 我这个人,在没有副业之前,一心 ...

  8. 《Java并发编程实战》第三章 对象的共享 读书笔记

    一.可见性 什么是可见性? Java线程安全须要防止某个线程正在使用对象状态而还有一个线程在同一时候改动该状态,并且须要确保当一个线程改动了对象的状态后,其它线程能够看到发生的状态变化. 后者就是可见 ...

  9. [Java并发编程(三)] Java volatile 关键字介绍

    [Java并发编程(三)] Java volatile 关键字介绍 摘要 Java volatile 关键字是用来标记 Java 变量,并表示变量 "存储于主内存中" .更准确的说 ...

随机推荐

  1. Ubuntu18.04双系统下安装CUDA10+cuDNN7.5

    前言 本篇写于2019-4-25 这两天装Ubuntu18.04双系统简直装到崩溃.一是非常著名的开机卡死在Logo界面的问题,另一个是在装Nvidia驱动和CUDA的时候,更是费心.而网上的资料又良 ...

  2. 【Spring】 AOP Base

    1. AOP概述 2. AOP的术语: 3. AOP底层原理 4. Spring 中的AOP 4.1 概述 4.2 分类 4.3 Spring的传统AOP 针对所有方法的增强:(不带有切点的切面) 带 ...

  3. Java Web总结(一)-- 入门

    一.基本概念 1.1.WEB开发的相关知识 WEB,在英语中web即表示网页的意思,它用于表示Internet主机上供外界访问的资源. Internet上供外界访问的Web资源分为: 静态web资源( ...

  4. iptables的删除命令中的相关问题

    最近在做一个VPN中间件的配置工作,在配置iptables的时候,当用户想删除EIP(即释放当前连接),发现使用iptables的相关命令会提示错误. iptables: Bad rule (does ...

  5. 激光三角测量(sheet of light)halcon示例详解 Reconstruct_Connection_Rod_Calib.hdev 三维重建

    原文作者:aircraft 原文链接:https://www.cnblogs.com/DOMLX/p/11555100.html 前言:最近项目用到halcon的3d模板匹配,三维重建,相机标定,所以 ...

  6. servlet 的基础学习

    Servlet是sun公司提供的一门用于开发动态web资源的技术. Sun公司在其API中提供了一个servlet接口,用户若想用发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完 ...

  7. 会用python把linux命令写一遍的人,进大厂有多容易?

    看过这篇<2000字谏言,给那些想学Python的人,建议收藏后细看!>的读者应该都对一个命令有点印象吧?没错,就是 linux 中经常会用到的 ls 命令. 文章中我就提到如何提升自己的 ...

  8. 洛谷 P3868 [TJOI2009]猜数字

    题意简述 给定\(a[1],a[2],\cdots,a[n]\) 和 \(b[1],b[2],\cdots,b[n]\),其中\(b\)中元素两两互素. 求最小的非负整数\(n\),满足对于任意的\( ...

  9. [Advanced Python] 11 - Implement a Class

    基础概念:[Python] 08 - Classes --> Objects 进阶概念:[Advanced Python] 11 - Implement a Class 参考资源:廖雪峰,面向对 ...

  10. elasticsearch的分布式基础概念(1)

    Elasticsearch对复杂分布式机制的透明隐藏特性 Elasticsearch是一套分布式的系统,分布式是为了应对大数据量 隐藏了复杂的分布式机制 分片机制(随随便便就将一些document插入 ...