2
\$\begingroup\$

This is a follow-up answer "implementation" to a question I posted on SO. For the sake of convenience, I will repeat my intent: I want one thread (called sub-thread) to print 10 times under outer-loop with 2 iterations; then another (boss-thread) to print 100 times under outer-loop with 2 iterations provided that sub-thread goes first. It will look something like this:

Sub Thread- iter = 1
Sub Thread- iter = 2
...
Sub Thread- iter = 10
Boss Thread- iter = 1
Boss Thread- iter = 2
...
Boss Thread- iter = 100

This sub-thread and boss-thread printing sequence will continue for 2 times (outer-loop).

Here's my code:

public class InterThCom {
    // flag default to false for checking if sub-thread 
    // gets the lock first
    private boolean isTh2RunFirst = false; 

    public static void main(String[] args) {
        InterThCom itc = new InterThCom(); 

        Thread t1 = new Thread(itc.new Th1(), "Boss-thread-"); 
        Thread t2 = new Thread(itc.new Th2(), "Sub-thread-");

        t1.start();
        t2.start();
    }

    private class Th1 implements Runnable {

        @Override
        public void run() { 
            for (int i = 0; i < 2; i++) { 
                synchronized (InterThCom.class) { // lock up inner-loop

                    // boss-thread gets the lock first 
                    // wait for sub-thread and let it run;
                    // otherwise, skip this check
                    if (isTh2RunFirst == false) {
                        // wait for sub-thread, if boss-thread gets the lock first 
                        try {
                            InterThCom.class.wait();
                        } catch (InterruptedException e1) { 
                            e1.printStackTrace();
                        }
                    } 

                    // print iteration 100 times 
                    for (int j = 0; j < 100; j++) {
                        System.out.println(Thread.currentThread().getName() + " iter-" + (j + 1));
                    }
                    // done printing 100 times

                    // sub-thread should run already at this point 
                    isTh2RunFirst = true;

                    // This print helps split boss-th and sub-th prints
                    System.out.println(Thread.currentThread().getName() + " outer-loop iter:" + (i + 1));

                    // wake up sub-thread 
                    InterThCom.class.notify();

                    // wait for sub-thread 
                    try {
                        InterThCom.class.wait();
                    } catch (InterruptedException e) { 
                        e.printStackTrace();
                    } 
                } 
            }
        } 
    }

    private class Th2 implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < 2; i++) { 
                synchronized (InterThCom.class) {
                    // print iteration 10 times 
                    for (int j = 0; j < 10; j++) {
                        System.out.println(Thread.currentThread().getName() + " iter-" + (j + 1));
                    }
                    // done printing 10 times

                    // sub-thread already prints j iteration
                    isTh2RunFirst = true; 

                    // This print helps split boss-th and sub-th prints
                    System.out.println(Thread.currentThread().getName() + " outer-loop iter:" + (i + 1));

                    // wake up boss-thread 
                    InterThCom.class.notify();

                    // wait for boss-thread 
                    try {
                        InterThCom.class.wait();
                    } catch (InterruptedException e) { 
                        e.printStackTrace();
                    } 
                } 
            }
            synchronized (InterThCom.class) {
                // boss-thread is waiting at the last iteration, so wake it up
                InterThCom.class.notify();
            } 
        } 
    }

} 

Things I would like help with:

  • Did I use "synchronized" block in an efficient way that aligns with conventional approach?

  • Are there other locking approach that will make my code less cluttered and cleaner?

    • My initial thought was using a separate class called PrintStmt to wrap all the statements inside the run and then called it in the run method then lock the invocation. That way, run only has the invocation and the lock.
  • Also, my wait and notify pairs are all over, is there a better way to "organize" them in a way that looks better? E.g. one of my notify call is outside of the double for-loop in the sub-thread Th2 class. This is an edge case but I am having trouble to integrate that inside the double-loops.

  • I am new to multi-threading. So, I am grateful for any other addresses and corrections to my implementation of the inter-communication for two threads with some extra requirement. Or other suggestions on implementing thread-communication will be appreciated.

\$\endgroup\$
0
\$\begingroup\$

One completely different approach would be to use RxJava to abstract away any manual thread handling. RxJava offers the class Observable. For our purpose here you can think of it as a stream of data that begins to emit values once you call subscribe() on it. Observable#onNext gets called every time the next value gets emitted from the stream. This way we can create two Observables from a stream of integers (100 for boss, 10 for sub) and arrange them in the desired way:

public static void main(String[] args) throws InterruptedException {
    Observable<Integer> bossObservable = Observable.range(1, 100) // emit integers from 1 to 100
            .subscribeOn(Schedulers.io()) // choose thread pool to run on
            .doOnNext(i -> System.out.println(Thread.currentThread() + " Boss " + i)); // gets executed for each
                                                                                        // value in the stream
    Observable<Integer> subObservable = Observable.range(1, 10) // emit integers from 1 to 10
            .subscribeOn(Schedulers.io()) // choose thread pool to run on
            .doOnNext(i -> System.out.println(Thread.currentThread() + " Sub " + i)); // gets executed for each
                                                                                        // value in the stream

    subObservable.concatWith(bossObservable) // append bossObservable to subObservable
                                        // -> bossObservable runs after subObservable has finished
            .repeat(2) // we repeat this two times, like the outer for loop in your solution
            .subscribe(); // this starts the execution

    while (true) {
        Thread.sleep(100); // keep main thread alive
    }
}

The example code uses RxJava 2.2.3. As you can probably tell, the code is quite concise but also hard to grasp if you're not familiar with Rx, because the framework takes care of the thread handling.

If you want to learn more about reactive programming you can read the excellent introduction by Andre Staltz: https://gist.github.com/staltz/868e7e9bc2a7b8c1f754

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.