A common situation in programs with multiple threads is when two
threads have access or share a single resource like a file, database
connection, an object. This scenario can provoke error situations or
data inconsistency.
To guarantee that only one thread will execute a critical section,
prevent data inconsistency and errors, synchronizations mechanisms
should be implemented.
Java provides two basic mechanisms:
- The keyword synchronized.
- The java.util.concurrent.locks.Lock interface and its implementations.
Every object in java has a built in lock, that only comes into play
when the object has synchronized some code.
If one thread has the lock of an object, no other thread can have the
lock until the first thread releases it.
A thread can acquire more than one lock.
Example:
public class Resource {
public synchronized void lock() {
System.out.println(“Begining, the thread ” + Thread.currentThread().getName() + “has the lock”);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“Ending, the thread ” + Thread.currentThread().getName() + “has the lock”);
}
}
public class Concurrent extends Thread {
Resource resource;
public Concurrent( Resource resource, String name) {
super(name);
this. resource = resource;
}
public void run(){
resource.lock();
}
}
.....
Resource resource = new Resource();
Concurrent c1 = new Concurrent(resource, “one”);
c1.start();
Concurrent c2 = new Concurrent(resource, “two”);
c2.start();
......
In the previous example two thread access concurrently a resource,
but one thread has to wait to the other to finish using the resource
since the resource is protected using the synchronized keyword, there
is no guarantee which thread will acquire the resource first.
The messages in console should appear:
Begining, the thread one has the lock
Ending, the thread one has the lock
Begining, the thread two has the lock
Ending, the thread two has the lock
or
Begining, the thread two has the lock
Ending, the thread two has the lock
Begining, the thread one has the lock
Ending, the thread one has the lock
The messages for each thread appear consecutive and are not mix since
each thread doesn't release the lock of the Resource object until it
finishes executing the lock() method.
If the synchronized keyword is removed from the declaration of the
lock() in the Resource class, the outcome of the program could be
any, an example of the output would be:
Begining, the thread two has the lock
Begining, the thread one has the lock
Ending, the thread one has the lock
Ending, the thread two has the lock
The synchronized keyword can be applied to an object's instance to
protect access of a block of code instead of a method, it has the
same behavior, the thread who access the block of code tries to
acquire the lock of the object.
Example:
public class Resource {
public void lock() {
synchronized(this) {
System.out.println(“Begining, the thread ” + Thread.currentThread().getName() + “has the lock”);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“Ending, the thread ” + Thread.currentThread().getName() + “has the lock”);
}
}
}
........
The other synchronization mechanism provided by java is the Lock
interface and its implementations, according to the java API “Lock
implementations provide more extensive locking operations than can be
obtained using synchronized methods and statements. They allow more
flexible structuring, may have quite different properties, and may
support multiple associated Condition objects”.
Lock interface provides several methods to obtain a lock from an
object with different behaviors.
The following example is the same as the one used above with the
synchronized keyword.
Example:
public class Resource {
Lock lockObject = new ReentrantLock();
public void lock() {
lockObject.lock();//Adquires the lock
System.out.println(“Begining thread ” + Thread.currentThread().getName() + “has the lock”);
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“Ending thread ” + Thread.currentThread().getName() + “has the lock”);
lockObject.unlock();//Releasing the lock
}
}
.......
In the example the ReentrantLock implementation of the Lock interface
was used, but the API provides more implementations with different
behavior each.
ReentrantLock has another constructor which accepts a boolean
parameter known as the fair parameter, and if it is false indicates
that any thread that is waiting for the lock will receive it, but if
it is true
indicates that the thread that has been waiting for the mos time for
the lock will receive it.
Lock lock = new ReentrantLock(true);//fair set to true
The lock() method tries to acquire the lock of the lock
object, if another thread has the lock of the object, the thread
waits until the thread that has the lock releases it.
When a thread finishes using a lock should invoke unlock()
method to release the lock so other threads can access the lock.
The Lock interface has the tryLock() method, and it is similar to the lock() methd,
but this method doesn't block waiting for a lock as lock() does, it
returns a boolean value indicating if the lock was acquire or not,
returns true if the lock was free and was acquired by the current
thread, and false otherwise.
This method should be used inside an if() condition, if the lock is
obtained a block of code is executed.
If (lockObject.tryLock()) {
.........
}
No comments:
Post a Comment