2

我有以下类,它应该是线程安全的:

public class ShouldBeMadeThreadSafe {

    private double[] k = {70.0,70.0}; 
    private double[] b = {10.0,10.0};
    private double[] m = {5.0,6.0};

    public synchronized void setKX(double kx) {k[0]=kx;}
    public synchronized void setKY(double ky) {k[1]=ky;}
    public synchronized void setBX(double bx) {b[0]=bx;}
    public synchronized void setBY(double by) {b[1]=by;}
    public synchronized void setMX(double mx) {m[0]=mx;}
    public synchronized void setMY(double my) {m[1]=my;}

    public double[] getK() {return Arrays.copyOf(k, k.length);}
    public double[] getB() {return Arrays.copyOf(b, b.length);}
    public double[] getM() {return Arrays.copyOf(m, m.length);}

}

当然,这在 getter 中存在可见性问题,因为只有在监视器的解锁和锁定之间才能保证发生之前的关系。

显而易见的解决方法是将 synchronized 关键字添加到 getter:

public class OkButIDontLikeDeadlocks {

    private double[] k = {70.0,70.0}; 
    private double[] b = {10.0,10.0};
    private double[] m = {5.0,6.0};

    public synchronized void setKX(double kx) {k[0]=kx;}
    public synchronized void setKY(double ky) {k[1]=ky;}
    public synchronized void setBX(double bx) {b[0]=bx;}
    public synchronized void setBY(double by) {b[1]=by;}
    public synchronized void setMX(double mx) {m[0]=mx;}
    public synchronized void setMY(double my) {m[1]=my;}

    public synchronized double[] getK() {return Arrays.copyOf(k, k.length);}
    public synchronized double[] getB() {return Arrays.copyOf(b, b.length);}
    public synchronized double[] getM() {return Arrays.copyOf(m, m.length);}

}

我不喜欢这个,因为这样我调用了一个持有锁的外星方法,这要求可能的死锁。也许这里不是这种情况,但即便如此我认为它不够优雅(如果我错了,请纠正我)。

现在我想知道以下是否是线程安全的:

public class AmIThreadSafe {

    private volatile double[] k = {70.0,70.0}; 
    private volatile double[] b = {10.0,10.0};
    private volatile double[] m = {5.0,6.0};

    public void setKX(double kx) {k[0]=kx;}
    public void setKY(double ky) {k[1]=ky;}
    public void setBX(double bx) {b[0]=bx;}
    public void setBY(double by) {b[1]=by;}
    public void setMX(double mx) {m[0]=mx;}
    public void setMY(double my) {m[1]=my;}

    public double[] getK() {return Arrays.copyOf(k, k.length);}
    public double[] getB() {return Arrays.copyOf(b, b.length);}
    public double[] getM() {return Arrays.copyOf(m, m.length);}

}

我不认为这是因为我没有重写 volatile 引用本身。

那么,使 ShouldBeMadeThreadSafe 线程安全的最佳方法是什么?

4

2 回答 2

5

您误解了不要调用外星代码持有锁的建议。Arrays.copyOf 不是外来代码:它是 JDK 的一种精确识别、精确指定的方法。外星人代码的情况是这样的:

public synchronized double[] getK(Runnable r) { r.run(); return k; };

在这里,您正在执行run一个完全未知的类的方法,它也可以调用您的其他方法之一,从而破坏不变量。

在所有方法上都可以使用synchronized;是的,volatile不会帮助你。 

于 2013-08-02T15:40:19.923 回答
1

你说得对,这不是线程安全的,因为你给出的原因。

您的三个主要选择是:

  • synchronizegetter 和 setter(如您的帖子中所示)
  • 写入设置器中的volatile引用;setKY(double ky) {k[1]=ky; k = k;}
  • 使用AtomicLongArray,它为您提供类似于 CAS 的数组元素访问器,包括内存可见性。您可以使用Double.doubleToLongBits将双打转换为长整型

老实说,我会选择第一个。这是最简单的,而且动作会非常快,以至于同步不太可能成为瓶颈。

于 2013-08-02T15:46:06.167 回答