问题
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.
回答1:
Looks like what you trying to build is classic example of Producer Consumer problem. You will find thousands of solution online for this problem. some of them listed below.
- https://www.geeksforgeeks.org/producer-consumer-solution-using-threads-java/
- http://www.java2s.com/Tutorials/Java/Java_Thread/0030__Java_Thread_Producer_Consumer.htm
- https://www.programming9.com/programs/java/313-producer-consumer-problem-in-java
回答2:
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());
}
}
}
}
回答3:
If you know the exact number of threads you starts each round: You can do it like this:
private int threadCounter;
private int threadCount = 3;
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());
if (threadCounter++ % threadCount == 0) {
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());
}
}
}
It will allow to increment counter only to the the first thread in each round.
回答4:
You could use AtomicInteger
here and forgo with worrying about formal locks:
public class Worker {
private static AtomicInteger counter = new AtomicInteger(0);
private void doSomeOperation() {
counter.incrementAndGet();
System.out.println("Counter value: " + counter + " worked by thread " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
Worker w = new Worker();
new Thread(w::doSomeOperation).start();
new Thread(w::doSomeOperation).start();
new Thread(w::doSomeOperation).start();
}
}
来源:https://stackoverflow.com/questions/58164463/java-allow-one-thread-to-update-a-value-others-to-wait-and-skip-critical-secti