有条件线程安全.
事出于一次偶然的MS中,在问及hashmap和hashtable的时候很习惯的就说非线程安全和线程安全。却被告知有条件线程安全这一说法。
困惑之余查看了hashtable 和hashmap 的源码,
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V> 及
public class Hashtable<K, V> extends Dictionary<K, V> implements Map<K, V>,
Cloneable, Serializable
在Hashtable 中每一个方法都是synchronized同步的,也就是线程安全的。
查看资料发现,早有砖家提出了有条件线程安全这一概念。
并发专家Brian Goetz将Hashtable和Vector类描述为“有条件线程安全的”。一个类难道不是线程安全就是线程不安全的吗?不幸的是,线程安全并不是一个非真即假的命题。
一个类在可以被多个线程安全调用时就是线程安全的? 这可能是大部分人理解的线程安全。
砖家提出:
· ...可以从多个编程线程中调用,无需线程之间不必要的交互。· ...可以同时被多个线程调用,不需要调用一方有任何操作。
下面看看vector的一个例子
执行结果大家也可以预料的到的
顺利执行完成
或
可能有人这时候就要说,run 方法中的Vector每一个方法都是同步的,这不能作为线程不安全的依据
问题代码在于:
这一问题是由get()的前置条件是以size()的结果来定义的这一事实所带来的。只要看到这种必须使用一种方法的结果作为另一种讲法的输入条件的样式,它就是一个状态依赖,就必须保证至少在调用这两种方法期间元素的状态没有改变。一般来说,做到这一点的唯一方法在调用第一个方法之前是独占性地锁定对象,一直到调用了后一种方法以后。在上面的迭代Vector元素的例子中,需要在迭代过程中同步Vector对象。
再回过头来看看
线程安全性 类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。 此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。 正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。
上面例子中的方法之间的状态依赖关系似乎破坏了整个类的一致性和独立性。
不可变 不可变的对象一定是线程安全的,并且永远也不需要额外的同步。因为一个不可变的对象只要构建正确,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。Java 类库中大多数基本数值类如 Integer 、 String 和 BigInteger 都是不可变的。
线程安全 由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。这种线程安全性保证是很严格的 -- 许多类,如 Hashtable 或者 Vector 都不能满足这种严格的定义。
所以。。。
事出于一次偶然的MS中,在问及hashmap和hashtable的时候很习惯的就说非线程安全和线程安全。却被告知有条件线程安全这一说法。
困惑之余查看了hashtable 和hashmap 的源码,
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V> 及
public class Hashtable<K, V> extends Dictionary<K, V> implements Map<K, V>,
Cloneable, Serializable
在Hashtable 中每一个方法都是synchronized同步的,也就是线程安全的。
查看资料发现,早有砖家提出了有条件线程安全这一概念。
并发专家Brian Goetz将Hashtable和Vector类描述为“有条件线程安全的”。一个类难道不是线程安全就是线程不安全的吗?不幸的是,线程安全并不是一个非真即假的命题。
一个类在可以被多个线程安全调用时就是线程安全的? 这可能是大部分人理解的线程安全。
砖家提出:
· ...可以从多个编程线程中调用,无需线程之间不必要的交互。· ...可以同时被多个线程调用,不需要调用一方有任何操作。
下面看看vector的一个例子
import java.util.List;
import java.util.Vector;
public class VectorTest {
List vector= new Vector();
public void init() {
for (int i=0; i<1000 ; i++) {
vector.add(i);
}
}
public void get(int i) {
vector.get(i);
}
public void remove(int i) {
vector.remove(i);
}
public void start() {
this.init();
new Thread(new ListOpt(vector)).start();
for (int i=0; i<100 ; i++) {
new Thread(new ListOpt1(vector)).start();
}
}
public static void main(String[] args) {
VectorTest test = new VectorTest();
test.start();
}
class ListOpt implements Runnable {
private List list;
public ListOpt(List list ) {
this.list = list;
}
public void run() {
try {
for (int i=0 ;i<1000; i++) {
int listsize= list.size();
System.out.println(list.get(listsize-1));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class ListOpt1 implements Runnable {
private List list;
public ListOpt1(List list ) {
this.list = list;
}
public void run() {
try {
list.remove(list.size()-1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
执行结果大家也可以预料的到的
顺利执行完成
或
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: ***
at java.util.Vector.get(Unknown Source)
at VectorTest$ListOpt.run(VectorTest.java:44)
at java.lang.Thread.run(Unknown Source)
可能有人这时候就要说,run 方法中的Vector每一个方法都是同步的,这不能作为线程不安全的依据
问题代码在于:
int listsize= list.size();
System.out.println(list.get(listsize-1));
这一问题是由get()的前置条件是以size()的结果来定义的这一事实所带来的。只要看到这种必须使用一种方法的结果作为另一种讲法的输入条件的样式,它就是一个状态依赖,就必须保证至少在调用这两种方法期间元素的状态没有改变。一般来说,做到这一点的唯一方法在调用第一个方法之前是独占性地锁定对象,一直到调用了后一种方法以后。在上面的迭代Vector元素的例子中,需要在迭代过程中同步Vector对象。
再回过头来看看
线程安全性 类要成为线程安全的,首先必须在单线程环境中有正确的行为。如果一个类实现正确(这是说它符合规格说明的另一种方式),那么没有一种对这个类的对象的操作序列(读或者写公共字段以及调用公共方法)可以让对象处于无效状态,观察到对象处于无效状态、或者违反类的任何不可变量、前置条件或者后置条件的情况。 此外,一个类要成为线程安全的,在被多个线程访问时,不管运行时环境执行这些线程有什么样的时序安排或者交错,它必须仍然有如上所述的正确行为,并且在调用的代码中没有任何额外的同步。其效果就是,在所有线程看来,对于线程安全对象的操作是以固定的、全局一致的顺序发生的。 正确性与线程安全性之间的关系非常类似于在描述 ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。
上面例子中的方法之间的状态依赖关系似乎破坏了整个类的一致性和独立性。
不可变 不可变的对象一定是线程安全的,并且永远也不需要额外的同步。因为一个不可变的对象只要构建正确,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。Java 类库中大多数基本数值类如 Integer 、 String 和 BigInteger 都是不可变的。
线程安全 由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。这种线程安全性保证是很严格的 -- 许多类,如 Hashtable 或者 Vector 都不能满足这种严格的定义。
所以。。。