`
jokermanager
  • 浏览: 140412 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

不可变性

阅读更多

3.4  不可变性

为了满足同步的需要,另一种方法是使用不可变对象[EJ Item13]。到目前为止,几乎所有我们已经描述过的原子性与可见性的危险,比如访问过期数据,未及时更新或者观察一个处于不一致状态的对象,它们都产 生于多线程下各种难以预测的行为协同工作,多个线程总试图同时访问相同的可变状态。如果对象的状态不能被修改,这些风险与复杂度就自然而然地消失了。

创建后状态不能被修改的对象叫做不可变对象。不可变对象天生是线程安全的。它们的常量(域)是在构造函数中创建的。既然它们的状态无法被修改,这些常量永远不会变。

不可变对象永远是线程安全的。

不可变对象是简单的。它们只有一种状态,构造函数谨慎地控制着这个状态。程序设计中最困难的元素之一是推断复杂对象的可能状态。另一方面,推断不可变对象的状态却是很轻松的。

不可变对象也是更安全的。将可变对象传递给不可信的代码,或者将它发布到不可信代码可以找到的地方,都是危险的——不可信代码可能会改变它们的状态,甚至更糟的,还会保留引用并在其他线程中修改它们的状态。另一方面,不可变对象不会被恶意的或者

漏洞百出的代码所破坏,所以它们是安全的,可以放心地共享和发布,不需要创建防御性拷贝 [EJ Item 24]。

无论是Java语言规范还是Java存储模型都没有关于不可变性的正式定义,但是不可变性并不简单地等于将对象中的所有域都声明为final类型,所有域都是final类型的对象仍然可以是可变的,因为final域可以获得一个到可变对象的引用。

只有满足如下状态,一个对象才是不可变的:

l 它的状态不能在创建后再被修改;

l 所有域都是final类型13;并且,

l 它被正确创建(创建期间没有发生this引用的逸出)。

在不可变对象的内 部,同样可以使用可变性对象来管理它们的状态,如同清单3.11中示范的那样。尽管存储姓名的set是可变的,但是ThreeStooges的设计使得它 在被创建后就不可能再修改set。Stooges引用是final类型的,所以所有的对象状态只能通过final域询问。前面列出的最后一条要求,“正确 创建”是容易满足的。因为构造函数不会做什么事情,从而引起this的调用者之外的和不同于构造函数的代码也可以访问this引用。

清单3.11  构造于底层可变对象之上的不可变类

@Immutable

public final class ThreeStooges {

    private final Set<String> stooges = new HashSet<String>();

    public ThreeStooges() {

        stooges.add("Moe");

        stooges.add("Larry");

        stooges.add("Curly");

    }

    public boolean isStooge(String name) {

        return stooges.contains(name);

    }

}

因为程序的状态自始至终都在变化着,你可能会想使用不可变对象会有很多限制,但情况并非如此。“对象是不可变的”与“到对象的引用是不可变的”之间并不等同。

程序存储在不可变对象中的状态仍然可以通过替换一个带有新状态的不可变对象的实例得到更新。后面的章节提供了使用这项技术的例子13。

3.4.1  Final域

final关键字源于C++的const机制,不过受到了更多 的限制。它对不可变性对象的创建提供了支持。final域是不能修改的(尽管如果final域指向的对象是可变的,这个对象仍然可被修改),然而它在 Java存储模型中还有着特殊的语义。final域使得确保初始化安全性(initialization safety)成为可能,初始化安全性让不可变性对象不需要同步就能自由地被访问和共享。

即使对象是可变的, 将一些域声明为final类型仍然有助于简化对其状态的判断。因为限制了对象的可见性,也就约束了其可能的状态集,即使有一两个可变的状态变量,这样一个 “几乎不可变”的对象仍然比有很多的可变变量的对象要简单。将域声明为final类型,还向维护人员明确指出这些域是不能变的。

正如“将所有的域声明为私有的,除非它们需要更高的可见性”[EJ Item 12] 一样,“将所有的域声明为final型,除非它们是可变的”,也是一条良好的实践。

3.4.2  示例:使用volatile发布不可变对象

在第24页的UnsafeCachingFactorizer 中,我们试图用两个AtomicReference存储最新的数字和它的因数。但是这并非是线程安全的,因为我们无法原子化地获取或者更新这两个相关的 值。由于同样的原因,使用volatile变量也是不能保证线程安全性的。但是,有时不可变对象也可以提供一种弱形式的原子性。

用于因式分解的Servlet执行两个必须是原子性的操作:更 新缓冲的结果,以及根据缓冲数值是否与请求数值相匹配,有条件的选取缓存中的结果。无论何时,对一组相关数值都应该执行原子性操作,并且可以考虑为它们创 建不可变的容器(holder)类,比如清单3.12中的OneValueCache14。

通过使用不可变对象来持有所有的变量,可以消除在访问和更新这些变量时的竞争条

清单3.12  在不可变的容器中缓存数字和它的因数

@Immutable

class OneValueCache {

    private final BigInteger lastNumber;

    private final BigInteger[] lastFactors;

    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);

    }

}

件。若使用可变的容器对象,你就必须使用锁以确保原子性;使用不可变对象,一旦一个线程获得了它的引用,永远不必担心其他线程会修改它的状态。如果更新变量,会创建新的容器对象,不过在此之前任何线程都还和原先的容器打交道,仍然看到它处于一致的状态。

清单3.13的VolatileCachedFactorizer利用OneValueCache存储缓存的数字及其因数。当一个线程设置volatile类型的cache域引用到一个新的OneValueCache后,新数据会立即对其他线程可见。

与cache域相关的操作不会相互干扰,因为 OneValueCache是不可变的,而且每次只有一条相应的代码路径访问它。不可变的容器对象持有与不变约束相关的多个状态变量,并利用 volatile引用确保及时的可见性,这两个前提保证了即使VolatileCachedFactor没有显式地用到锁,但仍然是线程安全的。

分享到:
评论

相关推荐

    String型的不可变性

    介绍Java的内存分配机制,并举例说明String型的不可变性。

    Java常用类与基础API-String的理解与不可变性

    Java常用类与基础API--String的理解与不可变性

    Java中的final关键字:不可变性与更多应用.md

    Java中的final关键字:不可变性与更多应用

    计算机后端-Java-Java核心基础-第21章 常用类 06. 理解String的不可变性.avi

    计算机后端-Java-Java核心基础-第21章 常用类 06. 理解String的不可变性.avi

    Java String不可变性实现原理解析

    主要介绍了Java String不可变性实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    JAVA不可变类(immutable)机制与String的不可变性(推荐)

    主要介绍了JAVA不可变类(immutable)机制与String的不可变性(推荐)的相关资料,非常不错,具有参考借鉴价值,需要的朋友可以参考下

    通过实例解析java String不可变性

    主要介绍了通过实例解析java String不可变性,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    C#不可变类型深入解析

    学过C#的人都知道string类型,但是string作为一种特殊的引用类型还有一个重要的特征就是恒定性,或者叫不可变性,即Immutable。作为不可变类型,最主要的特性表现是:一旦创建,只要修改,就会在托管堆上创建一个新...

    built_redux:以dart编写的redux的实现,实现了不可变性

    built_redux:以dart编写的redux的实现,实现了不可变性

    论文研究-具有完全保密性的高效可净化数字签名方案.pdf

    为此,提出一个新的可净化数字签名方案,它基于传统数字签名方案、BLS签名方案和公钥加密方案构造,且满足可净化数字签名的所有基本安全性需求,即不可伪造性、不可变性、透明性、完全保密性及可审计性,同时具有比...

    Python元组(Tuple)定义和操作示例.md

    理解元组的不可变性和长度获取方法。 能学到什么 通过阅读本文,您将学会以下内容: 掌握元组的定义和基本操作方法的使用; 理解元组的不可变性和索引访问特定元素的方式; 学会使用切片访问元组的子集元素; 掌握...

    基础深化和提高-java函数式编程

    不可变性:倡导使用不可变对象和不可变数据结构,避免副作用和状态的改变。 纯函数:函数没有副作用,对于相同的输入始终产生相同的输出,不依赖外部状态。 惰性求值:延迟计算,只有在需要时才进行计算,这也是...

    python中元组的定义.docx

    python中元组的定义 Python中元组的定义 在Python中,元组是一种不可变的序列类型,它可以... tup[0] = 10 # TypeError: 'tuple' object does not support item assignment ``` 元组的不可变性使得它们在某些情况下比列

    基于DSP_Builder的双三次插值算法FPGA实现的研究_谢然.pdf

    现不同功能的灵活性但其硬件结构的不可变性导致了其总线的不可变性以及其循序执行的 CPU 结构 使其在结构化设计和数据处理的速度上发展受到很大限制[1].随着现场可编程门阵列(FPGA)器件工艺 技术的发展、...

    java 并发编程实践 001

    java 并发编程实践001 ...3.4 不可变性 3.5 安全发布 . 第4章 组合对象 4.1 设计线程安全的类 4.2 实例限制 4.3 委托线程安全 4.4 向已有的线程安全类添加功能 4.5 同步策略的文档化 …… 共16个章节

    微型数据绑定和校验框架form-binder-java.zip

    不可变性,让你可以安全的共享/(嵌套)复用 mapping定义对象 它的示例代码以及组件、可扩展点如下:  第一步,定义你的 binder 第二步,定义你的 mappings 第三步,准备好数据 第四步...

    java面试题进阶版附答案.docx

    一、多态性:解释了Java中多态性的概念,以及通过继承和方法重写实现多态性的方式...八、String和StringBuilder的区别:解释了String和StringBuilder的区别,包括不可变性和可变性的特点,以及对字符串操作的效率影响。

    C#与F#编程实践 英文版PDF带书签带源码

    “不可变性”和“函数值”等函数式概念使代码分析变得更轻松,同时还有助于并发性。新的F#语言、LINQ、C#中的某些新特性以及大量的.NET库,是.NET程序员进行函数式编程的强有力工具。 《C#与F#编程实践》主要讲解...

    微型数据绑定和校验框架form-binder.zip

    不可变性,让你可以安全的共享/(嵌套)复用 mapping定义对象 form-binder 一开始是为我一个基于 Scalatra 的项目准备的,用起来会比 Scalatra 内置的 Command 框架方便不少。但是你完全可以把它用在其他...

Global site tag (gtag.js) - Google Analytics