当前位置: 首页 > 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%
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • download使用浅析
  • Java程序员幽默爆笑锦集
  • js ES6 求数组的交集,并集,还有差集
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • Unix命令
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • 从零开始学习部署
  • 分布式熔断降级平台aegis
  • 搞机器学习要哪些技能
  • 基于Volley网络库实现加载多种网络图片(包括GIF动态图片、圆形图片、普通图片)...
  • 技术发展面试
  • 解析带emoji和链接的聊天系统消息
  • 前端每日实战 2018 年 7 月份项目汇总(共 29 个项目)
  • 前言-如何学习区块链
  • 嵌入式文件系统
  • 如何选择开源的机器学习框架?
  • 如何正确配置 Ubuntu 14.04 服务器?
  • 一个SAP顾问在美国的这些年
  • “十年磨一剑”--有赞的HBase平台实践和应用之路 ...
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • 蚂蚁金服CTO程立:真正的技术革命才刚刚开始
  • #if 1...#endif
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (Redis使用系列) Springboot 使用redis实现接口Api限流 十
  • (zhuan) 一些RL的文献(及笔记)
  • .gitignore
  • .net 4.0发布后不能正常显示图片问题
  • .NET Core引入性能分析引导优化
  • .Net Redis的秒杀Dome和异步执行
  • .NET 反射 Reflect
  • .Net 路由处理厉害了
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • @德人合科技——天锐绿盾 | 图纸加密软件有哪些功能呢?
  • [2544]最短路 (两种算法)(HDU)
  • [Android View] 可绘制形状 (Shape Xml)
  • [APUE]进程关系(下)
  • [C++]指针与结构体
  • [Dxperience.8.*]报表预览控件PrintControl设置