- 浏览: 143710 次
- 性别:
- 来自: 广州
文章分类
最新评论
-
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 线程之间共享数据的缺点在于数据访问必须同步,从而避免出现不一致的内容视图,后者可能会导致应用程序失败。例如,Hashtable
类的 put()
和 get()
方法是同步的。因为需要实现同步,所以 put()
和 get()
方法在执行时将同时单独地访问数据;否则,应用程序数据结构可能会被破坏。
当某个应用程序的线程频繁访问这些方法,导致线程出现阻塞时,这些方法的同步点将成为瓶颈。每次只能有一个线程获得内容的访问权。而其他线程必须等 待。如果线程出现排队等候(如果不是这样,线程能够进行其他有用操作),性能和吞吐量将下降。当性能分析显示同步方法实际上会导致排队点时,对代码进行优 化是有益的。
对于很少进行修改的数据,一种被称为分代数据结构(generational data structure)
的技术允许您使用较低的 volatile
开销来安全地发布可变数据结构。当数据结构被频繁访问但很少进行修改时,这将获得性能增益。例如,可以使用未同步的数据结构如 HashMap
,而不是同步的数据结构如 Hashtable
。该技术的关键内容包括:
- 发生更新时,制作数据结构的新副本。
- 完全填充它。
- 使用
volatile
引用将更新安全地发布到所有客户。
使用该技术,get
和 put
操作永远不会在数据结构的同一个示例上同时执行。将确保两个线程不会尝试同时更新数据结构,并且读取线程会始终查看一致的、最新版本的数据。(即使数据被
频繁更新,仍可以使用该方法,不过通过改善并发性而获得的性能增益将损失。频繁地重新填充数据结构可能会抵消由消除同步存取器方法而获得的性能增益。)
|
该技术使用了 Java 语言的三个特性;
- 自动垃圾收集。
当对象的最后一个引用不再使用时,Java 运行时可以自动释放该对象。应用程序不需要进行其他操作,只需确认当应用程序不再使用某个对象时,没有任何引用指向该对象。早期创建的对象会在最后一个客户使用完成后被自动释放。
- 对象引用的原子性。
一
个获取对象引用的简单赋值语句是不能被中断的。这意味着只要消费线程可以使用较旧的(但完整的)对象副本生成正确结果,就没有必要围绕单个对象的赋值语句
实现同步。但是,必须注意的是仍需在生产者(producer)线程上采取操作,以确保在执行赋值之前创建完成新的对象。正如本文 讨论
部分所述,生产者线程中需要使用同步代码以确保在对象赋值前完成对象创建。但是,不必在消费者线程中加入同步代码,这将消除开销较大的排队点。
- Java 内存模型。
Java 内存模型规定了
synchronized
和volatile
的语义。这些规则定义了共享对象及其内容在何时对于除当前正在执行的线程之外的线程是可见的。
为维持两个独立的数据结构实例而对数据结构中的数据进行修改时,您可以使用 Java 语言的以上特性。一旦其中一个被填充,它就不会再次更改。它是有效不可变的
。如果允许 get
和 put
操作在同一个数据结构上同时执行,这是比较危险的。本文所讨论的技术将确保所有 put
操作会在执行任何 get
操作之前完成。
清单 1 中的示例代码阐述了该技术:
// consumers will see updated values
static Object lockbox = new Object();
public static void buildNewMap() ... { // This is called by the producer
// when the data needs to be updated.
synchronized (lockbox) ... { // This must be synchronized because
// of the Java memory model.
Map newMap = new HashMap(currentMap); // for cases where new data is based on
// the existing values, you can use the
// currentMap as a starting point.
// add or remove any new or changed items to the newMap
newMap.put(....);
newMap.put(....);
currentMap = newMap;
}
/**/ /* After the above synchronization block, everything that is in the HashMap is
visible outside this thread. The updated set of values is available to
the consumer threads.
As long as assignment operation can complete without being interrupted
and is guaranteed to be written to shared memory and the consumer can
live with the out of date information temporarily, this should work fine. */
}
public static Object getFromCurrentMap(Object key) ... { // Called by consumer threads.
Map m = currentMap; // No locking around this is required.
Object result = m.get(key); // get on a HashMap is not synchronized.
// Do any additional processing needed using the result.
return (result);
}
下面将详细讨论清单 1 的内容:
-
第二个变量 — 即清单 1 中的
newMap
— 将保存用数据填充的HashMap
。这个变量受synchronized
块的保护,一次只能由一个线程使用 — producer 线程的工作是进行以下操作:- 创建新的
HashMap
并将其存储在newMap
变量中。 - 在
newMap
上执行整个put
操作集,这样消费者线程所需的所有数据都包含在newMap
中。 - 当
newMap
被完全填充后,将newMap
的值指定为currentMap
。
由于定时器(或侦听器)会在某些外部数据(如数据库)发生更改时被唤醒,因此可以定期执行生产者线程。
- 创建新的
- 需要使用
currentMap
内容的消费者线程仅仅访问对象并执行get
操作。请注意m = currentMap
赋值是一个单元操作,而且不需要进行同步,即使其他线程可能正在访问对象的值。这是安全的,因为currentMap
是可变的,并且是在生产者的同步块内部进行填充。这意味着通过currentMap
引用读取的数据结构内容至少会与currentMap
引用本身保持一致的更新程度。
一旦将 newMap
指定为 currentMap
,则内容始终不会更改。实际上,HashMap
是不可变的。这将允许多个 get
操作并行运行,从而获得主要性能改善。根据 Brian Goetz 在 Java Concurrency in Practice
(参阅 参考资料
)中 3.5.4 节的论述,即 “无需额外的同步即可使用安全发布的有效不可变对象”,安全发布是 volatile
引用的结果。
读取数据时,惟一可能发生更改的就是 currentMap
变量的对象引用。在消费者线程访问某个值的同时,生产者线程将使用新值覆盖当前值。因为对象引用是 Java 语言中的单元操作,所以在访问该对象时,消费者线程没有必要进行同步。最糟糕情形可能是消费者线程获得 currentMap
引用,然后生产者线程使用较新的内容覆盖该引用。在这种情况下,消费者线程会使用稍微有些旧但仍保持内部一致的数据。如果消费者线程在生产者线程准备运行的前一秒执行,则会出现同样结果。通常,这样不会引起任何问题。关键在于 currentMap
的内容会在发布时始终保持完全一致和不可变。
发生这种竞争时,消费者线程可能会使用 “旧” 版本数据的引用。“新” 的对象引用已经覆盖旧版本,但某些消费者线程仍使用旧版本。当最后一个消费者线程不再使用对旧对象的引用后,该对象将被释放并进行垃圾收集。Java 运行时将记录何时发生上述操作。应用程序不必显式释放旧对象,因为对象释放是自动进行的。
可以基于应用程序的需要,定期创建新版的 currentMap
。按照上述步骤进行操作,可以确保这些更新能够安全地反复进行。
清单 1 中的 synchronized
块必需确保两个生产者线程不会同时竞争更新 currentMap
。那样可能会导致数据损失,从而导致消费者线程查看不确定的结果。synchronized
将阻止优化程序作出这种决策,实际上是将整个映射创建作为原子操作处理。关键字 volatile
可以保证消费者线程在 currentMap
变量的值发生更改后不会继续查看其旧值。更重要的是,可以确保客户通过取消引用对象引用而获得的值至少与引用本身保持一致的更新程度。而普通的引用不能提供这种有序保证。
使用 synchronized
块和 volatile
关键字所带来的影响是消费者线程可以查看一致的视图。数据结构在发布后不会被修改这一事实将为生产者线程提供帮助。在这种情形中 —
发布有效不可变的对象图形 —
所需做的事情就是安全地发布根对象引用。请注意,也可以对根对象引用的消费者访问进行同步,但这将成为排队点,而排队点正是该技术试图避免的。Brian
Goetz 将这种方法称为 “开销较低的读-写锁” 技巧(参阅 参考资料
)。
本文所讨论的技术适用于共享数据很少更改且由多线程同时访问的场合。不过该技术仅适用于应用程序不要求 使用绝对最新数据的场合。
最终结果是并发访问随时间变化的共享数据。在要求高并发性的环境中。该技术可以避免在应用程序内部包含不必要的排队点。
需要注意的是由于 Java 内存模型的复杂性,本文所讨论的技术仅用于 Java 5.0 及更高版本。在早期的 Java 版本中,客户机应用程序面临的风险是查看未被完全填充的 HashMap
,或 HashMap
的已破坏的、无效的或不一致的内部数据结构视图。
本文作者非常感谢 Brian Goetz 对本文作出的技术评论和建议,使本文更加完整、严谨和准确。
学习
- 您可以参阅本文在 developerWorks 全球站点上的 英文原文
。
- “Double-checked locking: Clever, but broken
”(Brian Goetz,JavaWorld.com,2001 年 2 月):参阅有关同步 gotchas 的内容。
- Java Concurrency in Practice
(Brian Goetz,Addison-Wesley,2006 年 5 月):Java Concurrency in Practice
的第 16 章讨论了 Java 内存模型。
- “Java 理论与实践
: 正确使用 Volatile 变量”
(Brian Goetz,developerWorks,2007 年 6 月):有关正确使用变量的一些模式。
- 浏览 technology bookstore
,获取有关这些和其他技术主题的书籍。
- developerWorks Java 技术专区
:数以千计的有关 Java 编程的文章。
发表评论
-
JDK5新特性--java.util.concurrent ExecutorCompletionSe
2008-07-04 10:25 1290考 虑以下场景:浏览网页时,浏览器了5个线程下载网页中的图片 ... -
JDK5新特性--java.util.concurrent CyclicBarrier(3)
2008-07-03 15:13 1175在 实际应用中,有时候需要多个线程同时工作以完成同一件事情,而 ... -
java基础--Java 5.0多线程编程(3)
2008-07-03 14:35 1358Lock 接口 ReentrantLock 是 Loc ... -
java基础--Java 5.0多线程编程(2)
2008-07-03 14:30 2579*1: 定义了几个任务 *2: 初始了任务执行工具。 ... -
java基础--Java 5.0多线程编程(1)
2008-07-03 14:13 1501Java自 1995 ... -
关于用信号量Semaphore完成互斥锁Mutex
2008-07-03 11:26 2629本文探讨用信号量Semaphore实现互斥锁Mutex的问题 ... -
JDK5新特性--java.util.concurrent Semaphore(8)
2008-07-03 10:43 5909操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Ja ... -
使用Callable返回结果
2008-07-02 14:50 1758本文是Sun ... -
Java线程join()方法的用处
2008-07-02 14:24 6208run() 和start() 是大家都很熟悉的两个方法。把希望 ... -
JDK5新特性--java.util.concurrent BlockingQueue(4)
2008-07-02 10:57 2478并 发库中的BlockingQueue 是一个比较好玩的类,顾 ... -
JDK5新特性--java.util.concurrent CountDownLatch(5)
2008-07-02 09:34 1409从 名字可以看出,CountDownLatch 是一个倒数计 ... -
中断JAVA线程
2008-06-30 22:44 1548在JAVA中,通过其对线程类的内嵌支持,编程人员编 ... -
JAVA中断线程的方法
2008-06-30 11:26 2157Thread.stop , Thread.suspend , ... -
不可变性
2008-05-28 09:29 10233.4 不可变性 为了满足同步的需要,另一种方法是使用不可变 ... -
正确理解java构造函数内非final函数
2008-05-27 21:49 1449大家都知道java构造 的使用方法吧, 可能大家都用它来进行 ... -
Java 理论和实践:变还是不变?
2008-05-27 21:28 891不变对象具备许多能更方便地使用他们的特性,包括不严格的同步需求 ... -
线程同步原则
2008-05-27 21:14 1029同步的基本规则:只要读取可能由其他线程写入的变量,或者写入随后 ... -
Java的多线程及安全性
2008-05-27 21:11 3062多线程 是一种机制, ... -
利于ThreadLocal管理Hibernate Session
2008-05-26 16:25 1157在利用Hibernate开发DAO模块时,我们和 ... -
ThreadLocal的几种误区
2008-05-26 16:10 969ThreadLocal的几种误区 最近由于需要用到 ...
相关推荐
总之,Guava的不可变集合类提供了一种高效、安全的方式来处理集合数据,它们在Java编程中起到了重要的作用,特别是在多线程和高并发场景下。通过合理地使用这些类,开发者可以编写出更简洁、更安全的代码。
这种特性使得不可变集合在多线程环境下尤其有价值,因为它们天生就具有线程安全性,无需额外的同步措施来保证并发访问的安全。在本文中,我们将深入探讨Crystal中的不可变集合,包括它们的设计原理、实现方式以及...
ADO.NET 2.0是微软.NET Framework的一部分,它为开发者提供了高效、灵活的数据库访问机制,使得构建数据库驱动的应用程序变得更加简单。在本教程中,我们将深入探讨如何利用ADO.NET 2.0进行数据库应用程序开发。 1....
不可变对象可以安全地在多个线程间共享,无需担心同步问题,这在多线程环境中可以提高效率。此外,由于它们的状态不可改变,JVM有时可以对不可变对象进行优化,如缓存或共享相同的实例,称为“字符串池”。 对于...
在C#编程中,数组、集合对象和泛型是核心概念,它们在处理数据和构建高效应用程序时扮演着重要角色。 首先,数组是最基础的数据结构,用于存储同一类型的多个元素。在C#中,数组是一种固定大小的内存块,可以一次性...
12. **不可变集合**:`Collections.unmodifiableList()`、`Collections.unmodifiableSet()`和`Collections.unmodifiableMap()`等方法创建不可变集合,防止集合内容被修改,提高代码安全性。 13. **枚举Set**:`...
字符串在Java中是不可变的,每次修改都会生成新的对象。避免使用`+`操作符连接字符串,尤其是在循环中。可以使用StringBuilder或StringBuffer(线程安全)进行拼接。 4. **使用局部变量** 尽可能使用局部变量,...
字符串在JAVA中被视为一个不可变的对象,由`java.lang.String`类表示。这意味着一旦一个字符串被创建,它的值就不能被修改。例如,`String str = "abc";` 和 `String str1 = new String("abc");` 这两种创建字符串的...
- 避免频繁改变对象状态,特别是不可变对象(如String),可以通过创建新对象来替代。 - 合理分配对象空间,避免自动扩容带来的性能损耗。 - 对于短生命周期的对象,可以采用对象池机制来提高性能。 - 在对象的...
1. **创建**:在`Immutable.js`中,我们可以使用`List.of()`、`Map.of()`等方法创建不可变集合。 2. **访问**:通过索引或键来获取值,不会改变原有集合。 3. **更新**:如果需要修改值,如替换数组中的一个元素,...
这是因为字符串在.NET中是不可变的,每次连接都会创建一个新的字符串对象,而`StringBuilder`则是在内部进行修改,不会产生额外的对象。 **1.2.2 避免不必要的调用ToUpper或ToLower方法** 对字符串进行大小写转换...
6. **并发安全**:在多线程或多进程环境中,不可变数据结构可以减少同步开销,因为它们天生就线程安全。 安装`frozendict`库,你可以使用`pip`命令,如: ```bash pip install frozendict-2.1.2-cp38-cp38-macosx_...
在处理字符串时,应尽可能使用StringBuilder或StringBuffer来代替String,因为String是不可变的,每次操作都会产生新的String对象。 #### 3. 集合框架使用 在集合框架中,ArrayList和LinkedList的选择至关重要。...
总之,选择`Vector`可能是出于对线程安全的需求,但在现代Java编程中,考虑到性能和最佳实践,我们可能会推荐使用其他更适合的并发集合类,或者结合`Collections.synchronizedList()`等工具来手动同步`ArrayList`。...
- **不可变对象**:不可变对象一旦被创建之后就不能再修改其状态,因此它们天然具有线程安全性。 - **使用枚举类型**:枚举类型是不可变的,并且Java语言规范保证了枚举类型的单例性,是非常好的不可变对象实例。 #...
- 字符串是不可变的,频繁拼接会导致不必要的内存创建,使用`StringBuilder`或`StringBuffer`类。 - 使用`String.equals()`而不是`==`来比较字符串内容,避免空指针异常。 - 利用`String.intern()`方法共享相同的...
2. **对象重用**:避免频繁创建对象,尤其是对于像`String`这样的不可变对象。在字符串连接操作中,使用`StringBuilder`或`StringBuffer`代替`+`操作符,因为它们在内部维护一个缓冲区,性能更优。 3. **利用局部...