Java无序同步?

时间:2016-03-03 05:34:06

标签: java multithreading synchronized

我开始测试一些棘手的东西,但结果却被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。

我会在一个胎儿的角落生闷气,直到有些灵魂解决这个问题。

2 个答案:

答案 0 :(得分:0)

很抱歉打扰你这个愚蠢的问题。我忘了整数是不变的。想要删除它,但看起来它实际上可以帮助一些......

故事的寓意是

  1. 不要锁定不可变对象,除非您确定它不会更改,例如声明为final。修改后新创建不可变对象。所以你不会锁定共享对象。这正是这里发生的事情。

  2. 如果您必须锁定将要修改的内容,请确保不会修改其引用,例如,通过引用分配。

  3. 为了简化生活,请尝试仅锁定最终对象,以便您知道其引用不会更改。

答案 1 :(得分:0)

虽然您已添加了解释的答案,但我认为值得一提的是&#34;正在创建新对象的原因&#34;

实际上是因为Java的自动装箱/拆箱导致问题。

简而言之,当你在做什么时

Integer i = ...;
++i;

您需要了解Integer不允许++运营商。编译器知道iint包装器(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);
    .....
}