[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 stub13        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

时间: 2024-10-29 09:12:35

[Java并发编程实战] 共享对象之可见性的相关文章

JAVA并发编程学习笔记------对象的可见性及发布逸出

一.非原子的64位操作: 当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但至少这个值是由之前某个线程设置的值,而不是一个随机值,这种安全性保证被称为最低安全性.最低安全性适用于绝大多数变量,但存在一个例外:非volatile类型的64位数值变量(double,long),Java内存模型要求,变量的读取和写入操作都必须是原子操作,但对于非volatile型的long,double变量,JVM允许将64位的读操作或写操作分解为两个32位的操作,当读取一个非volatile类型的long

JAVA并发编程实战 读书笔记(二)对象的共享

<java并发编程实战>读书摘要 birdhack 2015年1月2日 对象的共享 JAVA并发编程实战读书笔记 我们已经知道了同步代码块和同步方法可以确保以原子的方式执行操作,但一种常见的误解是,认为关键之synchronized只能用于实现原子性或者确定临界区.同步还有另一个重要的方面:内存可见性. 1.可见性 为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制. 在没有同步的情况下,编译器.处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整.在缺乏足够同步的多线程程

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

一.可见性 什么是可见性? Java线程安全须要防止某个线程正在使用对象状态而还有一个线程在同一时候改动该状态,并且须要确保当一个线程改动了对象的状态后,其它线程能够看到发生的状态变化. 后者就是可见性的描写叙述即多线程能够实时获取其它线程改动后的状态. *** 待补充   两个工人同一时候记录生产产品总数问题 1. 失效数据 可见性出现故障就是其它线程没有获取到改动后的状态,更直观的描写叙述就是其它线程获取到的数据是失效数据. 2. 非原子64位操作 3. 加锁与可见性 比如在一个变量的读取与

《Java并发编程实战》第十一章 性能与可伸缩性 读书笔记

造成开销的操作包括: 1. 线程之间的协调(例如:锁.触发信号以及内存同步等) 2. 增加的上下文切换 3. 线程的创建和销毁 4. 线程的调度 一.对性能的思考 1 性能与可伸缩性 运行速度涉及以下两个指标: 某个指定的任务单元需要"多快"才能处理完成.计算资源一定的情况下,能完成"多少"工作. 可伸缩性: 当增加计算资源时(例如:CPU.内存.存储容器或I/O带宽),程序的吞吐量或者处理能力能相应地增加. 2 评估各种性能权衡因素 避免不成熟的优化.首先使程序正

《Java并发编程实战》要点笔记及java.util.concurrent 的结构介绍

买了<java并发编程实战>这本书,看了好几遍都不是很懂,这个还是要在实战中找取其中的要点的,后面看到一篇文章笔记做的很不错分享给大家!! 原文地址:http://blog.csdn.net/cdl2008sky/article/details/26377433 Subsections  1.线程安全(Thread safety) 2.锁(lock) 3.共享对象 4.对象组合 5.基础构建模块 6.任务执行 7.取消和关闭 8.线程池的使用 9.性能与可伸缩性 10.并发程序的测试 11.显

【java并发编程实战】-----线程基本概念

学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习Java并发编程,共同进步,互相指导. 在学习Java并发之前我们需要先理解一些基本的概念:共享.可变.线程安全性.线程同步.原子性.可见性.有序性. 共享和可变 要编写线程安全的代码,其核心在于对共享的和可变的状态进行访问. "共享"就意味着变量可以被多个线程同时访问.我们知道系统中的资

《Java并发编程实战》读书笔记

Subsections 线程安全(Thread safety) 锁(lock) 共享对象 对象组合 基础构建模块 任务执行 取消和关闭 线程池的使用 性能与可伸缩性 并发程序的测试 显示锁 原子变量和非阻塞同步机制 一.线程安全(Thread safety) 无论何时,只要多于一个线程访问给定的状态变量.而且其中某个线程会写入该变量,此时必须使用同步来协助线程对该变量的访问. 线程安全是指多个线程在访问一个类时,如果不需要额外的同步,这个类的行为仍然是正确的. 线程安全的实例: (1).一个无状

《Java并发编程实战》/童云兰译【PDF】下载

<Java并发编程实战>/童云兰译[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062521 内容简介 本书深入浅出地介绍了Java线程和并发,是一本完美的Java并发参考手册.书中从并发性和线程安全性的基本概念出发,介绍了如何使用类库提供的基本并发构建块,用于避免并发危险.构造线程安全的类及验证线程安全的规则,如何将小的线程安全类组合成更大的线程安全类,如何利用线程来提高并发应用程序的吞吐量,如何识别可并行执行的任务,如何提高单线程子

《java并发编程实战》笔记(一)

最近在看<java并发编程实战>,希望自己有毅力把它读完. 线程本身有很多优势,比如可以发挥多处理器的强大能力.建模更加简单.简化异步事件的处理.使用户界面的相应更加灵敏,但是更多的需要程序猿面对的是安全性问题.看下面例子: public class UnsafeSequence { private int value; /*返回一个唯一的数值*/ public int getNext(){ return value++; } } UnsafeSequence的问题在于,如果执行时机不对,那么