JAVA并发编程实战---第三章:对象的共享
在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。在缺乏足够同步的多线程程序中,要对内存操作的执行顺序进行判断几乎无法得到正确的结果。
非原子的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。
JAVA并发编程实战---第三章:对象的共享的更多相关文章
- Java并发编程实战 第3章 对象的共享
可见性 可见性是由于java对于多线程处理的内存模型导致的.这似乎是一种失败的设计,但是JVM却能充分的利用多核处理器的强大性能,例如在缺乏同步的情况下,Java内存模型允许编译器对操作顺序进行重排序 ...
- JAVA并发编程实战---第三章:对象的共享(2)
线程封闭 如果仅仅在单线程内访问数据,就不需要同步,这种技术被称为线程封闭,它是实现线程安全性的最简单的方式之一.当某个对象封闭在一个线程中时,这种方法将自动实现线程安全性,即使被封闭的对象本生不是线 ...
- Java并发编程实战 第4章 对象的组合
Java监视器模式 java监视器模式就是在将共享的数据封装在一个类里面,然后然后所有访问或者修改这些数据的方法都标注为synchronize. 车辆追踪模拟: 使用监视器模式: CarTracker ...
- 读书笔记-----Java并发编程实战(二)对象的共享
public class NoVisibility{ private static boolean ready; private static int number; private static c ...
- 【JAVA并发编程实战】1、对象的共享
1.栈封闭 在栈封闭中,只能通过局部变量才能访问对象. 所谓栈封闭就是把变量的声明以及应用都局限在一个局部线程中,在这个局部线程中声明和实例化的对象对于线程外部是不可见的,这个局部线程的栈,无法被任何 ...
- Java并发编程实战---第六章:任务执行
废话开篇 今天开始学习Java并发编程实战,很多大牛都推荐,所以为了能在并发编程的道路上留下点书本上的知识,所以也就有了这篇博文.今天主要学习的是任务执行章节,主要讲了任务执行定义.Executor. ...
- Java并发编程实战 第16章 Java内存模型
什么是内存模型 JMM(Java内存模型)规定了JVM必须遵循一组最小保证,这组保证规定了对变量的写入操作在何时将对其他线程可见. JMM为程序中所有的操作定义了一个偏序关系,称为Happens-Be ...
- 【java并发编程实战】第一章笔记
1.线程安全的定义 当多个线程访问某个类时,不管允许环境采用何种调度方式或者这些线程如何交替执行,这个类都能表现出正确的行为 如果一个类既不包含任何域,也不包含任何对其他类中域的引用.则它一定是无状态 ...
- Java并发编程实战 第8章 线程池的使用
合理的控制线程池的大小: 下面内容来自网络.不过跟作者说的一致.不想自己敲了.留个记录. 要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析: 任务的性质:CPU密集型任务.IO ...
随机推荐
- JavaScript对象的深浅复制
前言 从层次上来看,对象的复制可以简单地分为浅复制和深复制,顾名思义,浅复制是指只复制一层对象的属性,不会复制对象中的对象的属性,对象的深复制会复制对象中层层嵌套的对象的属性. 在复制对象时,除了要复 ...
- css3+js 实现砸金蛋效果
最近闲来无事,在网上看到有人写了个砸金蛋的效果,他是没有用到css3的,当时我就感觉没什么动态效果 感觉体验不是很好,所有我就想用css3来改下,于是也来试着写写. 本来想弄个视频给你们看看效果的,但 ...
- 关于C++ 循环
有的时分,可能需求屡次履行同一块代码.通常情况下,句子是顺序履行的:函数中的第一个句子先履行,接着是第二个句子,依此类推. 编程言语供给了答应更为杂乱的履行途径的多种操控结构. 循环句子答应咱们屡次履 ...
- 4 安装MPush
cnblogs-DOC 1.服务器环境 2.安装Redis3.安装Zookeeper4.安装MPush5.安装Alloc服务6.完整测试7.常见问题 一.Linux安装Mpush [root@loca ...
- 几个地图(高德、百度、Apple、Google)URL API
移动应用中,如何在自己的App中调起第三方的原生地图App,并显示相关的信息,如显示指定的一个坐标位置,显示一个起点到终点的路线查询,等等. 目前几个主要的地图商都提供了自己的App通过URL调用的形 ...
- AFNetworking 用法详解
之前一直使用ASIHttpRequest 做网络请求 ,后来新公司用AFNetWorking ,经过一段时间学习总结一下二者的优缺点: 1.AFNetWorking的优缺点 优点: 1.维护和使用者比 ...
- (原)HashMap之java8新特性
首先说一下HashMap存储结构,数组.链表.树这三种数据结构形成了hashMap.存储结构下图所示,根据key的hash与table长度确定table位置,同一个位置的key以链表形式存储,超过一定 ...
- 超炫的 CSS3 页面切换动画效果
在线演示 源码下载
- codeforces 528D Fuzzy Search
链接:http://codeforces.com/problemset/problem/528/D 正解:$FFT$. 很多字符串匹配的问题都可以用$FFT$来实现. 这道题是要求在左边和右边$k$个 ...
- Linux系统优化
前言:这篇博客主机讲下安装Linux系统后调优及安全设置 基础环境 一.使用网易163镜像做yum源 默认国外的yum源速度很慢,所以换成国内的. 先备份 下载163yum源:http://mirro ...