Java 类中常见的对象线程安全分类
Posted 沛沛老爹
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 类中常见的对象线程安全分类相关的知识,希望对你有一定的参考价值。
对于 Java 类中常见的线程安全性级别,没有一种分类方式可被广泛接受,不过相对重要的是在编写类时尽量记录下它们的线程安全行为。
根据线程安全性,Josh Bloch 将线程安全性分为5类:不可变、线程安全、有条件线程安全、 线程兼容和线程对立。
这种分类的核心是调用者是否可以或者必须用外部同步操作。下面分别描述了线程安全性的这五种类别。
1) 不可变
不可变的对象一定是线程安全的,并且永远也不需要额外的同步。因为一个不可变的对象只要构建OK,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。
Java 类库中大多数基本数值类如 Integer、String 和 BigInteger 都是不可变的。
基本数据类型一般都存放在栈上(题外话)
2) 线程安全
由类所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。这种线程安全性保证是很严格的。许多类,如 Hashtable 或者 Vector 都不能满足这种严格的定义。
3) 有条件的线程安全
有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外 部同步。条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器 ——由这些类返回的 fail-fast 迭代器假定在迭代器进行遍历的时候底层集合不会有变化。
为了保证其他线程不会在遍历的时候改变集合,进行迭代的线程应该确保它是独占性地访问集 合以实现遍历的完整性。通常,独占性的访问是由对锁的同步保证的——并且类的文档应该 说明是哪个锁(通常是对象的内部监视器(intrinsic monitor))。 如果对一个有条件线程安全类进行记录,那么您应该不仅要记录它是有条件线程安全的, 而且还要记录必须防止哪些操作序列的并发访问。用户可以合理地假设其他操作序列不需要任何额外的同步。
4) 线程兼容
线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。 这可能意味着用一个 synchronized 块包围每一个方法调用,或者创建一个包装器对象,其中 每一个方法都是同步的(就像 Collections.synchronizedList() 一样)。也意味着用 synchronized 块包围某些操作序列。为了最大程度地利用线程兼容类,如果所有调用都使用同一个块,那么就不应该要求调用者对该块同步。这样做会使线程兼容的对象作为变量实例 包含在其他线程安全的对象中,从而可以利用其所有者对象的同步。
许多常见的类是线程兼容的,如集合类 ArrayList 和 HashMap 、 java.text.SimpleDateFormat 、或者 JDBC 类 Connection 和 ResultSet 。
5) 线程对立
线程对立类是那些不管是否调用了外部同步都不能在并发使用时安全地呈现的类。线程 对立很少见,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这 时通常会出现线程对立。线程对立类的一个例子是调用 System.setOut() 的类。
线程安全类(以及线程安全性程度更低的的类) 可以允许或者不允许调用者锁定对象以 进行独占性访问。 Hashtable 类对所有的同步使用对象的内部监视器,但是 ConcurrentHashMap 类不是这样,事实上没有办法锁定一个 ConcurrentHashMap 对象以进 行独占性访问。除了记录线程安全程序,还应该记录是否某些锁——如对象的内部锁——对 类的行为有特殊的意义。
通过将类记录为线程安全的(假设它确实是线程安全的),您就提供了两种有价值的服务: 您告知类的维护者不要进行会影响其线程安全性的修改或者扩展,您还告知类的用户使用它 时可以不使用外部同步。通过将类记录为线程兼容或者有条件线程安全的,您就告知了用户 这个类可以通过正确使用同步而安全地在多线程中使用。通过将类记录为线程对立的,您就 告知用户即使使用了外部同步,他们也不能在多线程中安全地使用这个类。不管是哪种情况, 您都在潜在的严重问题出现之前防止了它们,而要查找和修复这些问题是很昂贵的。
虽然 Bloch 的描述类的线 程安全程度的五层系统没有涵盖所有可能的情况,但是它是一个很好的起点。如果每一个类 都将这种线程行为的程度加入到其 Javadoc 中,那么可以肯定的是我们大家都会受益。
以上是关于Java 类中常见的对象线程安全分类的主要内容,如果未能解决你的问题,请参考以下文章
Java笔记(07):常见对象--StringBuffer二分查找及排序算法