Как работает метод join() в классе Thread

278
22 марта 2017, 15:58

По идее метода join(), если я правильно его понимаю, он должен передавать управление программой тому потоку, на объекте которого был вызван, в то время как тот поток который этот вызов произвел, должен ждать.

Однако, если мы имеем несколько объектов потока, для которых идут вызовы метода join() подряд, то второй тоже запускается.

То есть, методе main мы имеем такой код:

    try {
        thread01.join();
        thread02.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

То по логике, сразу после выполнения вызова thread01.join();, метод main должен был остановиться и ждать пока thread01 не вернет ему управление программой, но логирование показывает, что вместо этого, он выполняет еще и thread02.join(); и только этого переходит в режим ожидания.

Почему так?

Это что означает, что метод join(), срабатывает только в тех случаях, когда после него не идет еще один join(), или тут блок try, имеет какой-то особый смысл? Помогите пожалуйста разобраться.

Полный код потоков:

public class CounterOfSpace {
    private int counterOfSpaces;
    private int counterOfWords;
    private String text;
    private Thread thread01;
    private Thread thread02;
    public CounterOfSpace(String text) {
        this.text = text;
    }
        void startAll() {
            System.out.println("Start program");
            calcWords();
            calcSpaces();
            thread01.start();
            thread02.start();
            try {
                thread01.join();
                thread02.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Finish program");
        }
        private void calcSpaces() {
            thread01 = new Thread(new Runnable() {
                @Override
                public void run() {
                    if (text.length() == 0) return;
                    for (char c : text.toCharArray()) {
                        if (c == ' ') {
                            counterOfSpaces++;
                            System.out.println("counterOfSpaces "+counterOfSpaces);
                        }
                    }
                }
            });
        }
        private void calcWords() {
            thread02 = new Thread(new Runnable() {
                @Override
                public void run() {
                    if (text.length() == 0) return;
                    String[] words = text.split(" ");
                    for (String word : words) {
                        if (!word.equals(" ")) {
                            counterOfWords++;
                            System.out.println("counterOfWords "+counterOfWords);
                        }
                    }
                }
            });
        }
    }
Answer 1

По идее метода join(), если я правильно его понимаю, он должен передавать управление программой тому потоку,

Это не совсем корректно, поток должен уходить в спящее состояние, а вот кому отдать управление решает шедулер.

Однако, если мы имеем несколько объектов потока, для которых идут вызовы метода join() подряд, то второй тоже запускается.

Что никак не противоречит парадигме. В данном случае мы имеем три истории с двумя точками синхронизации:

Main      Thread1             Thread2
<init>    <init>              <init>
running   running? finished?  running? finished?
join t1   finished            running? finished?
join t2   finished            finished

Данный код гарантирует, что после завершения t1.join поток t1 будет завершен, но не гарантирует, что он не завершится до вызова t1.join, что очевидно и происходит в этом случае. К моменту вызова t1.join поток t1 может быть в любом состоянии, даже если ему приходится считать тяжелые данные продолжительное время - формально это разрешено, хоть на практике такое будет встречаться нечасто. Вкупе с

логирование показывает

это говорит о том, что, скорее всего, поток просто очень быстро заканчивает работу, а вы принимаете это за несоответствующий спецификации феномен. В таких проблемах не стоит полагаться на логирование, потому что его синхронизационные отношения по отношению к основной программе туманны, и для меня не было бы неожиданным несоблюдение порядка строчек в выводе или длительные (относительно происходящих в программе процессов) задержки при выводе. Также стоит умеренно полагаться на временные замеры подобных вещей, потому что стандартный источник времени может быть не монотонным и возвращать меньшие значения на последующие вызовы, в результате чего будет выглядеть, будто программа выполняется в обратном порядке.

При желании можно заглянуть в исходный код Thread и увидеть, что join реализован через wait и защищен isAlive-гардами от случайных пробуждений, поэтому метод join вернет не раньше, чем поток перестанет возвращать true на isAlive.

READ ALSO
Из MainActivity не вызывается getView метод адаптер

Из MainActivity не вызывается getView метод адаптер

Добрый деньЯ учусь программировать приложения под андроид и в одной из книг нашел обучающий мануал - создание приложения прогноза погоды

274
Не заполняет List&lt;Product&gt;

Не заполняет List<Product>

Здравствуйте

191
Декодировать аудио из формата g729 в формат pcm_alaw на Java

Декодировать аудио из формата g729 в формат pcm_alaw на Java

По rtp поступает массив данных в байтахИзвестно, что данные в формате g729

203
Парсинг данных через GET запрос Elasticsearch

Парсинг данных через GET запрос Elasticsearch

Не понятно как сформировать запрос к базе Elasticsearchна официальном сайте пример запроса

268