- 浏览: 143850 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
randyjiawenjie1:
终于遇到一个人讲清楚了
阻塞I/O,非阻塞I/O -
dxqrr:
学习了。。。。
java中堆和堆栈的区别 -
tanhong:
[color=yellow][/color] ...
“is a”和“has a”的区别 -
uuid198909:
代码看着是比较………………
JDK5新特性--java.util.concurrent Semaphore(8) -
heipark:
兄弟,咱这代码纠结了点....
JDK5新特性--java.util.concurrent Semaphore(8)
不变对象具备许多能更方便地使用他们的特性,包括不严格的同步需求和不必考虑数据讹误就能自由地共享和高速缓存对象引用。尽管不变性可能未必对于任何类都
有意义,但大多数程式中至少有一些类将受益于不可变。在本月的 Java 理论和实践中,Brian Goetz
说明了不变性的一些长处和构造不变类的一些准则。请在附带的论坛中和作者和其他读者分享您关于本文的心得。(也能够单击文章顶部或底部的“讨论”来访问论
坛。)
不变对象是指在实例化后其外部可见状态无法更改的对象。Java 类库中的 String、Integer 和 BigDecimal 类就是不变对象的示例 — 他们表示在对象的生命期内无法更改的单个值。
不变性的长处
假如正确使用不变类,他们会极大地简化编程。因为他们只能处于一种状态,所以只要正确构造了他们,就决不会陷入不一致的状态。您不必复制或克隆不
变对象,就能自由地共享和高速缓存对他们的引用;您能够高速缓存他们的字段或其方法的结果,而不用担心值会不会变成失效的或和对象的其他状态不一致。不变
类通常产生最好的映射键。而且,他们本来就是线程安全的,所以不必在线程间同步对他们的访问。
自由高速缓存
因为不变对象的值没有更改的危险,所以能够自由地高速缓存对他们的引用,而且能够肯定以后的引用仍将引用同一个值。同样地,因为他们的特性无法更改,所以您能够高速缓存他们的字段和其方法的结果。
假如对象是可变的,就必须在存储对其的引用时引起注意。请考虑清单 1 中的代码,其中排列了两个由调度程式执行的任务。目的是:现在启动第一个任务,而在某一天启动第二个任务。
清单 1. 可变的 Date 对象的潜在问题 Date d = new Date();
Scheduler.scheduleTask(task1, d);
d.setTime(d.getTime() + ONE_DAY);
scheduler.scheduleTask(task2, d);
因为 Date 是可变的,所以 scheduleTask 方法必须小心地用防范措施将日期参数复制(可能通过
clone())到他的内部数据结构中。不然,task1 和 task2
可能都在明天执行,这可不是所期望的。更糟的是,任务调度程式所用的内部数据结构会变成讹误。在编写象 scheduleTask()
这样的方法时,极其容易忘记用防范措施复制日期参数。假如忘记这样做,您就制造了一个难以捕获的错误,这个错误不会马上显现出来,而且当他暴露时人们要花
较长的时间才会捕获到。不变的 Date 类不可能发生这类错误。
固有的线程安全
大多数的线程安全问题发生在当多个线程正在试图并发地修改一个对象的状态(写-写冲突)时,或当一个线程正试图访问一个对象的状态,而另一个线程
正在修改他(读-写冲突)时。要防止这样的冲突,必须同步对共享对象的访问,以便在对象处于不一致状态时其他线程不能访问他们。正确地做到这一点会很难,
需要大量文档来确保正确地扩展程式,还可能对性能产生不利后果。只要正确构造了不变对象(这意味着不让对象引用从构造函数中转义),就使他们免除了同步访
问的需要,因为无法更改他们的状态,从而就不可能存在写-写冲突或读-写冲突。
不用同步就能自由地在线程间共享对不变对象的引用,能够极大地简化编写并发程式的过程,并减少程式可能存在的潜在并发错误的数量。
在恶意运行的代码面前是安全的
把对象当作参数的方法不应变更那些对象的状态,除非文档明确说明能够这样做,或实际上这些方法具备该对象的任何权。当我们将一个对象传递给普通方
法时,通常不希望对象返回时已被更改。但是,使用可变对象时,完全会是这样的。假如将 java.awt.Point 传递给诸如
Component.setLocation() 的方法,根本不会阻止 setLocation 修改我们传入的 Point 的位置,也不会阻止
setLocation 存储对该点的引用并稍后在另一个方法中更改他。(当然,Component
不这样做,因为他不鲁莽,但是并不是任何类都那么客气。)现在,Point 的状态已在我们不知道的情况下更改了,其结果具备潜在危险 —
当点实际上在另一个位置时,我们仍认为他在原来的位置。然而,假如 Point
是不变的,那么这种恶意的代码就不能以如此令人混乱而危险的方法修改我们的程式状态了。
良好的键
不变对象产生最好的 HashMap 或 HashSet 键。有些可变对象根据其状态会更改他们的 hashCode() 值(如清单 2
中的 StringHolder 示例类)。假如使用这种可变对象作为 HashSet 键,然后对象更改了其状态,那么就会对 HashSet
实现引起混乱 — 假如枚举集合,该对象仍将出现,但假如用 contains()
查询集合,他就可能不出现。无需多说,这会引起某些混乱的行为。说明这一情况的清单 2 中的代码将打印“false”、“1”和“moo”。
清单 2. 可变 StringHolder 类,不适合用作键 public class StringHolder {
private String string;
public StringHolder(String s) {
this.string = s;
}
public String getString() {
return string;
}
public void setString(String string) {
this.string = string;
}
public boolean equals(Object o) {
if (this == o)
return true;
else if (o == null || !(o instanceof StringHolder))
return false;
else {
final StringHolder other = (StringHolder) o;
if (string == null)
return (other.string == null);
else
return string.equals(other.string);
}
}
public int hashCode() {
return (string != null ? string.hashCode() : 0);
}
public String toString() {
return string;
}
...
StringHolder sh = new StringHolder("blert");
HashSet h = new HashSet();
h.add(sh);
sh.setString("moo");
System.out.println(h.contains(sh));
System.out.println(h.size());
System.out.println(h.iterator().next());
}
何时使用不变类
不变类最适合表示抽象数据类型(如数字、枚举类型或颜色)的值。Java 类库中的基本数字类(如 Integer、Long 和
Float)都是不变的,其他标准数字类型(如 BigInteger 和
BigDecimal)也是不变的。表示复数或精度任意的有理数的类将比较适合于不变性。甚至包含许多离散值的抽象类型(如向量或矩阵)也很适合实现为不
变类,这取决于您的应用程式。
Flyweight 模式
不变性启用了 Flyweight
模式,该模式利用共享使得用对象有效地表示大量细颗粒度的对象变得容易。例如,您可能希望用一个对象来表示字处理文档中的每个字符或图像中的每个像素,但
这一策略的幼稚实现将会对内存使用和内存管理开销产生高得惊人的花费。Flyweight
模式采用工厂方法来分配对不变的细颗粒度对象的引用,并通过仅使一个对象实例和字母“a”对应来利用共享缩减对象数。有关 Flyweight
模式的更多信息,请参阅经典书籍 Design Patterns(Gamma 等著;请参阅参考资料)。
Java 类库中不变性的另一个不错的示例是 java.awt.Color。在某些颜色表示法(如 RGB、HSB 或
CMYK)中,颜色通常表示为一组有序的数字值,但把一种颜色当作颜色空间中的一个特异值,而不是一组有序的单独可寻址的值更有意义,因此将
Color 作为不变类实现是有道理的。
假如要表示的对象是多个基本值的容器(如:点、向量、矩阵或 RGB
颜色),是用可变对象还是用不变对象表示?答案是……要看情况而定。要如何使用他们?他们主要用来表示多维值(如像素的颜色),还是仅仅用作其他对象的一
组相关特性集合(如窗口的高度和宽度)的容器?这些特性多久更改一次?假如更改他们,那么各个组件值在应用程式中是否有其自己的含义呢?
事件是另一个适合用不变类实现的好示例。事件的生命期较短,而且常常会在创建他们的线程以外的线程中消耗,所以使他们成为不变的是利大于弊。大多
数 AWT
事件类都没有作为严格的不变类来实现,而是能够有小小的修改。同样地,在使用一定形式的消息传递以在组件间通信的系统中,使消息对象成为不变的或许是明智
的。
编写不变类的准则
编写不变类很容易。假如以下几点都为真,那么类就是不变的:
他的任何字段都是 final
该类声明为 final
不允许 this 引用在构造期间转义
任何包含对可变对象(如数组、集合或类似 Date 的可变类)引用的字段:
是私有的
从不被返回,也不以其他方式公开给调用程式
是对他们所引用对象的唯一引用
构造后不会更改被引用对象的状态
最后一组需要似乎挺复杂的,但其基本上意味着假如要存储对数组或其他可变对象的引用,就必须确保您的类对该可变对象拥有独占访问权(因为不然的
话,其他类能够更改其状态),而且在构造后您不修改其状态。为允许不变对象存储对数组的引用,这种复杂性是必要的,因为 Java
语言没有办法强制不对 final
数组的元素进行修改。注:假如从传递给构造函数的参数中初始化数组引用或其他可变字段,您必须用防范措施将调用程式提供的参数或您无法确保具备独占访问权
的其他信息复制到数组。否则,调用程式会在调用构造函数之后,修改数组的状态。清单 3
显示了编写一个存储调用程式提供的数组的不变对象的构造函数的正确方法(和错误方法)。
清单 3. 对不变对象编码的正确和错误方法 class ImmutableArrayHolder {
private final int[] theArray;
// Right way to write a constructor -- copy the array
public ImmutableArrayHolder(int[] anArray) {
this.theArray = (int[]) anArray.clone();
}
// Wrong way to write a constructor -- copy the reference
// The caller could change the array after the call to the constructor
public ImmutableArrayHolder(int[] anArray) {
this.theArray = anArray;
}
// Right way to write an accessor -- don't expose the array reference
public int getArrayLength() { return theArray.length }
public int getArray(int n) { return theArray[n]; }
// Right way to write an accessor -- use clone()
public int[] getArray() { return (int[]) theArray.clone(); }
// Wrong way to write an accessor -- expose the array reference
// A caller could get the array reference and then change the contents
public int[] getArray() { return theArray }
}
通过一些其他工作,能够编写使用一些非 final 字段的不变类(例如,String 的标准实现使用 hashCode
值的惰性计算),这样可能比严格的 final 类执行得更好。假如类表示抽象类型(如数字类型或颜色)的值,那么您还会想实现 hashCode()
和 equals() 方法,这样对象将作为 HashMap 或 HashSet 中的一个键工作良好。要保持线程安全,不允许 this
引用从构造函数中转义是很重要的。
偶尔更改的数据
有些数据项在程式生命期中一直保持常量,而有些会频繁更改。常量数据显然符合不变性,而状态复杂且频繁更改的对象通常不适合用不变类来实现。那么有时会更改,但更改又不太频繁的数据呢?有什么方法能让有时更改的数据获得不变性的便利和线程安全的长处呢?
util.concurrent 包中的 CopyOnWriteArrayList
类是如何既利用不变性的能力,又仍允许偶尔修改的一个良好示例。他最适合于支持事件监听程式的类(如用户界面组件)使用。虽然事件监听程式的列表能够更
改,但通常他更改的频繁性要比事件的生成少得多。
除了在修改列表时,CopyOnWriteArrayList 并不变更基本数组,而是创建新数组且废弃旧数组之外,他的行为和
ArrayList
类很相似。这意味着当调用程式获得迭代器(迭代器在内部保存对基本数组的引用)时,迭代器引用的数组实际上是不变的,从而能够无需同步或冒并发修改的风险
进行遍历。这消除了在遍历前克隆列表或在遍历期间对列表进行同步的需要,这两个操作都很麻烦、易于出错,而且完全使性能恶化。假如遍历比插入或除去更加频
繁(这在某些情况下是常有的事),CopyOnWriteArrayList 会提供更佳的性能和更方便的访问。
结束语
使用不变对象比使用可变对象要容易得多。他们只能处于一种状态,所以始终是一致的,他们本来就是线程安全的,能够被自由地共享。使用不变对象能够
完全消除许多容易发生但难以检测的编程错误,如无法在线程间同步访问或在存储对数组或对象的引用前无法克隆该数组或对象。在编写类时,问问自己这个类是否
能够作为不变类有效地实现,总是值得的。您可能会对回答常常是肯定的而感到吃惊。
发表评论
-
JDK5新特性--java.util.concurrent ExecutorCompletionSe
2008-07-04 10:25 1294考 虑以下场景:浏览网页时,浏览器了5个线程下载网页中的图片 ... -
JDK5新特性--java.util.concurrent CyclicBarrier(3)
2008-07-03 15:13 1177在 实际应用中,有时候需要多个线程同时工作以完成同一件事情,而 ... -
java基础--Java 5.0多线程编程(3)
2008-07-03 14:35 1360Lock 接口 ReentrantLock 是 Loc ... -
java基础--Java 5.0多线程编程(2)
2008-07-03 14:30 2580*1: 定义了几个任务 *2: 初始了任务执行工具。 ... -
java基础--Java 5.0多线程编程(1)
2008-07-03 14:13 1502Java自 1995 ... -
关于用信号量Semaphore完成互斥锁Mutex
2008-07-03 11:26 2632本文探讨用信号量Semaphore实现互斥锁Mutex的问题 ... -
JDK5新特性--java.util.concurrent Semaphore(8)
2008-07-03 10:43 5911操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Ja ... -
使用Callable返回结果
2008-07-02 14:50 1759本文是Sun ... -
Java线程join()方法的用处
2008-07-02 14:24 6210run() 和start() 是大家都很熟悉的两个方法。把希望 ... -
JDK5新特性--java.util.concurrent BlockingQueue(4)
2008-07-02 10:57 2479并 发库中的BlockingQueue 是一个比较好玩的类,顾 ... -
JDK5新特性--java.util.concurrent CountDownLatch(5)
2008-07-02 09:34 1409从 名字可以看出,CountDownLatch 是一个倒数计 ... -
中断JAVA线程
2008-06-30 22:44 1549在JAVA中,通过其对线程类的内嵌支持,编程人员编 ... -
JAVA中断线程的方法
2008-06-30 11:26 2158Thread.stop , Thread.suspend , ... -
不可变性
2008-05-28 09:29 10263.4 不可变性 为了满足同步的需要,另一种方法是使用不可变 ... -
使用不常进行修改的可变集合来减少应用程序的同步开销
2008-05-28 00:12 1329使用多个 Java 线程之间 ... -
正确理解java构造函数内非final函数
2008-05-27 21:49 1450大家都知道java构造 的使用方法吧, 可能大家都用它来进行 ... -
线程同步原则
2008-05-27 21:14 1031同步的基本规则:只要读取可能由其他线程写入的变量,或者写入随后 ... -
Java的多线程及安全性
2008-05-27 21:11 3063多线程 是一种机制, ... -
利于ThreadLocal管理Hibernate Session
2008-05-26 16:25 1159在利用Hibernate开发DAO模块时,我们和 ... -
ThreadLocal的几种误区
2008-05-26 16:10 971ThreadLocal的几种误区 最近由于需要用到 ...
相关推荐
并说明了在Java理论与实践中,不变性的一些长处、何时使用不变类和构造不变类的一些准则。使用不变对象比使用可变对象要容易得多。它们只能处于一种状态,所以始终是一致的,它们本来就是线程安全的,可以被自由地...
final关键字在Java中用来声明不可变对象,但旧的JMM允许final字段在构造函数中初始化后的值在某些线程中不可见,这违反了final的预期行为,导致不可变对象的不变性无法得到保证。 synchronized关键字是Java中用于...
在实践中,开发者应该充分利用这些增强的特性,例如,利用volatile保证状态变量的正确传播,以及利用final确保不变对象的安全共享。理解并应用这些内存模型的改变对于编写高效、可靠的并发Java程序至关重要。通过JSR...
Java中的线程安全性是并发编程中的关键概念,它关乎到多线程环境下程序的稳定性和正确性。线程安全的类意味着在多个线程并行访问时,它们的行为仍然是正确和一致的,无需额外的同步措施。然而,线程安全并不简单地...
通过《Java并发编程实践》这本书,读者可以深入了解Java并发编程的原理和最佳实践,提升编写高效、可靠的并发程序的能力。书中不仅讲解了理论知识,还提供了大量示例代码,有助于读者在实践中巩固所学。
在Java编程语言中,`volatile`关键字提供了一种轻量级的同步机制,用于确保共享变量的可见性和一定程度上的线程安全性。相比于传统的锁机制如`synchronized`,`volatile`变量的使用更为简单、高效,但在适用场景上...
13 Java 理论与实践: 描绘线程安全性 (2) 14 编写高效的线程安全类 (2) 15 轻松使用线程 同步不是敌人.mht 16 轻松使用线程 减少争用.mht 17 轻松使用线程 不共享有时是最好的.mht 18 适用于 Java 程序员的 CSP...
"版本不同也没多大区别"这句话暗示,即便是不同版本的教程,基本的Java编程原理和核心特性是不变的,这对于学习者来说是一个好消息,意味着即便教材的版本较旧,其核心知识仍然具有很高的参考价值。 Java是一种广泛...
Java中的`volatile`关键字在多线程编程中扮演着至关重要的角色,它的主要功能是确保在并发环境中,被volatile修饰的变量对所有线程都具有实时可见性,并且禁止指令重排序。理解`volatile`的工作原理有助于我们更好地...
在准备JAVA期末考试的过程...在复习时,要深入理解这些概念并进行实践,熟悉每种结构的用法,掌握面向对象编程的核心思想,以及Java内存管理和异常处理等高级话题。同时,多做练习题,巩固理论知识,提高实际编程能力。
【Java 精选面试题全集】 Java 是一种广泛使用的面向对象的编程语言,尤其在企业级应用开发中占据重要地位。对于寻找Java相关职位的开发者来说,掌握Java的核心概念...不断实践和学习新技术是提升Java开发技能的关键。
在Java中实现SIFT算法是一项技术性较强的任务,因为这通常需要对图像处理理论和数学有深入的理解。这个项目声称是用纯Java编写的,没有依赖如OpenCV或Matlab这样的外部库,这表明它可能使用了自定义的计算方法来实现...
这些小程序涵盖了从基本语法到面向对象编程的各个层面,为学习者提供了一个实践和巩固理论知识的平台。 在Java编程的世界里,入门阶段通常包括以下几个关键知识点: 1. **Java环境配置**:首先,你需要安装Java ...
在Java编程语言中,常量和变量是两个基础且至关重要的概念。它们是程序中存储数据的容器,理解和熟练运用它们对于编写高质量的Java代码至关重要。...同时,结合理论知识与实践操作,将有助于你在Java编程能力上的提升。
- 练习编写代码,理论结合实践。 8. **Java语法格式** - Java代码遵循严格的语法规则,如每个语句末尾必须以分号结尾;每条语句必须在大括号内等。 9. **代码注释** - 单行注释:使用`//`。 - 多行注释:使用`...
Java国际化编程,简称i18n(取"internationalization"一词中第18个字母i作为缩写),是Java平台提供的一种强大的功能,旨在帮助开发者...通过实践和学习,我们可以将这些理论知识转化为实际应用,提升软件的用户体验。
总的来说,"数字图像处理-Java代码"项目提供了一个学习和实践数字图像处理技术的平台,涵盖了许多核心概念和算法,对于理解和掌握图像处理技术有着重要的价值。通过阅读和分析这些代码,开发者不仅可以深化理论知识...
【贪吃蛇小游戏Java编写-跟狂神说Java学习】 贪吃蛇小游戏是经典的计算机程序设计练习,它能帮助初学者理解基本的编程概念、控制流、数据...这个项目是一个很好的实践平台,可以帮助开发者将理论知识转化为实际应用。
《JAVA开发实战经典》是Java编程领域的一本权威著作,其第二章主要涵盖了Java语言的基础知识和核心概念。...在实践中,不断调试和修改代码,将理论知识转化为实际编程能力,这是成为一名合格Java开发者的重要步骤。
综上所述,这个项目涵盖了计算机视觉的基础理论,Java编程技术,图像处理和机器学习在对象识别中的应用,以及代码优化和性能评估等多个方面的知识。在实践中,不断学习和改进将是提升项目效果的关键。