Here's my attempt at trying to understand synchronized()
blocks using an Object lock, using wait()
and notify()
:
import java.util.Scanner;
public class SyncTest {
private Scanner scanner;
private Object lock;
private volatile String string;
public SyncTest() {
scanner = new Scanner(System.in);
lock = new Object();
string = "";
}
public void start() {
//This thread will wait until the other thread notifies it.
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("String changed.");
}
}
});
t1.start();
//This thread will notify Thread 1 if a condition is met.
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
if (!string.isEmpty()) {
synchronized (lock) {
lock.notify();
}
string = "";
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t2.start();
//This is just a dummy thread to modify the string data.
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
string = scanner.nextLine();
}
}
});
t3.start();
}
public static void main(String[] args) {
new SyncTest().start();
}
}
Couple questions:
Is there any better way of achieving such functionality?
Is this how listeners work? How are they so efficient at listening to events? I mean, if I don't add the 200ms delay in
t2
execution, the thread will consume a lot of processing power, so, how can I create a listener behavior?
1 Answer 1
It does look sound to me with one exception that you need to be aware of, as far as I can see:
if (!string.isEmpty()) {
synchronized (lock) {
lock.notify();
}
string = "";
}
The moment you set string
to a new value, the first thread might not have visited the variable at this point. You can see that when outputting string
in both threads. This is only important if thread #1 is going to do processing on the value.
Additionally, because of the Thread.sleep(200);
you might miss input lines because string
value might have changed an unidentified number of times during this period.
I mean, if I don't add the 200ms delay in t2 execution, the thread will consume a lot of processing power, so, how can I create a listener behavior?
The same way you did between #2 and #1, notify
#2 from #3.
To fix these issues, you want to use something like a BlockingQueue
. Thread #3 pushes the line to the Queue
, thread #2 dequeues the next value and notifies #1. Or, #2 pushes it to a second Queue
for #1.
java.util.concurrent
. If you're doing locking yourself, you're likely reinventing some threaded wheel. \$\endgroup\$java.util.concurrent
doesn't have aMonitor
class it does have, for instance,ReentrantLock
, see here for a (seemingly good) explanation. \$\endgroup\$