-1

I started testing out something tricky but ended being surprised by level 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();
        }
    }
}

I expected the output to be in order in the count, not thread name. But this is what I got instead:

-->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

How can this be? This is as simple as it gets. This is like being told Santa isn't true!

If I synchronized(this), it's in order as expected. So at least some sanity is in place. But, still, in this particular situation, why would synchronized(i) be insufficient?

I know, System.out.flush() isn't necessary but given the shock, I had to make sure.

After running it a few times, it's clear that threads have their local copy of i. Making i volatile doesn't solve the problem, though. This shouldn't be happening. If it's due to JVM optimization, then it's a bug.

I'm using JDK 1.7.0_21.

I'll be sulking in a corner in a fetal position until some kind soul solves this.

2 Answers2

0

Though you have added an answer with explanation, I think it worth mentioning the reason of "new object being created"

It is actually because of auto-boxing/unboxing of Java causing the problem.

In brief, when you are doing

Integer i = ...;
++i;

You need to understand Integer does not allow ++ operator. It is the compiler that knows that i is an int wrapper (Integer), so that it automatically convert i to int (auto-unboxing) and convert it back to Integer (auto-boxing) when you are doing ++i.

i.e. What happened internally will be something like:

Integer i = ...;
int itmp = i.intValue();
++itmp;
i = Integer.valueOf(itmp);

Hence, every time you do ++i, it is pointing to a new Integer silently. Therefore, threads are synchronizing on different Integer object instances, which cause the problem.


And, the proper way to perform synchronization in your case, given that you were originally locking an instance member variable, it means you are aiming to lock base on the Test object instance. So the change is as easy as:

@Override
public void run()
{
    synchronized (this)
    {
        System.out.println ("-->Entering synch thread " + Thread.currentThread().getName() + " i=" + ++i);
        .....
    }
}

or even better:

@Override
public synchronized void run()
{
    System.out.println ("-->Entering synch thread " + Thread.currentThread().getName() + " i=" + ++i);
    .....
}
Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
-1

Sorry to bother you with the stupid question. I forgot Integer is immutable. Wanted to delete it but it looks like it actually could help a few...

So the moral of the story is

  1. Don't lock on immutable objects unless you're sure it won't change, e.g., declared final. Immutable objects are newly created when modified. So you won't be locking on a shared object. This is exactly what happened here.

  2. If you have to lock on something that will be modified, make very sure its reference is not modified, e.g., by ref assignment.

  3. To make life simpler, try lock on final objects only so that you know its reference won't change.

  • "Immutable objects are newly created when modified" obviously you messed up the concepts. Immutable objects CANNOT be modified. I think you got the idea but you seemed explained in a weird way. Anyway, for your problem, it is as simple as changing `synchronized(i)` to `synchronized`. – Adrian Shum Mar 03 '16 at 06:13
  • @AdrianShum changing it how? Making the method `synchronized`? That won't help. – MartinS Mar 03 '16 at 06:30
  • @MartinS yes it help. Make the `run()` method `synchronized` (removing `synchronized(i)` of course), or change the `synchronized(i)` to `synchronized(this)` will solve the problem. – Adrian Shum Mar 03 '16 at 06:33
  • @AdrianShum no it won't because the Threads will lock on themselves and not something shared so they will all be able to enter the protected region at the same time. – MartinS Mar 03 '16 at 06:34
  • @MartinS Why don't you give it a try before you comment? :) All 100 threads are invoking the `run()` of the same `Runnable` (the `Test` object). Declaring `run()` as `synchronized` (or doing `synchronized(this)`) is synchronizing on the same `Test` object instance. Obviously you have messed up in understanding what will be locked in such case – Adrian Shum Mar 03 '16 at 06:36
  • Waaahhhh stupid me. I actually tried it - but with different Runnable objects. Yes you are right. – MartinS Mar 03 '16 at 06:38
  • Adrian, thanks for your clarification. But I was trying to highlight the diff between modifying value and modifying ref'd mem address. And a variable of immutable type can have its ref'd mem address modified, but not value. I wish Java had language support for immutability, which would make it explicit. – Black Swan Mar 03 '16 at 06:50
  • that's why I said the description is not accurate. Even for mutable objects, you can still do something like `i = new MutableObj()` which caused the same problem.. I am not saying what you said is wrong, it is just not accurately describing the issue. And please always remember, for non-primitive variable in Java they are always reference (i.e. Pointer), which means the "value" of such kind of variables are always the address . In Java, you cannot have a variable being the object itself, it can only be a pointer to object. – Adrian Shum Mar 03 '16 at 10:34