I need two threads to write one a shared array of ints. Both threads need to write on all the elements of that array. Each thread will write either 1 or 7, and the result should be like 171717171 (or 71717171). To do that I have the first Thread1 write at position 0, then wait. Thread2 now writes at position 0 and 1, notifies Thread1, and waits. Thread1 writes at position 1 and 2, notifies Thread2 and waits, etc. With the following code I get correct output, although when run with JPF it finds a deadlock. Its become really frustrating since I can not find whats wrong with it. Any advice would be appreciated.
import java.util.logging.Level;
import java.util.logging.Logger;
public class WriterThreadManager {
private int[] array = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
private Thread thread7;
private Thread thread1;
public static void main(String[] args) {
WriterThreadManager mng = new WriterThreadManager();
mng.exec();
}
public WriterThreadManager() {
thread7 = new Thread(new WriterRunnable(this, 7));
thread1 = new Thread(new WriterRunnable(this, 1));
}
public void overwriteArray(int pos, int num) {
array[pos] = num;
printArray();
}
private void printArray() {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]);
}
System.out.println("");
}
public synchronized void stopThread() {
try {
this.wait();
} catch (InterruptedException ex) {
Logger.getLogger(WriterThreadManager.class.getName()).log(Level.SEVERE, null, ex);
}
}
public synchronized void wakeUpThread() {
notifyAll();
}
private void exec() {
thread7.start();
thread1.start();
}
public int length() {
return array.length;
}
}
public class WriterRunnable implements Runnable {
private WriterThreadManager mng;
private int numberToWrite;
private static boolean flag = true;
@Override
public void run() {
int counter = 0;
int j = 0;
//first thread to get in should write only at
//position 0 and then wait.
synchronized (mng) {
if (flag) {
flag = false;
mng.overwriteArray(0, numberToWrite);
j = 1;
waitForOtherThread();
}
}
for (int i = j; i < mng.length(); i++) {
mng.overwriteArray(i, numberToWrite);
counter++;
if (i == mng.length() - 1) {
mng.wakeUpThread();
break;
}
if (counter == 2) {
waitForOtherThread();
counter = 0;
}
}
}
private void waitForOtherThread() {
mng.wakeUpThread();
mng.stopThread();
}
public WriterRunnable(WriterThreadManager ar, int num) {
mng = ar;
numberToWrite = num;
}
}
p.s: an example of the execution:
1000000000
7000000000
7700000000
7100000000
7110000000
7170000000
7177000000
7171000000
7171100000
7171700000
7171770000
7171710000
7171711000
7171717000
7171717700
7171717100
7171717110
7171717170
7171717177
7171717171
The error snapshot from JPF is the following:
thread java.lang.Thread:{id:1,name:Thread-1,status:WAITING,priority:5,lockCount:1,suspendCount:0}
waiting on: WriterThreadManager@152
call stack:
at java.lang.Object.wait(Object.java)
at WriterThreadManager.stopThread(WriterThreadManager.java:43)
at WriterRunnable.waitForOtherThread(WriterRunnable.java:53)
at WriterRunnable.run(WriterRunnable.java:45)
thread java.lang.Thread:{id:2,name:Thread-2,status:WAITING,priority:5,lockCount:1,suspendCount:0}
waiting on: WriterThreadManager@152
call stack:
at java.lang.Object.wait(Object.java)
at WriterThreadManager.stopThread(WriterThreadManager.java:43)
at WriterRunnable.waitForOtherThread(WriterRunnable.java:53)
at WriterRunnable.run(WriterRunnable.java:45)
I believe the race is due to this method:
private void waitForOtherThread() {
mng.wakeUpThread();
mng.stopThread();
}
While the individual wakeUpThread()
and stopThread()
methods are synchronized, you have the opportunity for unexpected thread scheduling between these calls.
Consider:
thread7 - notify thread1 to wakup
thread1 - wake up
thread1 - work to completion
thread1 - notify thread7 to wakeup
thread1 - wait to be notified to wakeup
thread7 - wait to be notified to wakeup
In this case you have deadlocked because thread1 sent its notifyAll() before thread7 had a chance to wait() for it.
Running in a different context can mess with your timing and cause these types of behaviors to appear.
To avoid this I suggestion doing this:
private void waitForOtherThread() {
synchronized(mng) {
mng.wakeUpThread();
mng.stopThread();
}
}
Or better yet, use a semaphore as @KumarVivekMitra suggested. Semaphores combine both the notification system and a counter so that the order of the notify and wait don't matter.