关于线程安全代码的问题

时间:2011-08-03 12:15:11

标签: java synchronization thread-safety

我有一个课程如下

public MyClass{

Boolean flag = false;

    public Boolean getflag(){
       synchronized(flag){
          //BLOCK 1
          return flag;
       }
    }

    public Boolean setflag(){
       synchronized(flag){
           //BLOCK 2
           this.flag = flag;
       }
    }
}

两种方法都在对象标志上同步。现在我怀疑是两个不同的线程可以同时执行同步块(1& 2)。 可能出现以下情况吗? 1)线程1是设置标志值,线程2是否同时获取其值?

4 个答案:

答案 0 :(得分:9)

是的,它可以。请注意您正在设置的同一对象上进行同步。因此,setter可以将对象引用更改为不同的东西,然后当setter仍然在synchronized块中时,getter可以在新对象上同步。

但还有更多:flag(通常)是对系统范围内的单例Boolean.TRUEBoolean.FALSE之一的引用,因此(至少在理论上)它可以锁定在你班上的这些外面,即使没有以任何方式提到你的班级。在这种情况下,你可能会陷入僵局,你可能很难搞清楚原因。

(另请注意,当前形式的代码是错误的,因为setter没有参数,因此this.flag = flag将引用分配给它自己 - 但是上面我假设你的意思是它表现得像普通的setter: )

修复是使用专用的private final锁定对象(如果你想绝对确保外面的任何人都不能在你班级中使用的同一个锁上同步 - 我想你的初衷是) :

public MyClass{

    private final Object lock = new Object();
    private Boolean flag = false;

    public Boolean getflag(){
       synchronized(lock){
          //BLOCK 1
          return flag;
       }
    }

    public void setflag(Boolean flag){
       synchronized(lock){
           //BLOCK 2
           this.flag = flag;
       }
    }
}

如果您对内部使用的同一锁定的其他同步不太担心,您可以简单地制作方法synchronized(在这种情况下,它们会锁定this)。

答案 1 :(得分:2)

我假设你的setFlag方法实际上应该有一个参数而没有返回值?

对我来说这看起来不错。

  • 标志设置为参考r1
  • 线程1调用setFlag(r2)
  • 线程1获取r1上的锁定
  • 线程1将标志设置为引用r2
  • 线程2获取r2上的锁定
  • 两个线程实际上是在同步块中并发执行,但是锁定在不同的对象上......

基本上,我认为在可变字段上进行同步是一个坏主意。有一个recent question about this你可能会感兴趣。

我会改用这个设计:

private final Object lock = new Object();
private boolean flag;

public void setFlag(boolean flag) {
    synchronized (lock) {
        this.flag = flag;
    }
}

public boolean getFlag() {
    synchronized (lock) {
        return flag;
    }
}

(或者只是使用一个volatile字段。这实际上取决于该类中的其他内容。)

答案 2 :(得分:0)

问题可以在方法setFlag中 - 它将更改“锁定对象”以进行同步。您必须确保在同一对象上进行同步 private Object lock = new Object(); 并在对象锁定上同步。

答案 3 :(得分:0)

这不会起作用。您在监视器上阻止了flag引用的对象。想象一下,如果一个线程进入setter,锁定当前的flag对象,然后flag指向一个新的Flag。然后第二个线程进入getter,并且可以自由获取lock on flag,因为它现在指向另一个对象。

因此,你可以让两个线程看起来都锁定在'flag'上,但是在不同的对象上。这就是为什么用于锁定的物体通常应该被宣布为最终物品,以避免出现这种情况。