Brian Goetz:SafePoint示例 - 它真的是线程安全的吗?

时间:2015-07-15 10:50:24

标签: java multithreading concurrency

在Brian Goetz的Java Concurrency in Practice中,有以下示例(列出4.11缩短版)。

public class SafePoint {
    private int x, y;
    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;
    }
}

它真的是线程安全的吗? xy既不是volatile也不是final,并且它们的设置没有锁定this,这意味着另一个调用get()的线程可能会看到陈旧值(零)。我在这里想念一下吗?

3 个答案:

答案 0 :(得分:2)

该方法是synchronized指示的线程保存。至于您的示例中的属性xy,这些属性只能在构造函数中编写。因此,您无需进行synchronized。如果您为x和/或y实施某种设置器,则需要设置这些设置器synchronized和属性volatile

答案 1 :(得分:1)

  

@jameslarge你读过这个标题吗?...

好的,我在你正在阅读的书的索引中查找了“安全出版物”,这就是它在第3.5.3节中所说的内容:

  

正确构建的对象可以安全地发布;从static初始化程序初始化对象引用,将对它的引用存储到volatile字段或AtomicReference中,将对它的引用存储到正确的final字段中构造对象,或将对它的引用存储到一个被锁定正确保护的字段中。

因此,答案取决于SafePoint类的使用方式。

如果线程A构造一个SafePoint(5, 15)实例,然后将其存储在非final,非volatile,非 - static字段,然后是线程B调用safePoint.get();返回给主题B的值可以是[0, 0],也可以是[5, 0][0, 15][5, 15]

如果safePoint字段为finalvolatile,或者它是由静态初始化程序设置的static字段,则返回给主题B的值永远是[5, 15]

此示例在我的环境中打印result = [5, 15],但如果我读到Goetz先生的书是正确的,那么JLS 允许它返回任何其他三种可能性。

public class SafePointDemo {
SafePoint safePoint;

void threadAwork() {
    safePoint = new SafePoint(5, 15);
    sleep(15000);
}

void threadBwork() {
    sleep(10000);
    int[] result = safePoint.get();
    System.out.println("result = [" + result[0] + ", " + result[1] + "]");
}

private void sleep(long n) {
    try {
    Thread.sleep(n);
    } catch(InterruptedException ex) {
    //do nothing
    }
}

class SafePoint {
    private int x, y;
    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;
    }
}

public static void main(String[] args) {
    SafePointDemo safePointDemo = new SafePointDemo();
    Thread threadA = new Thread(() -> safePointDemo.threadAwork());
    Thread threadB = new Thread(() -> safePointDemo.threadBwork());
    threadA.start();
    threadB.start();
}

}

答案 2 :(得分:0)

虽然整数不是最终的,但只是在构造函数中赋予它们值,你实际上是将它们作为最终值,因为在设置整数之前,调用者无法使用指向该对象的指针。

get方法的同步只保证一次只能有一个线程执行get方法,任何其他线程将被挂起,直到第一个完成。