私有构造函数捕获模式

《Java并发编程实践》的注解中有提到这一概念。

The private constructor exists to avoid the race condition that would occur if the copy constructor were implemented as this (p.x, p.y); this is an example of the private constructor capture idiom (Bloch and Gafter, 2005).

结合原文代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
@ThreadSafe
public class SafePoint{
    @GuardedBy("this") 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;
          this.y = y;
    }
}

这里的构造器public SafePoint(SafePoint p) { this (p.get()); }是为了捕获另一个实例的状态。get()方法是一个同步方法,为了避免竞态没有分别提供x、y的公有getter方法。

为了保证SafePoint的多线程安全性,在使用另一个实例构造新的实例时,使用了一个私有的构造器。

首先为什么不用下面这种,还是为了避免竞态(p.x和p.y调用不是原子操作)。

1
2
3
public SafePoint(SafePoint p) {
	this(p.x, p.y)
}

同理,这种也不行,两次调用get()方法不是原子操作。

1
2
3
public SafePoint(SafePoint p) {
	this(p.get()[0], p.get()[1])
}

为什么不用直接用数组,编译不通过:Call to "this()" must be first statement in constructor body

1
2
3
4
public SafePoint(SafePoint p) {
 	int[] a = p.get();
	this(a[0], a[1]);
}

为什么接受数组为参数的构造器不能公开,数组a是有外部传入的,并不能保证数组内容不会其他线程修改。

1
2
3
public SafePoint (int [] a) {
	this (a[0], a[1]); 
}

当然我们可以使用下面这种代替私有的构造器,这种方法是安全的,但是会产生重复的初始化代码。

1
2
3
4
5
public SafePoint(SafePoint p) {
    int[] a = p.get();
    this.x = a[0];
    this.y = a[1];
}

再回头看SafePoint的线程安全性,SafePoint有两个状态变量x、y。为了保证线程安全性,没有为其分别提供getter和setter方法,而是将其封装后发布并使用内置锁保护。

可以参考stackoverflow上的示例代码。

Comments

comments powered by Disqus