Since now we have some idea on locks and how threads use them for execution, Let’s look into some classes which further helps us in our use cases.
ReentrantLock class:
In addition to lock functionality, this class provide few more utility methods which help us in our user cases.
tryLock() method: This method returns boolean value “true” or “false” depending on whether lock is available or not.
Refer the code below as example. When below code is executed, intermittently we get “Lock not available…” statements in console, indicating lock cannot be acquired by the thread –
public class ReentrantLockExample {
public static void main(String[] args) throws InterruptedException {
SharedReentrantClass sharedObject = new SharedReentrantClass();
Thread readThread = new Thread(() -> {
while (true) {
try {
Thread.sleep(50);
sharedObject.getLockObj().lock();
System.out.println(System.currentTimeMillis() + "::" + sharedObject.getData() + "::");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
sharedObject.getLockObj().unlock();
}
}
});
Thread updateThread = new Thread(() -> {
while (true) {
try {
Thread.sleep(100);
if (sharedObject.getLockObj().tryLock()) {
sharedObject.setData(sharedObject.getData() + 1);
sharedObject.getLockObj().unlock();
} else {
System.out.println("Lock not available...");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
readThread.start();
updateThread.start();
readThread.join();
updateThread.join();
System.out.println("Finished....");
}
}
class SharedReentrantClass {
int data = 1;
private Lock lockObj = new ReentrantLock();
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
public Lock getLockObj() {
return lockObj;
}
}
ReentrantReadWriteLock class:
This class provides two objects for locking – Read Lock and Write lock.
- Read Lock:
- Can be acquired by multiple threads at a time.
- Cannot be acquired if Write Lock is already acquired.
- Write Lock:
- Can be acquired by only one thread at a time.
- Cannot be acquired if at least one thread has already acquired Read Lock.
Refer below code and on executing, we can observe console output supporting above points.
public class ReentrantRWLockExample {
public static void main(String[] args) throws InterruptedException {
SharedReentrantRWClass sharedObject = new SharedReentrantRWClass();
Thread readThread1 = new Thread(() -> {
while (true) {
ReentrantReadWriteLock.ReadLock readLock = sharedObject.getRwLock().readLock();
if (readLock.tryLock()) {
System.out.println(Thread.currentThread().getName() + "::" + sharedObject.getData());
readLock.unlock();
} else {
System.out.println("Did not get Lock since write in progress...");
}
}
});
Thread updateThread = new Thread(() -> {
while (true) {
try {
if (sharedObject.getRwLock().writeLock().tryLock()) {
Thread.sleep(100);
sharedObject.setData(sharedObject.getData() + 1);
sharedObject.getRwLock().writeLock().unlock();
} else {
System.out.println("Did not get Lock since read in progress...");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
readThread1.start();
updateThread.start();
readThread1.join();
updateThread.join();
System.out.println("Finished....");
}
}
class SharedReentrantRWClass {
int data = 1;
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public ReentrantReadWriteLock getRwLock() {
return rwLock;
}
public int getData() {
return data;
}
public void setData(int data) {
this.data = data;
}
}
Semaphore class:
Semaphore class provides a provision to only allow a fixed number (can be more than one) of thread to acquire resources. It works on the concept permits where only a certain number of permits are available to be taken up by threads.
Refer the below code example where we can see producer thread printing out number of available permits.
public class SemaphoreExample {
public static void main(String[] args) throws InterruptedException {
SemaphoreSharedClass sharedClass = new SemaphoreSharedClass();
Thread producer = new Thread(() -> {
try {
while (true) {
System.out.println("Available permits :: " + sharedClass.getSemaphore().availablePermits());
sharedClass.getSemaphore().acquire();
Thread.sleep(1000);
sharedClass.producer();
sharedClass.getSemaphore().release();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
Thread consumer1 = new Thread(() -> {
try {
while (true) {
sharedClass.getSemaphore().acquire();
Thread.sleep(500);
sharedClass.consumer(1);
sharedClass.getSemaphore().release();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
Thread consumer2 = new Thread(() -> {
try {
while (true) {
sharedClass.getSemaphore().acquire();
Thread.sleep(500);
sharedClass.consumer(2);
sharedClass.getSemaphore().release();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
producer.start();
consumer1.start();
consumer2.start();
producer.join();
consumer1.join();
consumer2.join();
System.out.println("....");
}
}
class SemaphoreSharedClass {
Semaphore semaphore = new Semaphore(2);
public Semaphore getSemaphore() {
return semaphore;
}
public void producer() {
System.out.println("Produced data");
}
public void consumer(int number) {
System.out.println("Consumed data :: " + number);
}
}
Methods wait(), notify() and notifyAll():
- Method wait(), put the thread in wait state due which thread will not perform further execution until notified.
- Method notify(), will notify only one of the waiting threads to start contesting for the resource lock.
- Method notifyAll(), will notify all threads which are in waiting state to start contesting for the resource lock.
Refer the below code.
public class ThreadComm {
public static void main(String[] args) throws InterruptedException {
ThreadCommSharedClass sharedClass = new ThreadCommSharedClass();
Thread thread1 = new Thread(() -> {
sharedClass.methodA(1);
});
Thread thread3 = new Thread(() -> {
sharedClass.methodA(3);
});
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
sharedClass.methodB();
});
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();
System.out.println("Finished.....");
}
}
class ThreadCommSharedClass {
public synchronized void methodA(int number) {
try {
System.out.println("Method A started " + number);
wait();
System.out.println("Notification received....");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public synchronized void methodB() {
System.out.println("Processing done....");
notifyAll(); // replace with notify()
}
}
Try replacing notifyAll() with notify() and you will observe that program will not get terminated because we have two threads in waiting state but notify() method only tell one of the threads to start contesting, Leaving other thread in wait state.