Many Java programmers confused themselves like hell while writing multi-threaded Java programs e.g. where to synchronized? Which Lock to use? What Lock to use etc. I often receive request to explain about how to use Locks in Java, so I thought to write a simple Java program, which is multi-threaded and uses rather new Lock interface. Remember Lock is your tool to guard shared resource which can be anything e.g. database, File system, a Prime number Generator or a Message processor. Before using Locks in Java program, it’s also better to learn some basics. Lock is an interface from java.util.concurrent package. It was introduced in JDK 1.5 release as an alternative of synchronized keyword. If you have never written any multi-threading program, then I suggest first start with synchronized keyword because it’s easier to use them. Once you are familiar with working of multi-threading program e.g. How threads share data, how inter thread communication works, you can start with Lock facility. As I told you Lock is an interface, so we cannot use it directly, instead we need to use its implementation class. Thankfully Java comes with two implementation of java.util.concurrent.locks.Lock interface, ReentrantLock and ReentrantReadWriteLock, later provides two more inner implementation known as ReentrantReadWriteLock.ReadLock and ReentrantReadWriteLock.WriteLock. For our simple multi-threaded Java program's purpose ReentrantLock is enough.
Here is the idiom to use Locks in Java :
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
You can see that Lock is used to protect a resource, so that only one thread can access it at a time. Why we do that? to make sure our application behave properly. For example we can use Lock to protect a counter, whose sole purpose is to return a count incremented by one, when anyone calls its getCount() method. If we don't protect them by parallel access of thread, then it’s possible that two thread receives same count, which is against the program's policies. Now, coming back to semantics, we have used lock() method to acquire lock and unlock() method to release lock. Always remember to release lock in finally block, because every object has only one lock and if a thread doesn't release it then no one can get it, which may result in your program hung or threads going into deadlock. That's why I said that synchronized keyword is simpler than lock, because Java itself make sure that lock acquired by thread by entering into synchronized block or method is released as soon as it came out of the block or method. This happens even if thread came out by throwing exception, this is also we have unlock code in finally block, to make sure it run even if try block throws exception or not. In next section we will see example of our multi-threaded Java program, which uses Lock to protect shared Counter.
Java Lock and ReentrantLock Example
Here is a sample Java program, which uses both Lock and ReentrantLock to protect a shared resource. In our case it’s an object, a counter's object. Invariant of Counter class is to return a count incremented by 1 each time someone calls getCount() method. Here for testing three threads will call getCount() method simultaneously but guard provided by Lock will prevent shared counter. As an exercise you can also implement same class using synchronized keyword. Here is complete code :
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
*
* Java Program to show how to use Locks in multi-threading
* e.g. ReentrantLock, ReentrantReadWriteLock etc.
*
* @author Javin Paul
*/
public class LockDemo {
public static void main(String args[]) {
// Let's create a counter and shared it between three threads
// Since Counter needs a lock to protect its getCount() method
// we are giving it a ReentrantLock.
final Counter myCounter = new Counter(new ReentrantLock());
// Task to be executed by each thread
Runnable r = new Runnable() {
@Override
public void run() {
System.out.printf("Count at thread %s is %d %n",
Thread.currentThread().getName(), myCounter.getCount());
}
};
// Creating three threads
Thread t1 = new Thread(r, "T1");
Thread t2 = new Thread(r, "T2");
Thread t3 = new Thread(r, "T3");
//starting all threads
t1.start();
t2.start();
t3.start();
}
}
class Counter {
private Lock lock; // Lock to protect our counter
private int count; // Integer to hold count
public Counter(Lock myLock) {
this.lock = myLock;
}
public final int getCount() {
lock.lock();
try {
count++;
return count;
} finally {
lock.unlock();
}
}
}
Output:
Count at thread T1 is 1
Count at thread T2 is 2
Count at thread T3 is 3
You can even put a long loop inside Runnable's run() method to call getCount() numerous time, if you see a duplicate means there is a problem with your code, but without any duplicate means it’s working fine.
Common Mistakes made by beginners while using Locks in Java
Here are some of the common mistakes I have observed by looking at Java beginners lock related code :
1) Instead of sharing lock they provide different locks to each thread. This often happens to them unknowingly because they usually put the lock and guarded block inside Runnable, and they pass two different instances of Runnable to two different threads e.g. where SimpleLock is a Runnable, as shown below :
Thread firstThread = new Thread(new SimpleLock());
Thread secondThread = new Thread(new SimpleLock());
class SimpleLock implements Runnable {
private Lock myLock = new ReentrantLock();
public void printOutput() {
System.out.println("Hello!");
}
public void run() {
if (myLock.tryLock()) {
myLock.lock();
printOutput();
}else
System.out.println("The lock is not accessible.");
}
}
Since here myLock is instance variable, each instance of SimpleLock has their own myLock instance, which means firstThread and secondThread are using different lock and they can run protected code simultaneously.
2) Second mistake Java beginners do is forget to call unlock() method, just like above example. without calling unlock() method, Thread will not release its lock and another thread waiting for that lock will never get that. Nothing will happen in this test program, but once you write this kind of code in real application, you will see nasty issues like deadlock, starvation and data corruption. By the way Lock interface also offers several advantages over synchronized keyword, check here to learn more.
That's all about how to use Locks in multi-threaded Java program for synchronization. Let me know if you have any difficult understanding Locks in Java or anything related to multi-threading, Will be glad to help you. For further reading, you can explore Java documentation of Lock interface and it's various implementation classes
Tidak ada komentar:
Posting Komentar