私人构造函数,以避免竞争条件 [英] Private constructor to avoid race condition

查看:127
本文介绍了私人构造函数,以避免竞争条件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在读这本书 Java Concurrency in Practice session 4.3.5

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

  }

我不清楚它说的位置


私有构造函数的存在是为了避免在复制构造函数实现为此时发生的竞争条件(px,py);这是私有构造函数捕获习惯用法的一个例子(Bloch和Gafter,2005)。

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

我知道它提供了一个getter来检索在数组中同时使用x和y而不是每个都有一个单独的getter,因此调用者将看到一致的值,但为什么是私有构造函数?这里有什么诀窍

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

推荐答案

这里已经有很多答案,但我真的想深入了解一些细节(如就像我的知识让我一样)。我强烈建议您运行答案中的每个样本,以便亲自了解事情的发生和原因。

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.

要了解解决方案,你需要先了解这个问题。

假设SafePoint类实际上是这样的:

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();
    }
}

哪些变量会创建此对象的状态?只有两个:x,y。它们是否受到某些同步机制的保护?好吧,他们是通过内部锁定,通过synchronized关键字 - 至少在setter和getter中。他们在其他地方'感动'吗?当然在这里:

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

您在这里做的是正在阅读宾语。对于一个线程安全的类,你必须协调对它的读/写访问,或者在同一个锁上同步。但是这里没有发生这样的事情。 setXY 方法确实是同步的,但克隆构造函数不是,因此可以以非线程安全的方式调用这两个。我们可以制造这门课吗?

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?

让我们试一试:

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();
}
}

输出很容易就是这个:

 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.

解决方案?


  • 同步构造函数,以便读取将在同一个锁上同步,但Java中的构造函数不能使用synchronized关键字 - 这当然是逻辑。

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

可能使用不同的锁,例如Reentrant lock(如果不能使用synchronized关键字)。但它也行不通,因为构造函数中的第一个语句必须是对this / super 的调用。如果我们实现了一个不同的锁,那么第一行必须是这样的:

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()//其中lock是ReentrantLock,编译器不会去由于上述原因允许这样做。

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!

请参阅此代码示例

/*
 * 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]);    
}

电话会是这样的:

 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?

我们需要找到一种方法来读取和写入同一锁上同步的SafePoint。

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.

我们可以通过调用* getXY 方法安全地阅读,所以我们需要一种方法来使用它,但是我们没有一个构造函数来接受这样的参数 - 创建一个。

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]);
}

然后,实际的调用:

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.

这篇关于私人构造函数,以避免竞争条件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