7
private volatile Object obj = new MyObject();
void foo() 
{
  synchronized(obj)
  {
    obj.doWork();
  }
}
void bar()
{
  synchronized(obj)
  {
    obj.doWork();
    obj = new MyObject(); // <<<< notice this line (call it line-x)
  }
}

假设在某个时间点,一个线程 t_bar正在执行bar(),另一个线程正在t_foo执行,foo并且t_bar刚刚获得obj,因此t_foo实际上正在等待。

在执行完同步块后barfoo会执行它的同步块,对吗?obj它会看到什么价值?旧的?还是新装的bar

(我希望看到新值,这就是以这种方式编码的重点,但我想知道这是否是一个“安全”的赌注)

4

5 回答 5

2

在您描述的确切情况下,是的,读取obj内部 foo 的同步块将看到前一个 bar 的同步块设置的新值。

有趣的是,它并不总是在那种确切的情况下发生。该程序不是线程安全的,例如,如果在bar()退出后立即相同的线程调用另一个bar(),而 foo 线程正在锁定旧对象。bar 线程锁定新对象,因此两个线程同时执行,都obj.doWork()在同一个新对象上执行。

我们可能可以通过以下方式部分修复它

// suppose this line happens-before foo()/bar() calls
MyObject obj = new MyObject();

void foo()
    while(true)
        MyObject tmp1 = obj;
        synchronized(tmp1)
            MyObject tmp2 = obj;
            if(tmp2==tmp1)
                tmp2.doWork();
                return;
            // else retry

这至少保证当前没有obj.doWork()对同一个 obj 的调用,因为obj.doWork()只能发生在锁定完全相同的同步块中obj

于 2013-04-26T17:26:19.583 回答
1

它将表现正常,就好像对象引用没有在内部更改一样。原因是对象锁定测试只会进行一次。因此,即使对象在内部发生变化,线程也会继续等待,并且行为将与对象相同 [未更改] 相同。

我尝试了另一件事。我在新对象创建后放置了一个睡眠语句,然后启动下一个线程,并且正如预期的那样,两个线程同时开始工作。请参阅下面的代码。

public class ChangeLockObjectState {

    private volatile Object obj = new Object();

    void foo() {
        synchronized (obj) {
            try {
                System.out.println("inside foo");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    void bar() {
        synchronized (obj) {
            try {
                System.out.println("inside bar");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            obj = new Object(); // <<<< notice this line (call it line-x)

            System.out.println("going out of  bar");

            try {

                Thread.sleep(5000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            System.out.println("wait over");

        }
    }

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        final ChangeLockObjectState test = new ChangeLockObjectState();

        new Thread(new Runnable() {

            @Override
            public void run() {
                test.bar();

            }
        }).start();

        Thread.sleep(6000);

        new Thread(new Runnable() {

            @Override
            public void run() {
                test.foo();

            }
        }).start();

    }

}
于 2013-04-26T16:43:58.927 回答
0

显示新值。即使没有制作它也可以工作obj volatile。这是因为同步仍然保留在旧对象上,并在等待线程 (t_foo) 进入后提供对新值的可见性。这是测试:

public class Main3 {
    private MyObject obj = new MyObject(1);

    void foo()
    {
        synchronized(obj)
        {
            System.out.println(obj.number);
            obj.doWork();
        }
    }

    void bar()
    {
        synchronized(obj)
        {
            System.out.println(obj.number);

            obj.doWork();

            //force the foo thread to wait at the synchronization point
            for(int i = 0; i < 1000000000l; i++);

            obj = new MyObject(2); // <<<< notice this line (call it line-x)
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final Main3 m3 = new Main3();

        Thread t1 = new Thread( new Runnable() {
            @Override
            public void run() {
                m3.bar();
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                m3.foo();
            }
        });

        t1.start();
        t2.start();
    }
}

class MyObject {
    int number;

    public MyObject(int number) {
        this.number = number;
    }

    public void doWork() {
    }
}
于 2013-04-26T17:19:14.020 回答
0

这是不安全和损坏的。更改您锁定的对象不起作用。

当一个线程试图进入一个同步块时,它首先必须计算括号中的表达式,以确定它需要什么锁。如果在那之后锁发生了变化,线程没有任何办法知道,它最终会获取旧锁并进入同步块。此时,它看到对象并对其进行评估,获取新的引用,并使用旧的(现在不相关的)锁调用它的方法,而不持有新的锁,即使其他线程可能持有新的锁并且可以同时在同一个对象上执行该方法。

于 2013-04-26T16:43:51.763 回答
-2

obj将读取的新值。

从标准的发生在之前的部分:

对 volatile 字段(第 8.3.1.4 节)的写入发生在对该字段的每次后续读取之前。

从共享变量的定义:

所有实例字段、静态字段和数组元素都存储在堆内存中。在本章中,我们使用术语变量来指代字段和数组元素。局部变量(第 14.4 节)、形式方法参数(第 8.4.1 节)和异常处理程序参数(第 14.20 节)永远不会在线程之间共享,并且不受内存模型的影响。

同步块内部的读取obj与表达式的初始评估是分开的,obj以确定要锁定哪个对象的内置监视器。的重新分配obj将在第一次读取之前发生,但不会在第二次读取之前发生。由于obj是一个volatile字段,因此第二次读取必须看到 的更新值obj

于 2013-04-26T16:45:48.310 回答