Hi I have a situation in which I have to allow only one thread to say update a variable.
There is a trigger, which might invoke multiple threads to update this variable, however the update should happen only once by the first thread whichever arrives at the critical section.
Ideally the flow should be like follows:
Thread-1; Thread-2 and Thread-3 are invoked to update a variable in critical section guarded by a lock or a mutex
Critical section using this guard allows only one thread to enter, Thread-2 and Thread-3 wait just outside.
Once this variable is updated by Thread-1; Thread-2 and Thread-3 resume with other work without causing an effect on the variable.
I have come up with the following implementation, but I am not able to make other threads wait and skip updation:
public class Main {
private static ReentrantLock lock = new ReentrantLock();
private int counter = 0;
public static void main(String[] args) {
Main m = new Main();
new Thread(m::doSomeOperation).start();
new Thread(m::doSomeOperation).start();
new Thread(m::doSomeOperation).start();
}
private void doSomeOperation() {
try {
System.out.println("Thread about to acquire lock: " + Thread.currentThread().getName());
if (lock.tryLock()) {
System.out.println("Lock held by " + Thread.currentThread().getName() + " " + lock.isHeldByCurrentThread());
counter++;
// Thread.sleep(3000);
System.out.println("Counter value: " + counter + " worked by thread " + Thread.currentThread().getName());
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("Unlocked: " + Thread.currentThread().getName());
}
}
}
}
At the end, the counter value is 3, I want counter value to be one also I want other threads to wait till the first thread updates the counter. Ideas appreciated. I would prefer a solution using locks / mutex rather than wait and notify.
Output:
Thread about to acquire lock: Thread-0
Lock held by Thread-0 true
Thread about to acquire lock: Thread-1
Counter value: 1 worked by thread Thread-0
Unlocked: Thread-0
Thread about to acquire lock: Thread-2
Lock held by Thread-2 true
Counter value: 2 worked by thread Thread-2
Unlocked: Thread-2
Process finished with exit code 0
Note
My use case, is different - the example of updating a counter is for simplicity sake. Actually I am updating a session token in the doSomeOperation method.
解决方案
Problem is occurring because Initially one thread increases the counter and release the lock and your program is running so fast that, once first thread releases the lock then another thread enters the method and it sees the lock free so it acquires the lock and increase the counter further. You can make use of countDownLatch in this case.
Here one thread which get the lock decrease the latch count by one and makes it zero, after that none of the thread will be able to process because
latch.getCount()==1 condition will fail.
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
public class Test2 {
private static ReentrantLock lock = new ReentrantLock();
private static CountDownLatch latch= new CountDownLatch(1);
private int counter = 0;
public static void main(String[] args) {
Test2 m = new Test2();
new Thread(m::doSomeOperation).start();
new Thread(m::doSomeOperation).start();
new Thread(m::doSomeOperation).start();
}
private void doSomeOperation() {
try {
System.out.println("Thread about to acquire lock: " + Thread.currentThread().getName());
if (lock.tryLock() && latch.getCount()==1) {
System.out.println("Lock held by " + Thread.currentThread().getName() + " " + lock.isHeldByCurrentThread());
counter++;
latch.countDown();
Thread.sleep(3000);
System.out.println("Counter value: " + counter + " worked by thread " + Thread.currentThread().getName());
}
System.out.println("Exiting" + Thread.currentThread().getName());
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("Unlocked: " + Thread.currentThread().getName());
}
}
}
}
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
public class Test2 {
private static ReentrantLock lock = new ReentrantLock();
private static CountDownLatch latch= new CountDownLatch(1);
private int counter = 0;
public static void main(String[] args) {
Test2 m = new Test2();
new Thread(m::doSomeOperation).start();
new Thread(m::doSomeOperation).start();
new Thread(m::doSomeOperation).start();
}
private void doSomeOperation() {
try {
System.out.println("Thread about to acquire lock: " + Thread.currentThread().getName());
if (lock.tryLock() && latch.getCount()==1) {
System.out.println("Lock held by " + Thread.currentThread().getName() + " " + lock.isHeldByCurrentThread());
counter++;
latch.countDown();
Thread.sleep(3000);
System.out.println("Counter value: " + counter + " worked by thread " + Thread.currentThread().getName());
}
System.out.println("Exiting" + Thread.currentThread().getName());
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("Unlocked: " + Thread.currentThread().getName());
}
}
}
}