4.3.5 重点:理解发布状态可变的车辆追踪器
理解脚注:
p58页:如果将拷贝构造函数实现为this(p.x,p.y),那么会产生竞态 条件,而私有构造函数则可以避免这种竞态条件.这是私有构造函数捕获模式的一个实例。
public class Main {
public static void main(String[] args) {
final SafePoint originalSafePoint = new SafePoint(1, 1);
// One Thread is trying to change this SafePoint
new Thread(new Runnable() {
@Override
public void run() {
originalSafePoint.set(2, 2);
System.out.println("Original : " + originalSafePoint.toString());
}
}).start();
// The other Thread is trying to create a copy. The copy, depending on
// the JVM, MUST be either (1,1) or (2,2)
// depending on which Thread starts first, but it can not be (1,2) or
// (2,1) for example.
new Thread(new Runnable() {
@Override
public void run() {
SafePoint copySafePoint = new SafePoint(originalSafePoint);
System.out.println("Copy : " + copySafePoint.toString());
}
}).start();
}
}
// 线程安全类
class SafePoint {
private int x, y;
private SafePoint(int[] a) {
this(a[0], a[1]);
}
public SafePoint(SafePoint p) {
this(p.get());
}
public SafePoint(int x, int y) {
this.x = x;
this.y = y;
}
public synchronized int[] get() {
return new int[] { x, 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;
}
@Override
public String toString() {
return "(" + x + "," + y + ")";
}
}
// 线程不安全类
class SafePoint2 {
private int x;
private int y;
public SafePoint2(int x, int y) {
this.x = x;
this.y = y;
}
public SafePoint2(SafePoint2 safePoint2) {
this(safePoint2.x, safePoint2.y);
}
public synchronized int[] get() {
return new int[] { x, 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;
}
@Override
public String toString() {
return "(" + x + "," + y + ")";
}
当main中使用线程安全类SafePoint时,运行结果为:
Copy : (2,2)
Original : (2,2)
当main中使用线程不安全类SafePoint2时,运行结果为:
Copy : (2,1)
Original : (2,2)
原因:SafePoint2为线程不安全类,是因为发生了竞态条件,本质上是因为写操作set(int x, int y)和拷贝构造函数中的读操作this(safePoint2.x, safePoint2.y)不同步,发生竞态条件造成的。核心原因是类的构造函数不能使用同步机制
而get( )是加锁同步的,是可以安全使用的,因此可以构造一个接受get( )参数的私有构造函数(设为私有,防止外部调用):
private SafePoint(int[] a) {
this(a[0], a[1]);
}
再构造一个使用上述构造函数的公有拷贝构造函数
public SafePoint(SafePoint p) {
this(p.get());
}
这样公有拷贝构造函数不会与写操作set(int x, int y)发生竞态条件,可以安全的拷贝旧对象或最新对象,保证x和y处于一致(处于同一不变性条件),但个人觉得SafePoint类仍然存在线程安全问题(没有将x, y设为final),参照Java并发编程实战P42,3.5.1节。
具体参考stackoverflow上Eugene的精彩的解答:http://stackoverflow.com/questions/12028925/private-constructor-to-avoid-race-condition/12029346