在java并发编程实战p58页的脚注的时候没看懂,然后查百度,终于理解了,所以在这里记录一下
脚注:如果将拷贝构造函数实现为this(p.x,p.y),那么会产生竞态 条件,而私有构造函数则可以避免这种竞态条件.这是私有构造函数捕获模式的一个实例。
先上代码
public class Main {
public static void main(String[] args) {
final SafePoint originalSafePoint = new SafePoint(1, 1);
new Thread(new Runnable() {
@Override
public void run() {
originalSafePoint.set(2, 2);
System.out.println("Original : " + originalSafePoint.toString());
}
}).start();
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 + ")";
}
}
当时没有看懂,这个竟态条件到底在哪里。现在解释一下吧
不安全
- 假设当第一个线程在运行originalSafePoint.set(2, 2);的是发生上下文切换,第二个线程开始执行,如果用的是(// 线程不安全类)SafePoint2
- 当调用了this.x = x;的时候又发生上下文切换,运行了originalSafePoint.set(2, 2);,
- 然后又发生上下文切换,被修改过的y又被 this.y = y;这样复制了
安全
- 假设当第一个线程在运行originalSafePoint.set(2, 2);的是发生上下文切换,第二个线程开始执行,如果用的是(// 线程安全类)SafePoint
- 会调用this(p.get());,而这时锁还被第一个运行set的线程持有会阻塞,直道set操作完成,锁才释放
- 这样p.get()得到了set后的正确值,就没有线程安全问题啦