
Метод join()
Метод join() блокирует работу потока, в котором он вызван, до тех пор, пока не будет закончено выполнение вызывающего метод потока.
Рассмотрим пример:
public class Example {
public static void main(String[] args)
throws InterruptedException{
Thread thread1 = new Thread (new ExampleRunnable(" B", 80));
Thread thread2 = new Thread (new ExampleRunnable("A", 130));
thread1.start();
thread1.join();
thread2.start();
}}
В примере сначала будут выведены 10 букв "B", а только потом 10 букв "А". Давайте разберемся почему так? В самом начале работы метода main() он запустит новый поток thread1, а после вызова метода thread1.join() привяжется к нему и будет ожидать его завершения. Как только поток thread1 завершит свою работу метод main() начнет выполняться дальше, а именно, запустит поток thread2 на выполнение.
Такой механизм часто нужен тогда, когда в программе параллельно выполняется несколько потоков, решающие отдельные подзадачи алгоритма и методу main() нужно дождаться результатов их выполнения, чтобы выполнить окончательные расчеты и завершить алгоритм.
Рассмотрим еще один пример. Запустим два потока. Один очень быстрый печатает букву "В", второй – медленный печатает букву "А".
public class Example {
public static void main(String[] args)
throws InterruptedException{
Thread thread1 = new Thread (new ExampleRunnable(" B", 80));
Thread thread2 = new Thread (new ExampleRunnable("A", 400));
thread1.start();
thread2.start();
thread1.join();
System.out.println("Start wait!");
thread2.join();
System.out.println("End!!!");
}}
B
B
B
B
A
B
B
B
B
B
A
B
Start wait!
A
A
A
A
A
A
A
A
End!!!
Поток метода main() привязывается к потоку thread1 и ждет его завершения. После чего, печатает Start wait! и привязывается к медленно выполняющемуся потоку thread2, ожидает его завершения и печатает End!!!
Как вы можете видеть метод join()вызывается для экземпляра класса Thread. А можно ли остановить другие потоки и дожидаться завершения потока метода main(), ведь у нас нет экземпляра данного потока (он стартуется автоматически JVM)? Оказывается, чтобы получить ссылку на тот поток, в котором мы находимся, следует вызвать статический метод currentThread() класса Thread.
Следующий код приведет к тому, что программа повиснет, так как метод main() будет ожидать завершения (смерти) самого себя. Такое явление в многопоточном программировании называется взаимная блокировка или dead lock.
public static void main(String[] args)
throws InterruptedException{
Thread thread = Thread.currentThread();
thread.join();
}
Достаточно записи вида: Thread.currentThread().join() и программа висит)))
Такая же ситуация может возникнуть если один поток привязывается к другому и ожидает его смерти, в то время как этот второй поток привязался к первому и тоже ожидает его смерти. В итоге имеем dead lock. Два потока ждут завершения друг друга.
Кто не понял – объясняю популярно…
«Заходя в ванную, Анжела забыла взять с собой халат. Обычно она может выйти в комнату и в неодетом виде, но, пока она была в ванной, в гости зашёл Антон, которому Анжела должна отдать флешку, которая лежит у неё в сумочке. Сам Антон в сумочку лезть отказывается, и требует, чтобы флешку отдала ему Анжела. Без флешки он не уйдёт. Анжела не может выйти в комнату пока там Антон. Антон ждёт, пока ему отдадут флешку, Анжела ждёт ухода Антона, после которого она может выйти и отдать флешку.»
Теперь понятно? Продолжаем)))
Dead lock можно разрешить путем использования условного метода join(long millis) с параметром, равным времени в миллисекундах, на которое нужно привязаться к потоку и ожидать его смерти. Если бы в примере было записано thread.join(1000), то через 1000 мс. поток метода main() отпустило бы и он продолжил свое выполнение.
Напишем класс, в котором поток метода main()и порожденный им второй поток создадут dead lock.
public class Example {
public static void main(String[] args)
throws InterruptedException{
final Thread mainThread = Thread.currentThread();
Thread runThread = new Thread (new Runnable(){
public void run() {
try{
System.out.println("Run: wait for main! ");
mainThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
runThread.start();
System.out.println("Main: wait for run! ");
runThread.join();
}}
Main: wait for run!
Run: wait for main!
Два потока будут привязаны к друг другу и это приведет к дидлоку. В каком порядке будет выведен текст предсказать трудно. Одна из особенностей потоков – это недетерменизм. Когда стартуют два потока невозможно предсказать, кто завершится раньше, кто первый, а кто последний. Они выполняются абсолютно автономно. Поэтому предсказать заранее, чей метод join() выполнится первым практически невозможно. Это иногда приводит к существенным трудностям программирования потоков в Java.
Обратите внимание на то, что в классе при создании второго потока runThread был использован анонимный inner класс, в котором реализован метод run(). Такая странная конструкция встречается в Java очень часто. Будем с ней сталкиваться при обработке событий. При запуске такой программы компилятор создаст анонимный класс, который будет имплементить интерфейс Runnable, и инстанс (instance – экземпляр) этого класса. Вы, конечно, помните, что нельзя создавать экземпляры абстрактных классов и интерфейсов, если у них не реализованы методы. В данном случае мы реализовываем единственный метод интерфейса Runnable – метод run().
В стандартном jdk нет возможности стартовать мгновенно много (целый пучок) потоков. Потоки могут порождаться только последовательно. Это же относится и к методу join(). Нельзя привязаться и ждать завершения сразу нескольких потоков – только по очереди. Однако в Java версии 5 был добавлен пакет java.util.concurrent, содержащий более 50 методов, которые расширяют возможности класса Thread по работе с потоками. Кого интересует - изучайте документацию)))