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
实际上正在等待。
执行bar
中的同步块后,foo
将执行其同步块,对吗?它会看到obj
的价值是多少?旧的?或者在bar
中设置新的?
(我希望希望看到新值,这就是编码的重点,但我想知道这是否是'安全'的赌注)
答案 0 :(得分:2)
在您描述的确切情况下,是的,在foo的synchronized块内读取obj
将看到前一个条的同步块设置的新值。
有趣的是,它并不总是在那种确切的情况下发生。该程序不是线程安全的,例如,如果在bar()
退出后立即,相同的线程调用另一个bar()
,而foo线程锁定旧对象。条形线程锁定新对象,因此两个线程同时执行,在同一个新对象上执行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上没有obj.doWork()
的当前调用,因为obj.doWork()
只能在锁定完全相同的obj
答案 1 :(得分:1)
它将表现正常,就好像对象引用未在内部更改一样。原因是锁定对象的测试只会进行一次。因此,即使对象在内部发生变化,线程也会继续等待,并且行为会像对象相同[未更改]一样重新发送。
我尝试了另一件事。我在创建新对象之后放置了一个sleep语句,然后启动了下一个线程,并且正如预期的那样,两个线程同时开始工作。 请参阅下面的代码。
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();
}
}
答案 2 :(得分:0)
这是不安全和破碎的。更改锁定的对象不起作用。
当线程试图进入同步块时,它首先必须评估parens中的表达式,以便找出它需要什么锁。如果锁定在此之后发生变化,则线程无法知道,最终获取旧锁并进入同步块。此时它会看到对象并对其进行评估,获取新引用,并使用旧的(现在无关紧要的)锁调用其上的方法,并且不保留新锁,即使某些其他线程可能保持新锁并且可以同时在同一个对象上执行该方法。
答案 3 :(得分: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() {
}
}
答案 4 :(得分:-2)
将会读取obj
的新值。
来自Happens before的标准部分:
在对该字段的每次后续读取之前发生对易失性字段(第8.3.1.4节)的写入。
从共享变量的定义:
所有实例字段,静态字段和数组元素都存储在堆内存中。在本章中,我们使用术语变量来指代字段和数组元素。 局部变量(§14.4),形式方法参数(§8.4.1)和异常处理程序参数(§14.20)永远不会在线程之间共享,并且不受内存模型的影响。
同步块内部obj
的读取与表达式obj
的初始评估是分开的,以确定要锁定的对象的内置监视器。 obj
的重新分配将在第一次阅读之前发生,而不是第二次。由于obj
是volatile
字段,因此第二次读取必须看到obj
的更新值。