这章的主要内容是:如何共享和发布对象,从而使它们能够安全地由多个线程同时访问。

内存的可见性

确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。

上面的程序中NoVisibility可能会持续循环下去,因为读线程可能永远都看不到ready的值。一种更奇怪的现象是NoVisibility可能会输出0,因为读线程可能看到了写入ready的值,但却没有看到之后写入number的值,这种现象被称为“重排序”。多线程之指令重排序

失效数据

简而言之就是在缺乏同步的程序中可能会读取到过期的数据,也就是失效数据,就像上面的例子一样,当度线程查看ready变量时可能会的得到一个失效的值。

非原子的64位操作

虽然得到的可能是一个失效值,但至少这个值是由之前每个线程设置的,而不是一个随机值。这种安全性保证也被称为最低安全性。最低安全性适用于绝大多数变量,但是对于非volatile类型的64位数值变量(double和long)并非如此。java变量的读操作和写操作都是原子的,但是JVM允许将64位的读操作和写操作分解为2个32为的操作。这样当读取一个非volatile的long变量时,如果对该变量的读操作和写操作在不同的线程中执行,那么很可能会读取到某个值的高32为和另一个值的低32位。

加锁与可见性

Volatile变量

确保将变量的更新操作通知到其他线程,当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。在读取volatile类型的变量时总会返回最新的写入值。下面的程序给出了volatile变量的一种典型用法:检查每个状态标记以判断是否退出循环。

volatile的语义不足以确保递增操作(count++)的原子性。当且仅当满足一下所有条件是,才应该使用volatile变量:

1. 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值

2. 该变量不会与其他状态变量一起纳入不变性条件中

3. 在访问变量时不需要加锁

Java中Volatile关键字详解&&Java并发编程:volatile关键字解析

发布与逸出

“发布”一个对象是指:使对象能够在当前作用域之外的代码中使用。当某个不该发布的对象被发布时,这种情况就被称为逸出。发布对象的最简单方法是将对象的引用保存到一个公有的静态变量中;

安全的对象构造过程

不要在构造过程中使this引用逸出。在构造过程中使this引用逸出的一个常见的错误是:在构造函数中启动一个线程。当对象在其构造函数中创建一个线程时,无论是显示创建还是隐士创建,this引用都会被新创建的线程共享。(简而言之在对象的构造函数中的别的对象能够拿到当前对象的this引用从而造成逸出)。

线程封闭

不共享数据,仅在单线程内访问数据,避免使用同步的方式。这种技术被大量使用喻Swing和JDBC的Connection对象。

Ad-hoc线程封闭

指维护线程封闭性的职责完全由程序实现来承担。很脆弱,尽量使用更强的线程封闭技术(如栈封闭或ThreadLocal类)

栈封闭

只能通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行线程中。他们位于执行线程的栈中,其他线程无法访问这个栈。

ThreadLocal类

这个类能使线程中的某个值与保存值的对象关联起来。ThisLocal提供了get和set等访问接口或方法,当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。ThreadLocal对象通常用于防止对可变的单实例或全局变量进行共享。通过将JDBC的连接保存到ThreadLocal对象中(因为JDBC的连接对象不一定是线程安全的),每个线程都会有属于自己的连接。

当某个频繁执行的操作需要一个临时对象,例如缓冲区,同时又希望避免在每次执行时都重新分配该临时对象,就可以使用这项技术。当某个线程初次调用ThreadLocal.get方法时,救会调用initialValue来获取初始值。

不变性

满足同步需求的另一种方法是使用不可变对象,即对象创建以后状态不能修改,它的所有域都是final类型,并且对象是正确创建的(this引用没有逸出)。在不可变对象的内部仍可以使用可变对象来管理它们的状态,如

Final域

final类型的域是不能修改的,但如果final域所引用的对象是可变的(如上例),那么这些被引用的对象是可以修改的。

示例:使用volatile类型来发布不可变对象

继续前面因式分解的例子。因式分解Servlet将执行两个原子操作:更新缓存的结果,以及通过判断缓存中的数值是否等于请求的数值来决定是否直接读取缓存中的因数分解结果。每当需要对一组相关数据以原子方式执行某个操作时,就可以创建一个不可变的类来包含这些数据,如:

对数值及其因数分解结果进行缓存的不可变容器类

@Immutable
class OneValueCache {
private final BigInteger lastNumber;
private final BigInteger[] lastFactors;
/**
* 如果在构造函数中没有使用 Arrays.copyOf()方法,那么域内不可变对象 lastFactors却能被域外代码改变
* 那么 OneValueCache 就不是不可变的。
*/
public OneValueCache(BigInteger i,
BigInteger[] factors) {
lastNumber = i;
lastFactors = Arrays.copyOf(factors, factors.length);
}
public BigInteger[] getFactors(BigInteger i) {
if (lastNumber == null || !lastNumber.equals(i))
return null;
else
return Arrays.copyOf(lastFactors, lastFactors.length);
}
}

安全发布

不正确的发布,正确的对象被破坏

