Обеспечить последовательность печати, но потоки ожидают друг друга после одной итерации - PullRequest
0 голосов
/ 10 ноября 2018

У меня проблемы с разрешением этой темы в ожидании другой проблемы (даже после просмотра нескольких вопросов). Вот что я пытаюсь сделать: я хочу, чтобы один поток (называемый подпотоком) печатался 10 раз по внешнему циклу с 2 итерациями; затем еще один (босс-нить), чтобы печатать 100 раз по внешнему циклу с 2 итерациями, при условии, что подпоток идет первым. Это будет выглядеть примерно так:

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

Эта последовательность печати вспомогательных нитей и боссовых нитей будет продолжаться 2 раза (внешняя петля). Моя реализация имеет непредсказуемые результаты, то есть она печатает первую итерацию подпотока 10 раз, затем останавливается там или редко печатает все операторы и проходит через внутренний цикл и внешний цикл. Я использую wait() и notify() для обеспечения связи между потоками. Я не уверен, поместил ли блок synchronized в неправильное место или просто неправильно использовал пары wait() и notify(). Вот код:

public class Main {

    public static void main(String[] args) {
        Main ic = new Main();

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

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

    // Boss Thread
    private class Th1 implements Runnable {

        @Override
        public void run() {
            System.out.println("TH1 RUNS FIRST");
            synchronized (Main.class) { // lock outside of outer loop so
                                                        // boss thread can pick up the next iteration
                for (int i = 0; i < 2; i++) { 
                    // wait, let the sub-thread run first 
                    try {
                        Main.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    // print iterations 
                    for (int j = 0; j < 100; j++) {
                        System.out.println(Thread.currentThread().getName() + " iter = " + (j + 1));
                    }
                    System.out.println("end of boss outer----------------------" + (i + 1));
                    // wake up sub-thread and let it knows inner-iteration finished 
                    Main.class.notify();
                }
            }
        }
    }

    // Sub Thread
    private class Th2 implements Runnable {

        @Override
        public void run() { 
            for (int i = 0; i < 2; i++) {
                synchronized (Main.class) { // lock up Th2
                    // print iterations 
                    for (int j = 0; j < 10; j++) { 
                        System.out.println(Thread.currentThread().getName() + " iter = " + (j + 1)); 
                    }

                    // wake up other boss thread and let it know inner-iteration finished
                    Main.class.notify();

                    // wait for other thread to run
                    try {
                        Main.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("end of Sub outer---------------------- " + (i + 1));
                }
            }
        }
    }
}

Требуется дополнительная помощь: кто-нибудь скажет мне, является ли synchronized хорошим способом блокировки общего ресурса или есть другие лучшие альтернативы? Скажи ReentrantLock? Кроме того, являются ли notify() и wait() хорошим средством для реализации межпотоковой связи или существуют более эффективные способы сделать это менее подверженным ошибкам и более эффективным?

1 Ответ

0 голосов
/ 10 ноября 2018

После некоторых мыслей и усилий я придумал «реализацию», которая «соответствует» моим ожиданиям, как описано в посте с вопросом. Я внес некоторые изменения в пары wait и notify и добавил флаг isTh2RunFirst, чтобы облегчить взаимодействие двух потоков. Вот код:

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();
            } 
        } 
    }

} 

Я также опубликовал Пост обзора кода , если кто-то захочет подробно рассмотреть «правильную» реализацию (с точки зрения отраслевой практики и эффективности) потоковой связи для этого конкретного случая или для других в целом.

...