我开始测试一些棘手的东西,但结果却被0级惊讶......
public class Test implements Runnable
{
Integer i = 0;
public static void main(String[] args)
{
Test test = new Test();
for (int j = 0; j < 100; ++j)
{
Thread t = new Thread(test);
t.setName("" + j);
t.start();
}
}
@Override
public void run()
{
synchronized (i)
{
System.out.println ("-->Entering synch thread " + Thread.currentThread().getName() + " i=" + ++i);
System.out.flush();
System.out.println (" Synchronized, thread " + Thread.currentThread().getName() + " i=" + ++i);
System.out.flush();
try
{
Thread.sleep (0);
}
catch (InterruptedException e ) {}
System.out.println ("<--Exiting synch thread " + Thread.currentThread().getName() + " i=" + ++i);
System.out.flush();
}
}
}
我希望输出在计数中按顺序排列,而不是线程名称。但这就是我所得到的:
-->Entering synch thread 0 i=1
-->Entering synch thread 3 i=3
-->Entering synch thread 4 i=4
Synchronized, thread 4 i=5
-->Entering synch thread 5 i=6
<--Exiting synch thread 4 i=7
-->Entering synch thread 2 i=2
Synchronized, thread 2 i=9
<--Exiting synch thread 2 i=10
Synchronized, thread 5 i=8
这怎么可能?这很简单。这就像被告知圣诞老人不是真的!
如果我同步(这个),它按预期顺序排列。所以至少有一些理智到位。但是,在这种特殊情况下,为什么会同步(i)不足?
我知道,System.out.flush()不是必需的,但考虑到震惊,我必须确定。
运行几次后,很明显线程有i的本地副本。但是,使i volatile不能解决问题。这不应该发生。如果是由于JVM优化,那么这是一个错误。
我正在使用JDK 1.7.0_21。
我会在一个胎儿的角落生闷气,直到有些灵魂解决这个问题。
答案 0 :(得分:0)
很抱歉打扰你这个愚蠢的问题。我忘了整数是不变的。想要删除它,但看起来它实际上可以帮助一些......
故事的寓意是
不要锁定不可变对象,除非您确定它不会更改,例如声明为final。修改后新创建不可变对象。所以你不会锁定共享对象。这正是这里发生的事情。
如果您必须锁定将要修改的内容,请确保不会修改其引用,例如,通过引用分配。
为了简化生活,请尝试仅锁定最终对象,以便您知道其引用不会更改。
答案 1 :(得分:0)
虽然您已添加了解释的答案,但我认为值得一提的是&#34;正在创建新对象的原因&#34;
实际上是因为Java的自动装箱/拆箱导致问题。
简而言之,当你在做什么时
Integer i = ...;
++i;
您需要了解Integer
不允许++
运营商。编译器知道i
是int
包装器(Integer
),因此它会自动将i
转换为int
(自动取消装箱)和当您执行Integer
时,将其转换回++i
(自动装箱)。
即。内部发生的事情将是:
Integer i = ...;
int itmp = i.intValue();
++itmp;
i = Integer.valueOf(itmp);
因此,每次执行++i
时,它都会默默地指向新的Integer
。因此,线程在不同的Integer
对象实例上进行同步,这会导致问题。
并且,在您的情况下执行同步的正确方法,假设您最初锁定实例成员变量,这意味着您的目标是锁定基于Test
对象实例。所以改变就像:
@Override
public void run()
{
synchronized (this)
{
System.out.println ("-->Entering synch thread " + Thread.currentThread().getName() + " i=" + ++i);
.....
}
}
甚至更好:
@Override
public synchronized void run()
{
System.out.println ("-->Entering synch thread " + Thread.currentThread().getName() + " i=" + ++i);
.....
}