First, what I've read through:
- In Java critical sections, what should I synchronize on?
- synchronized(this) blocks whole object? [duplicate]
- Does thread.yield() lose the lock on object if called inside a synchronized method?
- What is the difference between a synchronized method and synchronized block in Java?
- Java synchronized difference threading [duplicate]
- What is the difference between a synchronized function and synchronized block?
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?