Locking in Java multi-threading / concurrency
environment could actually be implemented as 2 different level:
(1)
Object-level (instance-level)(2) Class-level
The passage of How important and how to implement tread-safe in Java concurrency / multithreading environment? has revealed different ways of
implementing object-level (or instance-level) locking.
The 3 ways introduced there could guarantee
thread-safety for most cases. However, there is still one circumstance which
would let it expose to race condition dilemma (not thread-safe).
Let’s revise the main() method of the example code there a little
bit and take a look:
public static void main(String[] args){
Parent counterA = new Parent();
Parent counterB = new Parent();
Child a = counterA.new Child("A", counterA, 5);
Child b = counterA.new Child("B", counterA, 10);
Child c = counterB.new Child("C", counterB, 5);
Child d = counterB.new Child("D", counterB, 10);
a.start();
b.start();
c.start();
d.start();
}
|
The revised full version of the example looks
like:
public class Parent {
private static int counter = 0;
public int getCount(long millis){
try {
// Pretend heavy-loading job here
Thread.sleep(millis);
} catch (InterruptedException
e) {
}
synchronized(this){
return ++counter;
}
}
class Child extends Thread{
private final String name;
private final Parent counter;
private final long millis;
public Child(final String name, final Parent counter, final long millis){
this.name = name;
this.counter = counter;
this.millis = millis;
}
public void run(){
for (int i = 0; i < 50; i++){
synchronized (Parent.this){
int oCounter = counter.getCount(millis);
System.out.println(name + ": " + oCounter);
}
}
}
}
public static void main(String[] args){
Parent counterA = new Parent();
Parent counterB = new Parent();
Child a = counterA.new Child("A", counterA, 5);
Child b = counterA.new Child("B", counterA, 10);
Child c = counterB.new Child("C", counterB, 5);
Child d = counterB.new Child("D", counterB, 10);
a.start();
b.start();
c.start();
d.start();
}
}
|
After running several times, you may
discover that the final largest result is varying between 17x to 200, instead
of 200 constantly.

The reason is simple. Since previously
mentioned locking approaches are only synchronizing with object-level (or
instance-level) lock on the Parent class (i.e. callee class), this would result
into race condition again as the callee class has been instantiated for more
than once (i.e. counterA, counterB) as shown in the example above.
For resolving the potential race condition
if we forsee the callee class is possible to be instantiated for more than
once, we can apply class-level synchronization.
In usual, synchronized(this)
which we see usually is object-level (i.e. instance-level) locking. While synchronized(getClass()) is class-level locking.
Class-level synchronization
Class-level synchronization could usually
implemented as block-synchronization style on the method of either: (1) the
caller class; or (2) the callee class.
Approach 1 -
Class-level block synchronization on method of callee class (i.e. Parent class)
Adding synchronized(this.getClass())
to the area of codes which involves operations on static variable. Such as:
public int getCount(long millis){
try {
// Pretend heavy-loading job here
Thread.sleep(millis);
} catch (InterruptedException
e) { }
synchronized(this.getClass()){
return ++counter;
}
}
|
Approach 2 -
Class-level block synchronization on method of caller class (i.e. Child class)
Adding synchronized(Parent.class) to the area of codes which involves operations on static variable. Such as:
public void run(){
for (int i = 0; i < 50; i++){
synchronized (Parent.class){
int oCounter = counter.getCount(millis);
System.out.println(name + ": " + oCounter);
}
}
}
|
Approach 3 -
Class-level method synchronization on method of callee class
If it is preferred to implemented
class-level synchronization as method-synchronization, it is still possible, but
you should make the method being called as "static" first. (i.e. public static synchronized int getCount(){ .. }
)Such as:
public static synchronized int getCount(long millis){
try {
// Pretend heavy-loading job here
} catch (InterruptedException
e) { }
// synchronized(this.getClass()){
return ++counter;
// }
}
|
/**
*
* @author Gary Wong
*
*/
public class Parent {
private static int counter = 0;
public int getCount(long millis){
try {
// Pretend heavy-loading job here
Thread.sleep(millis);
} catch (InterruptedException
e) { }
// This is class-level lock to guarantee thread-safe in
different circumstances
synchronized(this.getClass()){
return ++counter;
}
}
class Child extends Thread{
private final String name;
private final Parent counter;
private final long millis;
public Child(final String name, final Parent counter, final long millis){
this.name = name;
this.counter = counter;
this.millis = millis;
}
public void run(){
for (int i = 0; i < 50; i++){
// This is
class-level lock to guarantee thread-safe in different circumstances
//synchronized
(Parent.class){
int oCounter = counter.getCount(millis);
System.out.println(name + ": " + oCounter);
//}
}
}
}
public static void main(String[] args){
Parent counterA = new Parent();
Parent counterB = new Parent();
// long start = System.nanoTime();
// long start = System.currentTimeMillis();
Child a = counterA.new Child("A", counterA, 5);
Child b = counterA.new Child("B", counterA, 10);
Child c = counterB.new Child("C", counterB, 5);
Child d = counterB.new Child("D", counterB, 10);
a.start();
b.start();
c.start();
d.start();
try {
a.join();
b.join();
c.join();
c.join();
} catch
(InterruptedException e) {
e.printStackTrace();
}
*/
// long end = System.nanoTime();
// long end = System.currentTimeMillis();
// System.out.println("Completion duration: "
+ (end - start));
}
}
|
Conclusion
While considering both thread-safety and
performance, Approach 1 (i.e. Class-level block synchronization on method of
callee class) is obviously doing advantage over the other approaches. It is
because this approach is only synchronizing the minimum area of codes as lock
has its cost of time.
Hope you enjoy this article.
Reference:
http://stackoverflow.com/questions/23261120/difference-between-class-locking-and-object-locking-in-java
http://www.programcreek.com/2014/02/how-to-make-a-method-thread-safe-in-java/
http://tutorials.jenkov.com/java-concurrency/index.html
http://tutorials.jenkov.com/java-concurrency/race-conditions-and-critical-sections.html#

沒有留言:
張貼留言