在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要对内存操作的执行顺序进行判断几乎无法得到正确的结果。

非原子的64位操作

  当线程在没有同步的情况下读取变量时,可能会读到一个失效值,但至少这个值是由之前的某个线程设置,而不是一个随机值。这种安全性保证也被称为最低安全性。

  Java内存模型要求:变量的读取操作和写入操作都必须是原子操作,但对于非Volatile类型的long和Double变量,JVM允许将64的读操作或写操作,分解成两个32位的操作。因此,及时不考虑失效数据问题,在多线程中使用共享且可变的long或double等类型的变量也是不安全的,除非使用Volatile关键字或锁保护起来。

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

Volatile

  Volatile变量用来确保将变量的更新操作通知到其他线程。当把变量申明为Volatile类型后,编译器与运行时都会注意到这个变量是共享变量,因此不会讲该变量上的操作与其他内存操作一起重排序。Volatile变量不会被缓存在寄存器或者其他处理器不可见的地方,一次读取Volatile类型的变量总是会返回最新写入的值。

  加锁机制既可以确保可见性又可以确保原子性,而Volatile变量只能确保可见性。

  当且仅当满足以下所有条件时,才应该使用Volatile变量:

  • 对变量的写入操作不依赖与变量的当前值,或者能确保只有单个线程更新变量的值。
  • 该变量不会与其他状态变量一起纳入不变性条件中。
  • 在访问变量时不需要加锁。

  对象的发布与逸出

  发布:“发布”一个对象是指,使对象能够在当前作用域之外的代码中使用。

  例如:将一个指向该对象的引用保存到其他代码可以访问的地方,或者在某一个非私有的方法中返回该对象的引用,或者将引用传递到其他类的方法中。

  代码示范:将一个指向该对象的引用,保存到其他代码可以访问的地方。

// 保存在一个共有的静态变量中
public static Set<Secret> knowSecrets;
public void initialize(){ knowSecrets = new HashSet<Secret>(); }

  逸出:“逸出”是指,某个不应该发布的对象被发布出去。

  当发布某个对象时,可能会间接地发布其他对象。例如上述代码,若将一个 Secret 对象添加到 knownSecret 中,那么会发布这个 Secret 对象;因为任何代码都可以遍历这个集合,并获得对  Secret 对象的引用。同样,如果从非私有方法中返回一个引用,则会发布返回对象。

  代码示范:某一个非私有的方法中返回私有对象的引用。

class UnsafeStates{private String[] states = new String[]{"AK","AL"};public String[] getStates(){ return states; } }

  上述代码中,数组 states 已经逸出了它所在的作用域,因为这个本应是私有的变量,已经被发布了。

  逸出范围:当一个对象A被发布时,在A的非私有域中引用的所有对象同样会被发布。也就是,一个已经发布的对象A,能够通过非私有的变量引用和方法调用到达其他的对象,那么所能够到达的对象,均会跟随A一起发布。

  此处给出一个定义:“外部方法”。假如有一个类C,对于C来说,“外部方法”是指行为不完全由C来规定的方法,包括其他类中定义的方法以及类C中可以被改写的方法(既不是[private]方法,也不是[final]方法)。

  当把一个对象传递给外部方法时,则该对象就会面临一定的危险,因为你不知道外部方法会对该对象做些什么,因此我们需要使用封装。封装能够使得对程序的正确性进行分析变得可能,并使得无意中破坏设计约束条件变得困难。

  工厂方法避免this引用在构造方法中逸出:

  首先了解 this 引用是如何在构造方法中逸出的。先看一段代码:发布一个内部类的实例。

public class ThisEscape{public ThisEscape(EventSource source){source.registerListener(new EventListener(){ public void onEvent(Event e){ doSomething(e); } }); } }

  上述代码中,当 ThisEscape 发布 EventListener 时,也隐含发布了 ThisEscape 实例本身,因为这个内部类的实例中包含了对 ThisEscapse 实例的隐含引用。只要其他线程在ThisEscape未构造之前(构造返回状态)调用这个类,那么this就会被新建线程共享并识别它(线程溢出)。

  下面的代码对上面的示例进行解释:

public class ThisEscape {int i = 100; public ThisEscape(int j){ new Thread(new Runnable() { @Override public void run() { System.out.println(i + j); } }).start(); } public static void main(String[] args) { ThisEscape thisE = new ThisEscape(100); } }

  上述代码给出了逸出的一个特殊示例,即this引用在构造方法中逸出。当内部的new Thread发布时,在外部封装的ThisEscape实例也逸出了,于是可以取到 i 值与 j 进行计算。因此当从对象的构造方法中发布对象时,只是发布了一个尚未构造完成的对象。

  在构造方法中使用 this 引用逸出的常见错误:在构造方法中启动一个线程;在构造方法中调用一个可改写的实例方法。

  如果想在构造方法中注册一个事件监听器或启动线程,那么可以使用一个私有的构造方法和一个公共的工厂方法,从而避免不必要的构造过程。如以下程序所示:

