使用:
仅当volatile变量能简化代码的实现以及对同步策略的验证时,才应该使用它们。如果在验证正确性时需要对可见性进行复杂的判断,那么就不要使用volatile变量。volatile变量的正确使用方式包括:确保它们自身状态的可见性,确保它们所引用对象的状态的可见性,以及标识一些重要的程序生命周期事件的发生(例如,初始化或关闭)
volatile变量的一种典型用法:检查某个专题标记以判断是否退出循环,eg:
volatile boolean asleep;
...
while(!asleep)
countSomeSheep();
...
当且仅当满足以下所有条件时,才应该使用 volatile 变量:
1. 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。
2. 该变量不会与其他状态变量一起纳入不变性条件中。
3. 在访问变量时不需要加锁
当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据就不需要同步。这种技术被称为线程封闭(Thread Confinement)。
它是实现线程安全的最简单方式之一。
Ad-hoc线程封闭
Ad-hoc线程封闭是指,维护线程封闭性的职责完全由程序来承担。Ad-hoc线程封闭是非常脆弱的,因此在程序中尽量少用,在可能的情况下,应该使用更强的线程封闭技术(例如,栈封闭 或 ThreadLocal类)。
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<connection>(){ public Connection initialValue() { return DriverManager.getConnection(DB_URL); } }; public static Connection getConnection(){ return connectionHolder.get(); }
ThreadLocal变量类似于全局变量,它能降低代码的可重用行,并在类之间引入隐含的耦合性,因此在使用时要格外小心。
当满足以下条件时,对象才是不可变的:
1.对象创建以后其状态就不能修改
2.对象的所有域都是final类型
3.对象是正确创建的(在对象的创建期间,this引用没有逸出)
@Immutable class OneValueCache{ private final BigInteger lastNumber; private final BigInteger[] lastFactors; public OneValueCache(BigInteger i,BigInteger[] factors){ lastNumber = i; lastFactors = factors; } public BigInteger[] getFactors(BigInteger i){ if(lastNumber==null || !lastNumbers.equal(i)) return null; else return Arrays.copyOf(lastFactors,lastFactors.length); } }对于在访问和更新多个相关变量时出现的竞争条件问题,可以通过将这些变量全部存在一个不可变的对象中来消除。如果是一个可变的对象,那么就必须使用锁来确保原子性。如果是一个不可变对象,那么当现场获得了对该对象的引用后,就不必担心另一个线程会修改该对象的状态。如果要更新这些变量,那么可以创建一个新的容易对象,但其他使用原有对象的线程仍然会看到对象处于一致的状态。
@ThreadSafe public class VolatileCachedFactorizer implements Servlet{ private volatile OneValueCache cache = new OneValueCache(null,null); public void service(ServletRequest req,ServletResponse resp){ BigInteger i = extractFromRequest(req); BigInteger[] factors = cache.getFactors(i); if(factors==null){ factors = factor(i); cache = new OneValueCache(i,factors); } encodeIntoResponse(resp,factors); } }
另一方面,即使在发布不可变对象的引用时没有使用同步,也仍然可以安全地访问该对象,为了维持这种初始化安全性的保证,必须满足不可变性的所有需求:状态不可修改,所有域都是final类型,以及正确的构造过程。
这种保证还将延伸到被正确创建对象中所有final类型的域。在没有额外同步的情况下,也可以安全地访问final类型的域。然而,如果final类型的域所指向的是可变对象,那么在访问这些域所指向的对象的状态时仍然需要同步。
要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全的发布:
1.在静态初始化函数中初始化一个对象引用
2.将对象的引用保存到volatile类型的域或者AtomicReference对象中
3.将对象的引用保存到某个正确构造对象的final类型域中
4.将对象的引用保存到一个由锁保护的域中(包含 线程安全容器内部的同步)
线程安全库中的容器提供了一下的全发布保证:
1.通过将一个键或者值放入HashTable,synchronizedMap或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程(无论是直接访问还是通过迭代器访问)
2.通过将某个元素放入Vector,CopyOnWriteArrayList,CopyOnWriteArraySet,synchronizedList或synchronizedSet中,可以将该元素安全地帆布到任何从这些容器中访问该元素的线程
3.通过将某个元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程
类库中的其他数据传递机制(例如Future和Exchanger)同样能实现安全发布。
通常要发布一个静态构造的对象,最简单和最安全的方式是使用静态的初始化器:
public static Holder holder = new Holder(42);
静态初始化器由JVM在类的初始化阶段执行。由于在JVM内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全地发布。
3.5.4 事实不可变对象
所有的安全发布机制都能确保,当对象的引用对所有访问该对象的线程可见时,对象发布时的状态对于所有线程也将是可见的,并且如果对象状态不会再改变,那么就足以确保任何访问都是安全的。如果对象从技术上来看是可变的,但其状态再发布后不会再改变,那么把这种对象称为"事实不可变对象(Effectively Immutable Object)"。
当满足以下条件时,对象才是不可变的:
1.对象创建以后其状态就不能修改
2.对象的所有域都是final类型
3.对象是正确创建的(在对象的创建期间,this引用没有逸出)
事实不可变对象不需要满足不可变性的严格定义。在这些对象发布后,程序只需将它们视为不可变对象即可。在没有额外同步的情况下,任何线程都可以安全地使用被安全发布的事实不可变对象。
eg: Date本身是可变的,但如果将它作为不可变对象来使用吗,那么在多个线程之间共享Date对象时,就可以省去对锁的使用。
public Map<String,Date> lastLogin = Collections.synchronizedMap(new HashMap<String,Date>());
如果对象在构造后可以修改,那么安全发布只能确保"发布当时"状态的可见性。对于可变对象,不仅在发布对象时需要使用同步,而且在每次对象访问时同样需要使用同步来确保后续修改操作的可见性。要安全的共享可变对象,这些对象就必须被安全地发布,并且必须是线程安全的或者由某个锁保护起来。
对象的发布需求取决于它的可变性:
1.不可变对象可以通过任意机制来发布
2.事实不可变对象必须通过安全方式来发布
3.可变对象必须通过安全的方式来发布,并且必须是线程安全的或者由某个锁保护起来
当获得对象的一个引用时,你需要知道在这个引用上可以执行哪些操作。在使用它之前是否需要获得一个锁?是否可以修改它的状态,或者只能读取它?许多并发错误都由于没有理解共享对象的这些"既定规则"而导致的。当发布一个对象时,必须明确地说明对象的访问方式。
在并发程序中使用和共享对象时,可以使用一些实用的策略,包括:
1.线程封闭。 线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。
2.只读共享。 在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问。但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象。
3.线程安全共享。 线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步。
4.保护对象。 被保护的对象只能通过持有特定的锁来访问。保护对象也包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。
相关推荐
《java并发编程实战》读书笔记-第3章-对象的共享,脑图形式,使用xmind8制作 包括可见性、发布与逸出、线程封闭、不可变性、安全发布等内容
Java并发编程实战,第1章 简介,第2章 线程安全性 第3章 对象的共享 第4章 对象的组合 第5章 基础构建模块 第6章 任务执行 第7章 取消与关闭 第8章 线程池的使用 第9章 图形用户界面应用程序 第10章 避免...
第3章 继承 3.1 引言 3.2 设计类层次结构 3.3 给类层次结构添加实现代码 3.4 抽象类 3.5 重定义方法 3.6 实现栈类 3.7 多重继承 3.8 使用继承的规则 3.9 小结 3.10 课外阅读 3.11 复习题 3.12 复习题答案 第4...
《java并发编程实战》读书笔记-第3章-对象的共享,脑图形式,使用xmind8制作 包括线程安全类设计、实例封闭、线程安全性委托、现有线程安全类中添加功能和文档化同步策略等内容
第3章 创建项目 第4章 创建和编辑任务 第5章 项目资源与工作分配 第6章 管理项目成本 第7章 项目进度跟踪 第8章 优化项目 第9章 美化项目信息 第10章 项目报表和打印 第11章 多重项目管理 第12章 项目文件的共享 第...
第六版答案~~好容易才从网上找来的 拿来给大家共享下!!
第3章 高级面向对象概念 39 3.1 构造函数 39 3.1.1 何时调用构造函数 39 3.1.2 构造函数中有什么 40 3.1.3 默认构造函数 40 3.1.4 使用多个构造函数 41 3.1.5 构造函数的设计 44 3.2 错误处理 44 3.2.1 忽略...
Python面向对象编程指南深入介绍Python语言的面向对象特性,全书分3个部分共18章。...第3部分讲述测试、调试、部署和维护,分别介绍了Logging和Warning模块、可测试性的设计、使用命令行、模块和包的设计、质量和文档。
第3章 Data语意学(The Semantics of Data) 3.1 Data Member的绑定(The Binding of a Data Member) 3.2 Data Member的布局(Data Member Layout) 3.3 Data Member的存取 Static Data Members Nonstatic Data ...
第3章 类和对象 3.1 习题参考解答 3.2 上机实验题参考解答 第4章 类和对象的进一步讨论 4.1 习题参考解答 4.2 上机实验题参考解答 第5章 继承与派生 5.1 习题参考解答 5.2 上机实验题参考解答 第6章 多态性与虚函数 ...
第3章 Data语意学(The Semantics of Data) 3.1 Data Member的绑定(The Binding of a Data Member) 3.2 Data Member的布局(Data Member Layout) 3.3 Data Member的存取 Static Data Members Nonstatic Data ...
第3章 共享对象 3.1 可见性 3.2 发布和逸出 3.3 线程封闭 3.4 不可变性 3.5 安全发布 . 第4章 组合对象 4.1 设计线程安全的类 4.2 实例限制 4.3 委托线程安全 4.4 向已有的线程安全类添加功能 4.5 同步策略的文档化 ...
非法使用数据库的情况 编写合法程序绕过数据库管理系统及其授权机制 直接或编写应用程序执行非授权操作 通过多次合法查询数据库从中推导出一些保密数据 数据库系统第4章全文共81页,当前为第3页。 数据库安全性控制...
第3部分讲述测试、调试、部署和维护,分别介绍了Logging和Warning模块、可测试性的设计、使用命令行、模块和包的设计、质量和文档。 本书深入剖析Python,帮助读者全面掌握Python并构建出更好的应用程序,非常适合...
第3章 JavaScript事件处理.pdf 第4章 JavaScript基于对象编程.pdf 第5章 文档对象模型(DOM).pdf 第6章 String、Math、Array等数据对象.pdf 第7章 Window及相关顶级对象.pdf 第8章 Document对象.pdf 内容 绝对清楚 ...
第 3 章,类的声明和定义 第 4 章,继承 第 5 章,Class 类型,选择器 Selector 以及函数指针 第 6 章,NSObject 的奥秘 第 7 章,对象的初始化以及实例变量的作用域 第 8 章,类方法以及私有方法 第 9 章,内存管理...
第三章 AppleScrip语言初步! 第一节 对象、属性和命令! 第二节 标识符和关键字! 第三节 数据类型! 第四节 强制数据类型转换! 第五节 运算符! 第六节 提取对象中的元素! 第七节 添加注释和括号! 第八节 代码缩写! 第...
第3章 函数 3.1 函数的定义与使用 3.2 内联函数 3.3 带默认形参值的函数 3.4 函数重载 3.5 函数模板 3.6 使用C++系统函数 3.7 小结 习题 第4章 类与对象 4.1 面向对象的思想 4.2 面向对象程序设计的基本特点 4.3 ...
第3章、Android事件处理,包括按键响应机制和消息传递机制 3.2、基于监听器的事件处理: 3.3、基于回调的事件的处理: 3.4、响应系统设置的事件: 3.5、Handler消息传递机制: 第4章、深入理解Activity 4.1、...
第3章Servlet基础 1.在Servlet开发中,实现了多个Servlet之间数据共享的对象是【 】。 2.在Servlet容器启动每一个web应用时,就会创建一个唯一的ServletContext对象,该对象和web应用具有相同的【 】。 第4章请求和...