Java并发编程(四)可见性
除了使用synchronized关键字用于实现原子性或者确定"临界区(Critical Section)",还有一个重要的方面就是:内存的可见性(Memory Visibility)。我们不仅希望防止某个线程正在使用对象状态而另一个线程在同时修改状态,而且希望确保当一个线程修改了某个对象的状态后,其他线程能够看到状态变化。如果没有同步,那么这种情况也无法实现。
可见性
在单线程环境中,如果向某个变量先写入了值,然后在没有其他写入操作的情况下读取这个变量,那么总能得到相同的值。
然而读操作和写操作在不同的线程中执行时,情况却并非如此。
通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值。为了确保多个线程之间堆内存写入操作的可见性,必须使用同步机制。
只要在某个线程中无法检测到重排序情况(即使其他线程中可以很明显地看到线程中的排序),那么就无法确保线程中的操作按照程序指定的顺序来执行。
在没有同步的情况下,编译器、处理器以及运行时都有可能对操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要想对内存操作的执行顺序进行判断,几乎无法得出正确的结论。
失效的数据可能导致输出错误的值,或者使程序无法结束。如果对象的引用(例如链表的指针)失效,那么情况会更加复杂。失效的数据还可能导致一些令人困惑的故障,例如意料之外的异常、被破坏的数据结构、不精确的计算以及无限循环。
当线程在没有同步的情况下读取变量时,可能会得到一个失效的值,但至少这个值是由之前某个线程设置的值,而不是一个随机值。这种安全保证也被称为最低安全性(out-of-thin-air safety)。
最低安全性适用于绝大多数变量,但是存在一个例外:非volatile类型的64位数值变量(double和long)。Java内存模型要求,变量的读取操作和写入操作必须是原子操作,但对于非volatile类型的long和double变量,JVM允许将64位的读操作或写操作分界成为两个32位的操作。当读取一个非volatile类型的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么很有可能会读取到某个值的高32位和另一个值的低32位。
加锁和可见性
内置锁可以确保某个线程以一种可预测的方式来查看另一个线程的执行结果,如下图所示。当线程A执行某个同步代码块时,线程B随后进入由同一个锁保护的同步代码块,这种情况下可以保证,在锁被释放之前,A看到的变量值在B获得锁后同样可以由B看到。换句话说,当线程B执行由锁保护的同步代码块时,可以看到线程A之前在同一个同步代码块中的所有操作结果。如果没有同步,那么就无法实现上述保证。
现在我们可以理解了为什么在访问某个共享且可变的变量时要求所有线程在同一个锁上同步,就是为了确保某个线程写入该变量的值对于其他线程来说是可见的。否则如果一个线程在未持有正确锁的情况下读取某个变量,那么读到的可能是一个失效值。
volatile变量
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
另一个理解volatile的有效方法就是,把对volatile进行读写的操作都当成是同步的get和set方法。但是volatile的好处是,访问volatile变量不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile是一种比synchronized更轻量级的同步机制。
(在这里终于把volatile关键字理解了,泪目了)
volatile变量对可见性的影响比volatile变量本身更为重要。当线程A首先写入一个volatile变量并且线程B随后读取该变量时,在写入volatile变量之前对A可见的所有变量的值,在B读取了volatile变量后,对B也是可见的。因此,从内存可见性的角度来看,写入volatile变量相当于退出同步代码块,而读取volatile变量就相当于进入同步代码块。(不建议过度依赖volatile提供的可见性,这种方式比同步更脆弱,更加难以理解。)
仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它们。如果在验证正确性时需要对可见性进行复杂的判断,那么就不要使用volatile变量。volatile变量的正确使用方式包括:确保它们自身状态的可见性,确保它们所引用对象的状态的可见性,以及标识一些重要的程序生命周期事件的发生(例如,初始化或关闭)。
调试小提示:对于服务器应用程序,无论在开发阶段还是在测试阶段,当启动JVM时一定要指定-server命令行选项。server模式的JVM将比client模式的JVM进行更多的优化,例如将循环中未被修改的变量提升到循环外部,因此在开发环境中能正确运行的代码,可能会在部署环境(server模式的JVM)中运行失败。
虽然volatile变量很方便,但也存在一些局限性。volatile变量通常用作某个操作完成、发生中断或者状态的标志。尽管volatile变量也可以用于表示其他的状态信息,但在使用时要非常小心。例如,volatile的语义不足以确保递增操作(count++)的原子性。
当满足以下所有条件时,才应该使用volitile变量:
- 对变量的写入操作不依赖变量当前的值,或者你能确保只有单个线程更新变量的值。
- 该变量不会与其他状态变量一起纳入不变性条件中。
- 访问变量时不需要加锁。
感觉还要看一下JVM指令重排
Java并发编程(四)可见性的更多相关文章
- Java 并发编程(四):如何保证对象的线程安全性
01.前言 先让我吐一句肺腑之言吧,不说出来会憋出内伤的.<Java 并发编程实战>这本书太特么枯燥了,尽管它被奉为并发编程当中的经典之作,但我还是忍不住.因为第四章"对象的组合 ...
- 【Java并发编程四】关卡
一.什么是关卡? 关卡类似于闭锁,它们都能阻塞一组线程,直到某些事件发生. 关卡和闭锁关键的不同在于,所有线程必须同时到达关卡点,才能继续处理.闭锁等待的是事件,关卡等待的是其他线程. 二.Cycli ...
- Java并发编程-线程可见性&线程封闭&指令重排序
一.指令重排序 例子如下: public class Visibility1 { public static boolean ready; public static int number; } pu ...
- Java并发编程 (四) 线程安全性
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.线程安全性-原子性-atomic-1 1.线程安全性 定义: 当某个线程访问某个类时,不管运行时环境 ...
- Java并发编程-volatile可见性的介绍
要学习好Java的多线程,就一定得对volatile关键字的作用机制了熟于胸.最近博主看了大量关于volatile的相关博客,对其有了一点初步的理解和认识,下面通过自己的话叙述整理一遍. 有什么用? ...
- Java并发编程(四):并发容器(转)
解决并发情况下的容器线程安全问题的.给多线程环境准备一个线程安全的容器对象. 线程安全的容器对象: Vector, Hashtable.线程安全容器对象,都是使用 synchronized 方法实现的 ...
- [Java并发编程(四)] Java volatile 的理论实践
[Java并发编程(四)] Java volatile 的理论实践 摘要 Java 语言中的 volatile 变量可以被看作是一种 "程度较轻的 synchronized":与 ...
- Java并发编程:Java的四种线程池的使用,以及自定义线程工厂
目录 引言 四种线程池 newCachedThreadPool:可缓存的线程池 newFixedThreadPool:定长线程池 newSingleThreadExecutor:单线程线程池 newS ...
- Java并发编程原理与实战四十二:锁与volatile的内存语义
锁与volatile的内存语义 1.锁的内存语义 2.volatile内存语义 3.synchronized内存语义 4.Lock与synchronized的区别 5.ReentrantLock源码实 ...
- 《java并发编程实战》读书笔记2--对象的共享,可见性,安全发布,线程封闭,不变性
这章的主要内容是:如何共享和发布对象,从而使它们能够安全地由多个线程同时访问. 内存的可见性 确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化. 上面的程序中NoVisibility可能 ...
随机推荐
- java的classpath和path理解
1)classpath即是类路径的意思,后缀为.java的源程序编译成功之后会生成后缀为.class的类文件.classpath与import关键字是相关的,程序编译的时候,遇到import关键字,编 ...
- HTML5 boilerplate 笔记(转)
最近在研究HTML5 boilerplate的模版,以此为线索可以有条理地学习一些前端的best practice,好过在W3C的文档汪洋里大海捞针……啊哈哈哈…… 开头的IE探测与no-js类是什么 ...
- Java 图片添加水印效果
package com.xiaowu.drawwater.demo; import java.awt.AlphaComposite; import java.awt.Graphics2D; impor ...
- Virtualbox环境中安装Oracle 11gr2 RAC(ASM)
系统Oracle Linux 6.5,Oracle 11.2.0.1 终于开始安装ASM和RAC的行程了.开始前需要想清楚的几个事情: 如何规划网络配置(配置多网卡,实现连通性,规划内外网,eth0, ...
- Calendar抽象类返回自己和Integer.TYPE和int.class
public class Calend { public static void main(String[] args) { Calendar cal=Calendar.getInstance();/ ...
- [Javascript] Use a Pure RNG with the State ADT to Select an Element from State
The resultant in a State ADT instance can be used as a means of communication between different stat ...
- Windows如何自定义U盘盘符、文件夹图标、文件夹背景
自定义U盘盘符.文件夹图标.文件夹背景 注意对于Vista和Win7的用户不支持文件夹图标和文件夹背景的更换 1.自定义盘符:在U盘根目录下新建文件 autorun.inf(可先建.txt文本文档,再 ...
- CMake 设置Target输出目录和后缀名
一. SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../bin) 上面的语句能设置可执行文件的输出目录 在Win + VS环境下,会自动在你所设置 ...
- axios 和 vue-axios
1.main.js 全局引入 /** * 引入 axios 数据请求方式 * axios 和 VueAxios 必须同时注入 */ import axios from 'axios' import V ...
- JavaScript内存示意图
一.JavaScript内存示意图 二.js原理 1.js执行规则 (1)先定义 定义变量.定义函数.顺序:自上而下. (2)后执行 除了定义外的代码都是执行代码.顺序:自上而下. 2.动态开辟内存 ...