1

First, what I've read through:

And I followed the myriad links to duplicates listed on most of those posts. So I apologize ahead of time if this is a duplicate. I don't feel my question was answered by any of those, or the subsequent links. But then again, I am asking now because I have no clue as to what is going on here. Now on to the main event...

I have a pair of classes, A and B. Class B has an instance of A as a member:

Class A:

public class A {
    private final int count;

    public A(int val) {
        count = val;
    }

    public int count() { return count; }

    public A incrementCount() {
        return new A(count + 1);
    }

    public void doStuff(long i) {
        try {
            Thread.sleep(i * 100);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Class B:

public class B implements Runnable{
    private A a;

    public B() { a = new A(0); }

    @Override
    public void run() {
        for (int i = 1; i < 5; i++) {
                a.doStuff(i);
                a = a.incrementCount();
            }
    }
}

And I have a class that takes an instance of B and passes it to two threads, starts both threads, and then lets them do their thing:

public class App {
    public static void main(String[] args) {
        B b = new B();
        Thread b1 = new Thread(b, "b1");
        b1.start();
        Thread b2 = new Thread(b, "b2");
        b2.start();
        try {
            b1.join();
            b2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

What I expect is, for the instance of A contained by b, count to increment from 0 to 8, sequentially.

But for this code:

synchronized public A incrementCount() {
     return new A(count + 1);
}

synchronized public void doStuff(long i) {
    try {
        Thread.sleep(i * 100);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Or this code (equivalent to above I think):

public A incrementCount() {
    synchronized (this) {
        return new A(count + 1);
    }
}

public void doStuff(long i) {
    synchronized (this){
        try {
            Thread.sleep(i * 100);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

I get results like this:

THREAD|    OBJECT     |COUNT
----------------------------
main  |testbed.A@11121|  0    
b1    |testbed.A@64f6c|  1    
b1    |testbed.A@87238|  2    
b2    |testbed.A@2bb51|  2    
b2    |testbed.A@17d5d|  3    
b1    |testbed.A@16fa4|  4    
b2    |testbed.A@95c08|  4    
b1    |testbed.A@191d8|  5    
b2    |testbed.A@2d9c0|  5    

Clearly, something is amiss. I also think it is worth noting that the objects all appear to be unique objects, even though there are duplicate numbers.

But for this code (in Class A):

private final Object incrementLock = new Object();
private final Object doStuffLock = new Object();

...

public A incrementCount() {
    synchronized (incrementLock) {
        return new A(count + 1);
    }
}

public void doStuff(long i) {
    synchronized (doStuffLock){
        try {
            Thread.sleep(i * 100);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Or this code (in Class B):

@Override
synchronized public void run() {
    for (int i = 1; i < 5; i++) {
        a.doStuff(i);
        a = a.incrementCount();
    }
}

I get the results I expect:

THREAD|    OBJECT       |COUNT
------------------------------
main  |testbed.A@f7f540 |  0    
b1    |testbed.A@64f6cd |  1    
b2    |testbed.A@872380 |  2    
b1    |testbed.A@2bb514 |  3    
b2    |testbed.A@17d5d2a|  4    
b1    |testbed.A@16fa474|  5    
b2    |testbed.A@95c083 |  6    
b1    |testbed.A@191d8c1|  7    
b2    |testbed.A@2d9c06 |  8

Since there is only a single object being accessed by both threads (b1 and b2), why aren't synchronized (this) or synchronized public... locking the object instance, preventing both threads from entering the synchronized blocks and corrupting count, so to speak? Or did I miss something?

Community
  • 1
  • 1
nihilon
  • 834
  • 2
  • 8
  • 23

2 Answers2

1

You should synchronize the code in B, where you have multiple threads mutating the state (instance variable a). It does not make sense to synchronize methods in A, because instances of the class are really just immutable value objects.

When synchronizing methods on this in A, the most problematic part in the code is this:

a = a.incrementCount();

because there you leak the monitor outside of the class and re-assign the variable that's holding it.

Even though the version of A that uses different monitor objects for both methods seems to work, there is a race condition (which you could see if you add more threads and iteration steps and reduce/eliminate the sleep time in doStuff()) because nothing guarantees that the correctly incremented a got assigned in the above code.

The only way to make your code thread-safe is to synchronize the run() method in B.

Mick Mnemonic
  • 7,808
  • 2
  • 26
  • 30
  • Is there any chance I could convince you to elaborate on what you mean by *...leak the monitor outside of the class*? – nihilon Mar 19 '15 at 14:19
0

In incrementCount() you create each time a new instance of A which pretty much compromises your whole idea of synchronization. This is your problem here. Just increment the counter, don't replace/recreate the A instance each time.

peter.petrov
  • 38,363
  • 16
  • 94
  • 159