当前位置: 首页 > news >正文

[Java并发编程实战] 共享对象之可见性

「 盛年不重来,一日难再晨,及时当勉励,岁月不待人。」  陶渊明

我们已经知道同步代码块和同步方法可以保证以原子的方式执行,其实,同步还有另外一个重要概念:内存可见性。换句话说,我们不仅希望防止某个线程正在使用对象状态而另一个线程同时在修改状态,而且希望确保当一个线程修改了对象的状态后,其他线程能够看到修改后的状态。

可见性

一个线程对共享变量值的修改,能够及时的被其他线程看到。可见性微妙的,这是因为可能发生错误的事情总是与直觉大相径庭。来看下面这个例子和他的执行结果:

 1public class NoVisibility {
2 private static boolean ready;
3 private static int number;
4 private static class ReaderThread extends Thread {
5 public void run() {
6 while(!ready)
7 Thread.yield();
8 System.out.println(number);
9 }
10 }
11 public static void main(String[] args) {
12 // TODO Auto-generated method stub
13 new ReaderThread().start();
14 number = 88;
15 ready = true;
16 }
17}

上面的代码清单,亲测执行的结果是88。
然而,书本上的解释是可能出现错误的结果。错误的结果有下面两种情况(我重现不到下面的结果):

  1. NoVisibility 可能会一直保持循环,因为对读线程来说,主线程写给 ready 的值可能永远对读线程不可见。
  2. NoVisibility 可能会打印0,因为早在对 number 赋值之前,主线程就已经写入 ready 并使之对读线程可见,这是一种重排序。

即可亲测没有发生,但是可能会发生。为了防止这种现象的发生,只能通过对共享变量进行恰当的同步。

Java 内存模型(JMM,Java Memory Model)

描述了 java 程序中各种变量(线程共享变量)的访问规则,以及在 JVM 中将变量存储到内存和从内存中读取出变量的底层细节。

这里写图片描述这里写图片描述

所有变量都存储在主内存中,每个线程都有自己独立的工作内存,里面保存该线程使用到的变量副本,即主内存中该变量的一份拷贝。

线程对共享变量的所有操作必须在自己的工作内存,线程间变量值的传递需要通过主内存来完成。

加锁与可见性

加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作都必须在同一个锁上同步。

这里写图片描述这里写图片描述

当线程 B 执行有锁保护的代码块时,可以看到线程 A 之前在同一个同步代码块中所有的操作结果。这就是为啥要求所有线程在同一个锁上同步,为了确保某个线程写入该变量的值对于其他线程来说是可见的。

非原子的64位操作

JVM 允许将64位的读操作或写操作分解为两个32位的操作。Java 中的 long 类型和 double 类型是64位的,所以当读取一个非 volatile 类型的 long 变量时,如果该变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值的高32位和另一个值的低32位。因此,在多线程中使用共享的可变的 long 和 double 类型变量时不安全的,除非用关键字 volatile 来声明他们,或者用锁保护起来。

volatile变量

Java 提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程。volatile 变量具有 synchronized 的可见性,但是不具备原子特性。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

  • 对变量的写操作不依赖于自身当前值
  • 该变量没有包含在具有其他变量的不变式中

volatile 通常被当做标识完成、中断、状态的标记使用。典型应用如下代码,检查状态标记,以确定是否退出一个循环。

1volatile boolean asleep;
2 while(!asleep)
3 countSomeSheep();

当然,上面也可以用锁,但是会让代码变得复杂。volatile 变量不会加锁,也就不会引起线程的阻塞,相比 sychronized, 这只是轻量级的同步机制。尽管 volatile 也可以用来标识其他类型的状态信息,但是要格外小心。比如, volatile 的语义不足以使自增操作(count++)原子化。

转载于:https://www.cnblogs.com/seaicelin/p/9128040.html

相关文章:

  • Java实用类库
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • 『TensorFlow』线程控制器类变量作用域
  • Git漏洞导致攻击者可在用户电脑上运行任意代码
  • [译] 不用祖传秘方 - 写好代码的几个小技巧
  • el-input获取焦点 input输入框为空时高亮 el-input值非法时
  • 安装Cassandra数据库和访问客户端配置
  • CSS中background-position使用技巧
  • java调用IPFS去中心化体系
  • Scrapy 1.5.0之基础入门
  • OSChina 周一乱弹 —— 你老婆和闺蜜总用奇怪的眼神看着你
  • Linux 进程后台运行的几种方式 screen
  • ES6系列--4. 对象的扩展
  • 技术沙龙|风口之下,经验丰富的“传统开发者”要不要转型区块链开发?(西安)...
  • 中兴智能视觉大数据报道:至2020年人脸识别市场规模增速为166.6%
  • [译]如何构建服务器端web组件,为何要构建?
  • __proto__ 和 prototype的关系
  • Android路由框架AnnoRouter:使用Java接口来定义路由跳转
  • co.js - 让异步代码同步化
  • CSS3 变换
  • Intervention/image 图片处理扩展包的安装和使用
  • PAT A1017 优先队列
  • Python_OOP
  • Terraform入门 - 3. 变更基础设施
  • Vue学习第二天
  • 测试如何在敏捷团队中工作?
  • 工作手记之html2canvas使用概述
  • 记一次和乔布斯合作最难忘的经历
  • 类orAPI - 收藏集 - 掘金
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • 线性表及其算法(java实现)
  • 译有关态射的一切
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • Mac 上flink的安装与启动
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • ​Spring Boot 分片上传文件
  • ​ubuntu下安装kvm虚拟机
  • ​一文看懂数据清洗:缺失值、异常值和重复值的处理
  • # Apache SeaTunnel 究竟是什么?
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • #Java第九次作业--输入输出流和文件操作
  • #我与Java虚拟机的故事#连载19:等我技术变强了,我会去看你的 ​
  • (Java)【深基9.例1】选举学生会
  • (Redis使用系列) Springboot 使用redis的List数据结构实现简单的排队功能场景 九
  • (实战篇)如何缓存数据
  • .bat批处理(五):遍历指定目录下资源文件并更新
  • .describe() python_Python-Win32com-Excel
  • .NET C# 使用 SetWindowsHookEx 监听鼠标或键盘消息以及此方法的坑
  • .NET Core 2.1路线图
  • .NET I/O 学习笔记:对文件和目录进行解压缩操作
  • .Net mvc总结
  • .NET 中使用 TaskCompletionSource 作为线程同步互斥或异步操作的事件
  • .netcore 6.0/7.0项目迁移至.netcore 8.0 注意事项