I’m trying to remember my old CS days.
Been trying to properly implement, with the lowest possible primitives, a pair of synchronized threads. Of course I should use better concurrency tools on production code (stuff from java.util.concurrency, probably). But hey, I’m doing this for the challenge. Here is my code (this is my first question, so if this is much too long, please forgive me):
public class Test {
public volatile Object locker1 = new Object();
public volatile Object locker2 = new Object();
public volatile Object locker3 = new Object();
public class MyRunnable2 implements Runnable {
public void run() {
System.out.println( "MyRunnable2 started" );
synchronized( locker3 ) {
try {
System.out.println( "r2: waiting for locker3" );
locker3.wait();
System.out.println( "r2: got locker3" );
} catch ( java.lang.InterruptedException e ) {
System.out.println( "e: " + e );
}
}
for ( int c = 0; c < 50; ++c ) {
synchronized( locker2 ) {
try {
System.out.println( "r2: waiting for locker2" );
locker2.wait();
System.out.println( "r2: got locker2" );
} catch ( java.lang.InterruptedException e ) {
System.out.println( "e: " + e );
}
}
System.out.println( "r2: " + ( c ) );
try {
Thread.sleep(1);
} catch ( Exception e ) {
}
synchronized( locker1 ) {
System.out.println( "r2: signaling locker1" );
locker1.notify();
System.out.println( "r2: locker1 signaled" );
}
}
}
}
public class MyRunnable1 implements Runnable {
public void run() {
System.out.println( "MyRunnable1 started" );
synchronized( locker3 ) {
try {
System.out.println( "r1: waiting for locker3" );
locker3.wait();
System.out.println( "r1: got locker3" );
} catch ( java.lang.InterruptedException e ) {
System.out.println( "e: " + e );
}
}
for ( int c = 0; c < 50; ++c ) {
synchronized( locker1 ) {
try {
System.out.println( "r1: waiting for locker1" );
locker1.wait();
System.out.println( "r1: got locker1" );
} catch ( java.lang.InterruptedException e ) {
System.out.println( "e: " + e );
}
}
System.out.println( "r1: " + ( c ) );
try {
Thread.sleep(1);
} catch ( Exception e ) {
}
synchronized( locker2 ) {
System.out.println( "r1: signaling locker2" );
locker2.notify();
System.out.println( "r1: locker2 signaled" );
}
}
}
}
public static void main(String[] args) {
Test t = new Test();
t.test();
}
public void test() {
MyRunnable1 r1 = new MyRunnable1();
MyRunnable2 r2 = new MyRunnable2();
Thread t1 = new Thread( r1 );
Thread t2 = new Thread( r2 );
t1.start();
t2.start();
try {
Thread.sleep(1000);
} catch ( Exception e ) {
}
synchronized( locker3 ) {
System.out.println( "main: signaling locker3" );
locker3.notifyAll();
System.out.println( "main: locker3 signaled" );
}
try {
Thread.sleep(1000);
} catch ( Exception e ) {
}
synchronized( locker1 ) {
System.out.println( "main: signaling locker1" );
locker1.notify();
System.out.println( "main: locker1 signaled" );
}
try {
t1.join();
t2.join();
} catch ( java.lang.InterruptedException e ) {
System.out.println( "e: " + e );
}
}
}
My question is: How can I avoid the race condition at Test.test()? Most of the time, this works – but I’m not satisfied with the sleep call.
Also, please, I ask you people to evaluate my style. I’m always open to self-improvement.
EDIT: Just to make it clearer. I want MyRunnable1 to always run first. Print a number and then wait for MyRunnable2 to print the same number. Then it would print a second number and then wait for MyRunnable2 again. And so on.
I guess I can’t comfortably use java.util.concurrency until I know what happens under the hood.
@Chris Thompson is right – you can alternate on a single signal object. But you will never guarantee which thread goes first and you have to take some care to make sure that you don’t have your last thread waiting for your second-to-last-thread to notify after your second-to-last thread has already finished and exited.
I modified your code to work – but with no guarantee on who goes first, then I also added an alternate “MyRunnableOrdered” that controls the order the two threads execute. In either case if the threads do not have the same number of loops to complete, or if either one exits because of an error, then you will risk starvation. Paying attention to and using the interrupted exception will help in the latter case.
Note that the MyRunnableOrdered idea will not extend beyond two threads, because we do not control who wakes up when notify is called. What you would want in that case is an ordered list of threads to work through. At that point concurrency may not be the best solution!
There is also probably a better implementation of MyRunnableOrdered using AtomicBoolean and no locking if you decide to use the concurrency library.
Also note that we do not need to use “volatile” because all the variable access is protected by synchronized blocks.