不可变对象与初始化安全性

任何线程都可以子在不需要额外同步的情况下安全地访问不可变对象(状态不可变,域都是final类型,正确的构造过程)

安全发布的常用模式

可变对象必须通过安全的方式来发布。

事实不可变对象

本身可变但对象在发布后不会被修改。在没有额外的同步情况下,任何线程都可以安全地使用被安全发布的事实不可变对象。

可变对象

不仅需要安全发布,而且需要额外的同步和线程安全来保护

安全地共享对象

《java并发编程实战》读书笔记2--对象的共享,可见性,安全发布,线程封闭,不变性的更多相关文章

  1. Java并发编程实战 读书笔记(一)

    最近在看多线程经典书籍Java并发变成实战,很多概念有疑惑,虽然工作中很少用到多线程,但觉得还是自己太弱了.加油.记一些随笔.下面简单介绍一下线程. 一  线程与进程   进程与线程的解释   个人觉 ...

  2. 读书笔记-----Java并发编程实战(二)对象的共享

    public class NoVisibility{ private static boolean ready; private static int number; private static c ...

  3. Java并发编程实战 读书笔记(二)

    关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...

  4. Java并发编程实战 第3章 对象的共享

    可见性 可见性是由于java对于多线程处理的内存模型导致的.这似乎是一种失败的设计,但是JVM却能充分的利用多核处理器的强大性能,例如在缺乏同步的情况下,Java内存模型允许编译器对操作顺序进行重排序 ...

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

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

  6. 《java并发编程实战》笔记

    <java并发编程实战>这本书配合并发编程网中的并发系列文章一起看,效果会好很多. 并发系列的文章链接为:  Java并发性和多线程介绍目录 建议: <java并发编程实战>第 ...

  7. Java多线程编程实战读书笔记(一)

    多线程的基础概念本人在学习多线程的时候发现一本书——java多线程编程实战指南.整理了一下书中的概念制作成了思维导图的形式.按照书中的章节整理,并添加一些个人的理解.

  8. Java并发编程实践读书笔记(1)线程安全性和对象的共享

    2.线程的安全性 2.1什么是线程安全 在多个线程访问的时候,程序还能"正确",那就是线程安全的. 无状态(可以理解为没有字段的类)的对象一定是线程安全的. 2.2 原子性 典型的 ...

  9. 【JAVA并发编程实战】2、对象的组合

    1. 设计线程安全的类 1.找出构成对象状态的所有变量 2.找出约束状态变量的不变性条件 3.建立对象状态的并发访问管理策略 package cn.xf.cp.ch04; /** * *功能:JAVA ...

  10. Java并发编程艺术读书笔记

    1.多线程在CPU切换过程中,由于需要保存线程之前状态和加载新线程状态,成为上下文切换,上下文切换会造成消耗系统内存.所以,可合理控制线程数量. 如何控制: (1)使用ps -ef|grep appn ...

随机推荐

  1. Dalvik虚拟机中DexClassLookup结构解析

    http://blog.csdn.net/roland_sun/article/details/46877563 原文如下: 在Android系统中,所有的类定义以及具体的代码都是包含在DEX文件中的 ...

  2. HDU2819:Swap(二分图匹配)

    Swap Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submis ...

  3. python 栈和队列

    class Stack: def __init__(self): self.items = [] def isEmpty(self): return self.items == [] def push ...

  4. Kubernetes - Getting Started With Kubeadm

    In this scenario you'll learn how to bootstrap a Kubernetes cluster using Kubeadm. Kubeadm solves th ...

  5. MyBatis框架的使用及源码分析(二) 配置篇 SqlSessionFactoryBuilder,XMLConfigBuilder

    在 <MyBatis框架中Mapper映射配置的使用及原理解析(一) 配置与使用> 的demo中看到了SessionFactory的创建过程: SqlSessionFactory sess ...

  6. UOJ#21 【UR #1】缩进优化

    传送门 http://uoj.ac/problem/21 枚举 (调和级数?) $\sum_{i=1}^{n} (a_i / x + a_i \bmod x) =\sum a_i - (\sum_{i ...

  7. SpringCloud Feign重试详解

    摘要: 今天在生产环境发生了数据库进程卡死的现象,除了sql因为全量更新,没加索引的原因,最主要还是我们的接口的服务器端接口出现问题了.忽视了更新接口的幂等性,以及调用方feign client的重试 ...

  8. javascript中break和continue

    1.break break语句会立即退出循环,强制执行循环后面的语句 var num = 0; for(var i=1;i<10;i++){ if(i%5 == 0){ break; } num ...

  9. 【Matlab】使用Matlab运行Windows命令

    可以使用Matlab的一些命令来帮助程序运行.比如说 ! calc % 打开计算器 ! mspaint % 打开画图 dos calc % 打开计算器 比如一个程序要运行很长时间,而我们又不能一直守在 ...

  10. 如何才可以干掉Cortana进程,开机不启动

    直接禁用即可WIN——设置——隐私——语音.墨迹书写和键入——停止收集有关我的信息——关闭