当前位置: 首页 > 知识库问答 >
问题:

避免竞争条件的私有构造函数

吴单鹗
2023-03-14
  @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;
       }

  }

我不清楚它写在哪里

私有构造函数的存在是为了避免将复制构造函数实现为此(p.x,p.y)时出现的争用条件;这是私有构造函数捕获习惯用法的一个例子(Bloch和Gafter,2005)。

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

共有1个答案

尉迟鑫鹏
2023-03-14

这里已经有一堆答案,但我真的想深入一些细节(我的知识让我)。我强烈建议您运行答案中的每个示例,以亲眼看看事情是如何发生的以及为什么会发生。

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

假设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。它们是否受到某种同步机制的保护?它们是通过内部锁,通过synchronized关键字--至少在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} 

这是逻辑,因为一个线程updates=写入我们的对象,另一个线程正在从中读取。它们在某些公共锁上不同步,因此输出。

>

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

    可以使用不同的锁,如可重入锁(如果不能使用synchronized关键字)。但它也不能工作,因为构造函数内部的第一条语句必须是对this/super的调用。如果我们实现一个不同的锁,那么第一行必须是这样的:

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

  •  类似资料:
    • 问题内容: 我正在开发的应用程序中存在潜在的竞争状况,我想在查询中考虑和避免这种情况。 总结应用程序流程… 在表中创建一个新行: 通过查看对时间敏感的表格,找出Bar先生是否是获胜者: 如果他是赢家,请相应地更新他的条目行: 由于每个奖项只能颁发一次,因此我需要消除比赛条件的任何可能性,在这种情况下,另一个过程可以查询奖项表并更新上述步骤2和3之间的条目表。 我一直在做一些研究,发现了大量关于事务

    • 9.1. 竞争条件 在一个线性(就是说只有一个goroutine的)的程序中,程序的执行顺序只由程序的逻辑来决定。例如,我们有一段语句序列,第一个在第二个之前(废话),以此类推。在有两个或更多goroutine的程序中,每一个goroutine内的语句也是按照既定的顺序去执行的,但是一般情况下我们没法去知道分别位于两个goroutine的事件x和y的执行顺序,x是在y之前还是之后还是同时发生是没法

    • 问题内容: 我正在阅读本书第4.3.5节 我不清楚它在哪里说 私有构造函数的存在是为了避免如果将复制构造函数实现为(px,py)时会发生竞争情况。这是私有构造函数捕获习语的一个示例(Bloch和Gafter,2005)。 我知道它提供了一个同时在数组中一次获取x和y的getter,而不是为每个数组分别获取一个getter,因此调用方将看到一致的值,但是为什么要使用private构造函数呢?这有什么

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

    • 9.6. 竞争条件检测 即使我们小心到不能再小心,但在并发程序中犯错还是太容易了。幸运的是,Go的runtime和工具链为我们装备了一个复杂但好用的动态分析工具,竞争检查器(the race detector)。 只要在go build,go run或者go test命令后面加上-race的flag,就会使编译器创建一个你的应用的“修改”版或者一个附带了能够记录所有运行期对共享变量访问工具的tes

    • 问题内容: 如何停止MySQL中的竞争条件?当前的问题是由一个简单的算法引起的: 从表中选择一行 如果不存在,将其插入 然后会得到重复的行,或者如果您通过唯一/主键阻止它,则会出现错误。 现在,通常我认为事务在这里有所帮助,但是由于该行不存在,所以事务实际上并没有帮助(或者我是否错过了什么?)。 LOCK TABLE听起来有些矫kill过正,尤其是如果该表每秒更新多次。 我唯一想到的其他解决方案是