If you always invoke the methods using the first technique, then the two methods are really atomic: only one thread at a time will (run method1 and run method2), as a block.
In the second case, any given instance of B
will invoke the two methods atomically -- but if two threads each have a separate instance of B
, then the method1()
and method2()
calls can interleave. Imagine:
- Thread1 owns
B b1 = new B(a)
- Thread2 owns
B b2 = new B(a)
(for the same instance a
)
- Thread1 invokes
b1.newB()
, acquires a lock on b1.lock
- Thread2 invokes
b2.newB()
, acquires a lock on b2.lock
(which is not contended, since it's a separate object than b1.lock
)
- Thread1 starts
a.method1()
- Thread2 tries to start
a.method1()
but is blocked because it's synchronized
- Thread1 finishes
a.method1()
- Thread2 starts
a.method1()
- Thread1 starts
a.method2()
as Thread2 is still running method1()
- Atomicity is not achieved!
You could address this by making B.lock
a static final, but this may be more locking than you need -- now all calls to {A.method1(); A.method2();}
are serialized, rather than being serialized per instance of A
.
Even if you use first synchronization the method, you're still at the mercy of everyone else playing nice. If someone else invokes a.method1()
directly (not via B.foo
), you can still get non-atomic interleaving.
- Thread1 owns
B b1 = new B(a)
- Thread2 owns
a
directly (same instance of a
)
- Thread1 invokes
b1.newB()
, acquires a lock on b1.lock
- Thread1 starts
a.method1()
- Thread2 tries to start
a.method1()
but is blocked because it's synchronized
- Thread1 finishes
a.method1()
- Thread2 starts
a.method1()
- Thread1 starts
a.method2()
as Thread2 is still running method1()
- Atomicity is not achieved!
There's not much you can do about that, other than restricting the visibility of method1()
and method2()
. For instance, you could make them package-private or protected, and assume/hope that classes that have access to the methods will know better than to abuse their powers. If you go that route, then you don't even need to synchronize method1()
or method2()
-- you can document and assume (and even assert, if you want) that they'll be invoked while this
is locked.