Private Constructor capture idiom

1. Private Constructor capture idiom


java puzzler 53

The following code fails to compile, tips: Cannot refer to an instance field arg while explicitly invoking a constructor

package arkblue.lang.javapuzzler.n53;

class Thing {
        public Thing(int i) {

        }
}

public class MyThing extends Thing {
        private final int arg;

        public MyThing() {
                super(arg = Math.round(12L)); // Compilation failed
        }

}

Suppose Thing is a library class, only a constructor argument, did not provide any access device, you do not have permission to access internal and therefore can not modify it.

At this time, want to write a subclass, the constructor through the bar with SomtOtherClass.func () method to calculate the super class constructor parameter. Return value of this method call can return again and again different values, you want to pass this value is stored in the parent class of a final sub-class instance. So with the above code, but can not compile.

Modify

class SomeOtherClass {
        static int func() {
                return Math.round(12L);
        }
}

public class MyThing extends Thing {
        private final int arg;

        public MyThing() {
                this(SomeOtherClass.func());
        }

        private MyThing(int i) {
                super(i);
                arg = i;
        }
}

The program uses the alternate constructor invocation mechanism (alternate constructor invocation)

In the private constructor, the expression SomeOtherClass.func () the value has been captured in the variable i, and it can be returned after the superclass constructor to store the final type of domain arg.

source: http://www.quweiji.com/capture-the-private-constructor-private-constructor-capture/


2. Private constructor to avoid race condition

I am reading the book Java Concurrency in Practice session 4.3.5

  @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;
       }

  }

I am not clear where It says

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).

I understand that it provides a getter to retrieve both x and y at once in a array instead of a separate getter for each, so the caller will see consistent value, but why private constructor ? what's the trick here

share improve this question
 
4 
It's only private because they don't want other people to use it ;-) –  oldrinb Aug 19 '12 at 18:38
 
I have updated the answer ) –  Boris Treukhov Aug 19 '12 at 19:52
 
@user1389813 you should notice that this pattern could be easily avoided if the constructor would be refactored to a method for example. See my answer also ;) –  Eugene Aug 20 '12 at 12:42

up vote 19 down vote accepted

There are already a bunch of answers here, but I would really like to dive into some details (as much as my knowledge let's me). I will strongly advise you to run each sample that is present here in the answer to see for yourself how things are happening and why.

To understand the solution, you need to understand the problem first.

Suppose that the SafePoint class actually looks like this:

class SafePoint {
    private int x;
    private int y;

    public SafePoint(int x, int y){
        this.x = x;
        this.y = y;
    }

    public SafePoint(SafePoint safePoint){
        this(safePoint.x, safePoint.y);
    }

    public synchronized int[] getXY(){
        return new int[]{x,y};
    }

    public synchronized void setXY(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;
    }

    public String toString(){
      return Objects.toStringHelper(this.getClass()).add("X", x).add("Y", y).toString();
    }
}

What variables create the state of this object? Just two of them : x,y. Are they protected by some synchronization mechanism? Well they are by the intrinsic lock, through the synchronized keyword - at least in the setters and getters. Are they 'touched' anywhere else? Of course here:

public SafePoint(SafePoint safePoint){
    this(safePoint.x, safePoint.y);
} 

What you are doing here is reading from your object. For a class to be Thread safe, you have to coordinate read/write access to it, or synchronize on the same lock. But there is no such thing happening here. The setXY method is indeed synchronized, but the clone constructor is not, thus calling these two can be done in a non thread-safe way. Can we brake this class?

Let's try this out:

public class SafePointMain {
public static void main(String[] args) throws Exception {
    final SafePoint originalSafePoint = new SafePoint(1,1);

    //One Thread is trying to change this SafePoint
    new Thread(new Runnable() {
        @Override
        public void run() {
            originalSafePoint.setXY(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();
}
}

The output is easily this one:

 Copy : SafePoint{X=2, Y=1}
 Original : SafePoint{X=2, Y=2} 

This is logic, because one Thread updates=writes to our object and the other is reading from it. They do not synchronize on some common lock, thus the output.

Solution?

  • synchronized constructor so that the read will synchronize on the same lock, but Constructors in Java can not use the synchronized keyword - which is logic of course.

  • may be use a different lock, like Reentrant lock (if the synchronized keyword can not be used). But it will also not work, because the first statement inside a constructor must be a call to this/super. If we implement a different lock then the first line would have to be something like this:

    lock.lock() //where lock is ReentrantLock, the compiler is not going to allow this for the reason stated above.

  • what if we make the constructor a method? Of course this will work!

See this code for example

/*
 * this is a refactored method, instead of a constructor
 */
public SafePoint cloneSafePoint(SafePoint originalSafePoint){
     int [] xy = originalSafePoint.getXY();
     return new SafePoint(xy[0], xy[1]);    
}

And the call would look like this:

 public void run() {
      SafePoint copySafePoint = originalSafePoint.cloneSafePoint(originalSafePoint);
      //SafePoint copySafePoint = new SafePoint(originalSafePoint);
      System.out.println("Copy : " + copySafePoint.toString());
 }

This time the code runs as expected, because the read and the write are synchronized on the same lock, but we have dropped the constructor. What if this were not allowed?

We need to find a way to read and write to SafePoint synchronized on the same lock.

Ideally we would want something like this:

 public SafePoint(SafePoint safePoint){
     int [] xy = safePoint.getXY();
     this(xy[0], xy[1]);
 }

But the compiler does not allow this.

We can read safely by invoking the *getXY method, so we need a way to use that, but we do not have a constructor that takes such an argument thus - create one.

private SafePoint(int [] xy){
    this(xy[0], xy[1]);
}

And then, the actual invokation:

public  SafePoint (SafePoint safePoint){
    this(safePoint.getXY());
}

Notice that the constructor is private, this is because we do not want to expose yet another public constructor and think again about the invariants of the class, thus we make it private - and only we can invoke it.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值