Sid Zhang Sid Zhang - 1 month ago 12
Java Question

Stuck when using currentThread as the lock object

I tried to simulate this scene: thread in an array can execute one after another as the order in the array. My idea is to let

thread[i]
always notify
thread[(i+1)%threads.length]
.

However, the code will be stuck somewhere:

public class OrderedOperationThreadOneLock {

//<- Assign tasks to workers
private static final String[] message1 = {"A", "D", "G"};
private static final String[] message2 = {"B", "E", "H"};
private static final String[] message3 = {"C", "F", "I"};

private static final Task task1 = new Task(message1);
private static final Task task2 = new Task(message2);
private static final Task task3 = new Task(message3);

private static Thread[] workers = {
new Thread(task1),
new Thread(task2),
new Thread(task3)
}; //->

private static void printThreadsStatus() {
for (int i = 0; i < workers.length; ++i) {
System.out.println("Thread: " + workers[i].getName() +
"\tPriority: " + workers[i].getPriority() +
"\tStatus: " + workers[i].getState()
);
}
System.out.println("----------------------------------------------");
}


// Print a message then wait for other thread notify.
// Until all the messages are outputted.
private static class Task implements Runnable {
private final String[] messages;
Task(String[] messages) {
this.messages = Arrays.copyOf(messages, messages.length);
}
@Override
public void run() {
for (int i = 0; i < messages.length; ++i) {
System.out.println(messages[i]);
Thread curThread = Thread.currentThread();

// Stuck here
synchronized (curThread) {
try {
curThread.wait();
printThreadsStatus();
} catch (InterruptedException e) {
System.err.println("Oh, Why?");
}
}
wakeUpNext();
}
}

// `thread[i]` always notify `thread[(i+1)%threads.length]`
private void wakeUpNext() {
Thread curThread = Thread.currentThread();
int i = 0;
boolean isNotified = false;
while (i < workers.length && !isNotified) {
if (curThread.getId() == workers[i].getId()) {
int toNotify = (i+1) % workers.length;
System.out.println("Worker " + i + " is waking up " + toNotify);
synchronized (workers[toNotify]) {
workers[toNotify].notify();
}
isNotified = true;
}
i++;
}
}
}

public static void main(String... args) throws InterruptedException {
for (int i = 0; i < workers.length; ++i) {
workers[i].start();
Thread.sleep(10); // Ensure start in 1,2,3 sequence
}

System.out.println("================================");
printThreadsStatus();
synchronized (workers[0]) {
workers[0].notify();
printThreadsStatus();
}

for (int i = 0; i < workers.length; ++i) {
workers[i].join(5000);
printThreadsStatus(); // All the threads are in WAITING status...
}

printThreadsStatus();
}
}


However, if I use the
Task
as the monitor lock. Then the program won't be stuck:

public class OrderedOperationThreadSingleLock {
//<- Assign tasks to workers
private static final String[] message1 = {"A", "D", "G"};
private static final String[] message2 = {"B", "E", "H"};
private static final String[] message3 = {"C", "F", "I"};

private static final Task[] tasks = {
new Task(message1),
new Task(message2),
new Task(message3)
};

private static Thread[] workers = {
new Thread(tasks[0]),
new Thread(tasks[1]),
new Thread(tasks[2])
};//->

private static void printThreadsStatus() {
for (int i = 0; i < workers.length; ++i) {
System.out.println("Thread: " + workers[i].getName() +
"\tPriority: " + workers[i].getPriority() +
"\tStatus: " + workers[i].getState()
);
}
System.out.println("----------------------------------------------");
}


private static class Task implements Runnable {
private final String[] messages;
Task(String[] messages) {
this.messages = Arrays.copyOf(messages, messages.length);
}
@Override
public void run() {
for (int i = 0; i < messages.length; ++i) {
System.out.println(messages[i]);
synchronized (this) {
try {
wait();
printThreadsStatus();
} catch (InterruptedException e) {
System.err.println("Oh, Why?");
}
}
wakeUpNext();
}
}

// Use task as the lock instead of thread object
private void wakeUpNext() {
int i = 0;
boolean isNotified = false;
Thread t = Thread.currentThread();
while (i < workers.length && !isNotified) {
if (t.getId() == workers[i].getId()) {
int toNotify = (i+1) % workers.length;
System.out.println("Worker " + i + " is waking up " + toNotify);
synchronized (tasks[toNotify]) {
System.out.println("Prepare Waking up " + toNotify);
tasks[toNotify].notify();
}
isNotified = true;
}
i++;
}
}
}

public static void main(String... args) throws InterruptedException {
for (int i = 0; i < workers.length; ++i) {
workers[i].start();
Thread.sleep(10);
}

System.out.println("================================");
printThreadsStatus();
synchronized (tasks[0]) {
tasks[0].notify();
printThreadsStatus();
}

for (int i = 0; i < workers.length; ++i) {
workers[i].join(5000);
printThreadsStatus();
}

printThreadsStatus();
}
}


My question is why thread object would block the executing and task won't?

Answer

Funny thing is that problem is in workers[i].join(5000);. Method join is synchronized on thread too and invoke wait, so when you notify thread in wakeUpNext you wake up wrong one.

It happens next way.

  • Main thread notify thread 1
  • Thread 1 wake up
  • Thread 1 notify thread 2
  • Thread 1 sleep waiting thread 1 to by notified.
  • Main thread invoke join on thread 1 which lead to main thread sleep to.
  • At some point thread 3 notify thread 1, but there is two thread waiting this notification (Thread 1 itself and main thread) for some reason main thread get notified.
  • Main thread get back to sleep because join method ready to spurious wake ups
Comments