当前位置: 首页 > 面试题库 >

私人建造者避免比赛条件

李烨烁
2023-03-14
问题内容

我正在阅读本书第Java Concurrency in Practice4.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)。

我知道它提供了一个同时在数组中一次获取x和y的getter,而不是为每个数组分别获取一个getter,因此调用方将看到一致的值,但是为什么要使用private构造函数呢?这有什么窍门


问题答案:

这里已经有很多答案了,但是我真的很想深入研究一些细节(就我所知,这是我的全部)。我会强烈建议您运行答案中出现的每个示例,以亲自了解情况如何以及原因。

要了解解决方案,您需要首先了解问题。

假设SafePoint类实际上如下所示:

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。它们受到某种同步机制的保护吗?好吧,它们是通过内在锁,通过synced关键字进行的-
至少在setter和getter中。他们在其他任何地方被“感动”吗?当然在这里:

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

您在这里所做的就是从对象中 读取 。为了使一个类是线程安全的,您必须协调 对该 类的读/写访问,或 在相同的锁上进行同步。
但是这里没有发生任何事情。该 setXY 方法确实是同步的,但克隆的构造不是,因此调用这两个可以在非线程安全的方式来完成。我们可以上这节课吗?

让我们尝试一下:

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}

这是逻辑,因为一个线程update ==写入我们的对象,而另一个线程正在读取它。它们不会在某个通用锁上同步,因此不会同步输出。

解?

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

  • 可能使用其他锁,例如可重入锁(如果无法使用synced关键字)。但这也行不通,因为 构造函数中的第一条语句必须是对此this / super的调用 。如果我们实现一个不同的锁,那么第一行必须是这样的:

lock.lock()//其中锁是ReentrantLock,出于上述原因,编译器将不允许这样做。

  • 如果使构造函数成为方法怎么办?当然可以!

参见此代码示例

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

这次代码按预期运行,因为读和写在同一锁上同步,但是 我们删除了构造函数 。如果不允许这样做怎么办?

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

理想情况下,我们想要这样的东西:

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

但是编译器不允许这样做。

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

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

然后,实际的调用:

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

请注意,该构造函数是私有的,这是因为我们不想公开另一个公共的构造函数,并 再次 考虑该类的不变量,因此我们将其设为私有-只有我们可以调用它。



 类似资料:
  • 我不清楚它写在哪里 私有构造函数的存在是为了避免将复制构造函数实现为此(p.x,p.y)时出现的争用条件;这是私有构造函数捕获习惯用法的一个例子(Bloch和Gafter,2005)。 我知道它提供了一个getter来在数组中同时检索x和y,而不是为每一个单独的getter,所以调用者将看到一致的值,但为什么是私有构造函数呢?这里有什么诀窍

  • 我是科特林的新手。我想问Kotlin的私人建造者是干什么的?。我的意思是,如果我们不能创建它的实例,应该是什么类呢?

  • 问题内容: 这是带有潜在竞争条件的Django视图的简单示例: 竞争条件应该非常明显:用户可以两次发出此请求,并且该应用程序可能同时执行,从而导致其中一个请求覆盖另一个请求。 假设函数相对复杂,并且基于无法放置在单个存储过程中并且难以放置在存储过程中的各种奇怪的东西进行计算。 所以这是我的问题:django可使用哪种锁定机制来处理类似的情况? 问题答案: Django 1.4+支持select_f

  • 问题内容: 通常我会尽可能避免转换类型,因为我认为这是不良的编码实践,并且可能会导致性能下降。 但是,如果有人要我解释为什么会这样,我可能会像前灯中的鹿一样看它们。 那么,为什么/何时铸造不好? 它对于Java,C#,C ++是通用的,还是每个不同的运行时环境都按照自己的方式处理? 欢迎使用任何语言的细节,例如为什么在c ++中不好? 问题答案: 您已经用三种语言标记了这三种语言,答案在三种语言之

  • 我有一个分布式任务队列,其中的任务如下所示: 这里有一个竞争条件:如果任务队列软件在完全相同的时间启动其中两个任务,它们都将从数据库中获得相同的<code>old_path</code>,并且竞争失败者的取消链接调用失败(将失败者的新路径从未来的取消链接中孤立出来)。 有没有办法让我构建它来绕过这场比赛?如果需要,我可以从当前设计中抛出几乎任何东西。具体来说,我使用的是PostgreSQL,Pyt