public class SafeListener{private final EventListener listener; private SafeListener(){ listener = new EventListener(){ public void onEvent(Event e){ doSomething(e); } } } public static SafeListener newInstance(EventSource source){ SafeListener safe = new SafeListener(); source.registerListener(safe.listener); return safe; } }

  上面的代码为在构造方法中注册一个事件监听器,新建的线程无法在构造方法之前共享和识别 safe。

转载于:https://www.cnblogs.com/wxgblogs/p/5462869.html

JAVA并发编程实战---第三章:对象的共享相关推荐

  1. java并发编程实战-第三章-对象的共享

    3.1可见性 首先我们需要知道的是,java的线程都有自己独立的缓存,线程之间进行共享变量的交互是通过自身和缓存和主存的交互实现的. 如果线程的每次更改缓存都刷入主存,主存每次被一个线程的缓存修改,都 ...

  2. JAVA并发编程实战---第三章:对象的共享(2)

    线程封闭 如果仅仅在单线程内访问数据,就不需要同步,这种技术被称为线程封闭,它是实现线程安全性的最简单的方式之一.当某个对象封闭在一个线程中时,这种方法将自动实现线程安全性,即使被封闭的对象本生不是线 ...

  3. Java并发编程实战 第4章 对象的组合

    Java监视器模式 java监视器模式就是在将共享的数据封装在一个类里面,然后然后所有访问或者修改这些数据的方法都标注为synchronize. 车辆追踪模拟: 使用监视器模式: CarTracker ...

  4. 【JAVA并发编程实战】1、对象的共享

    1.栈封闭 在栈封闭中,只能通过局部变量才能访问对象. 所谓栈封闭就是把变量的声明以及应用都局限在一个局部线程中,在这个局部线程中声明和实例化的对象对于线程外部是不可见的,这个局部线程的栈,无法被任何 ...

  5. Java并发编程实战笔记2:对象的组合

    设计线程安全的类 在设计现车让安全类的过程之中,需要包含以下三步: 找出构成对象状态的所有变量 找出约束状态变量的不变性条件 建立对象状态的并发访问策略 实例封闭 通过封闭机制与合适的加锁策略结合起来 ...

  6. Java并发编程实战 第14章 构建自定义的同步工具

    状态依赖性 定义:只有满足特定的状态才能继续执行某些操作(这些操作依赖于固定的状态,这些状态需要等待别的线程来满足). FutureTask,Semaphroe,BlockingQueue等,都是状态 ...

  7. java并发编程实战wwj----------第三阶段-------------CompletableFuture---------------56-59

    代码: 我找的博客:https://www.cnblogs.com/happyliu/archive/2018/08/12/9462703.html 第二段代码: 这里设置的是守护线程. 代码: pa ...

  8. Java并发编程实战 第13章 显式锁

    接口Lock的实现类: ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock Reentra ...

  9. java并发编程实战wwj----------第三阶段-------------ConcurrentHashMap----------------73

    红黑树的总结:https://www.jianshu.com/p/5dbaa6707017 链表+数组+红黑树. 首先看下node的数据结构: static class Node<K,V> ...

最新文章

  1. 小项目--bank1
  2. oraclesqldeveloper 批量插入多个存储过程_MongoDB如何批量执行写操作
  3. 《零基础》MySQL 数据类型(八)
  4. 创建预编译头 Debug 正常 Release Link Error:预编译头已存在,使用第一个 PCH
  5. 时间管理术第一原则总结
  6. signature=995eb8e443ef674d51fa76dabc7ac89c,我國7-8歲學童動作協調能力之初探
  7. 数组、字典转json串,不支持字符串
  8. xp系统计算机无法用搜索功能,XP系统搜索功能无法使用的六种解决方法
  9. 适合程序员的简历模板
  10. LWM2M,MQTT与CoAP区别和联系
  11. Java垃圾回收的时间点
  12. pkl形式的数据集读取和可视化
  13. 私网地址与公网地址是如何转换的?
  14. PCL库学习笔记——使用变换矩阵变换点云
  15. SSM开发相关安装教程(idea、tomcat、maven、DB)
  16. 已拦截跨源请求:同源策略禁止读取位于...的远程资源。(原因:CORS 请求未能成功)。
  17. 【评测】无血清细胞冻存液
  18. 愚人节恶搞网站谨防遭黑客攻击
  19. 吐槽各大竞赛题库的优劣
  20. python统计套利_期货市场内外盘低频统计套利基于Python

热门文章

  1. 《JavaScript数据可视化编程》——1.4 用离散图表绘制x/y值
  2. Linux指定网卡工作模式
  3. Intent中的四个重要属性——Action、Data、Category、Extras
  4. Linux系统分析之启动流程
  5. 右键命令行在这里-cmd here
  6. Windows 10 [ ERROR ] Can not init Myriad device: NC_ERROR Error
  7. OpenCV Harris角点检测
  8. C语言 二进制文件读取和写入
  9. 嵌入式linux如何下载程序,Linux平台的下载程序-嵌入式系统-与非网
  10. 【源码解析】HashMap源码跟进(红黑树的实现)