设计线程安全的类
-
过程包含三个基本要素:
- 找出构成对象状态的所有变量
- 找出约束状态变量的不变性条件
- 建立对象状态的并发管理策略
-
同步策略:如何在不违背对象不变条件或后验条件的情况下对其状态的访问操作进行协同
-
如果在某个操作中包含有基于状态的先验条件,那么这个操作就称为依赖状态的操作
实例封闭
-
将对象封装在类的一个实例(作为类的一个私有成员)中,或者封闭在某个作用域内(作为局部变量),再或者封闭在线程内(在某个线程中将对象从一个方法传递到另一个方法,而不是在多个线程之间共享该对象)
-
HahSet不是线程安全的
-
只要包装器对象拥有对底层容器对象的唯一引用(即把底层容器对象封闭在包装器中),那么它就是线程安全的
-
Java监视器模式:Vector/Hashtable,把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护
-
通过在返回客户代码之前复制可变的数据来维持线程安全性,所以可能出现性能问题
线程安全性的委托
-
ConcurrentHashMap/CopyOnWriteArrayList是线程安全的
-
如果某个类有复合操作,那么仅靠委托不足以实现线程安全性,这种情况下,这个类必须提供自己的加锁机制保证这些复合操作都是原子操作,除非整个复合操作都可以委托给状态变量
-
如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全地发布这个变量
-
私有构造函数捕获模式:因为构造函数不能使用同步synchronized,要拷贝一个多状态的对象,可以先把要拷贝的对象传到公有构造函数,在公有构造函数中调用私有构造函数,中间通过一个同步方法传递多个状态:
(https://blog.csdn.net/sinat_34933191/article/details/80557939)
private SafePoint(int[] a) {
this(a[0], a[1]);
}
public SafePoint(SafePoint p) {
this(p.get());
}
public synchronized int[] get() {
return new int[] { x, y };
}
public SafePoint(int x, int y) {
this.x = x;
this.y = y;
}
//线程不安全
//safePoint2.x, safePoint2.y看到的可能不是同一个snapshot的safePoint2
public SafePoint2(SafePoint2 safePoint2) {
this(safePoint2.x, safePoint2.y);
}
public synchronized void set(int x, int y) {
this.x = x;
// Simulate some resource intensive work that starts EXACTLY at this
// point, causing a small delay
try {
Thread.sleep(10 * 100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.y = y;
}
在现有的线程安全类中添加功能
-
最安全的方法是修改原始的类,但通常无法实现;
-
另一种是扩展这个类,extends,不是所有的类都向子类公开;
-
第三种是扩展类的功能而不扩展类本身,将扩展代码放入一个“辅助类”中,(但要使用正确的锁进行客户端加锁操作,不要用辅助类的锁去锁了成员变量,没用)
-
当为现有类添加一个原子操作时,更好的方法是组合,新的类有一个原类的成员,其它方法都照常委托给原类,通过的新类的内置锁保证新添加的和其它原有的方法都是线程安全性;使用时就使用这个新类代替原有的类;(这种方法和第三种的区别在于,第三种中新方法的实现是在客户端,用的还是原类,组合方法包装了一个新类给客户端用)