1. Обзор
В этом руководстве мы обсудим различные методы join() в классе
Thread
. Мы подробно рассмотрим эти методы и некоторый пример кода.
Подобно методам wait()
и notify()
, join()
является еще одним механизмом синхронизации между потоками.
Вы можете быстро просмотреть этот учебник , чтобы узнать больше о wait()
и notify()
.
2. Метод Thread.join ()
Метод соединения определен в классе Thread
:
> `public final void join() throws InterruptedException`
Ожидает завершения этого потока.
Когда мы вызываем метод join()
для потока, вызывающий поток переходит в состояние ожидания. Он остается в состоянии ожидания до тех пор, пока указанный поток не завершится.
Мы можем увидеть это поведение в следующем коде:
class SampleThread extends Thread {
public int processingCount = 0;
SampleThread(int processingCount) {
this.processingCount = processingCount;
LOGGER.info("Thread Created");
}
@Override
public void run() {
LOGGER.info("Thread " + this.getName() + " started");
while (processingCount > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
LOGGER.info("Thread " + this.getName() + " interrupted");
}
processingCount--;
}
LOGGER.info("Thread " + this.getName() + " exiting");
}
}
@Test
public void givenStartedThread_whenJoinCalled_waitsTillCompletion()
throws InterruptedException {
Thread t2 = new SampleThread(1);
t2.start();
LOGGER.info("Invoking join");
t2.join();
LOGGER.info("Returned from join");
assertFalse(t2.isAlive());
}
При выполнении кода следует ожидать результатов, подобных приведенным ниже:
INFO: Thread Created
INFO: Invoking join
INFO: Thread Thread-1 started
INFO: Thread Thread-1 exiting
INFO: Returned from join
Метод join()
также может возвращать значение, если указанный поток был прерван . В этом случае метод выдает InterruptedException
.
Наконец, если указанный поток уже завершен или не был запущен, вызов метода join()
немедленно возвращает значение .
Thread t1 = new SampleThread(0);
t1.join(); //returns immediately
3. Методы Thread.join()
с тайм-аутом
Метод join()
будет продолжать ждать, если указанный поток заблокирован или его обработка занимает слишком много времени. Это может стать проблемой, поскольку вызывающий поток перестанет отвечать на запросы. Чтобы справиться с такими ситуациями, мы используем перегруженные версии метода join()
, которые позволяют нам указать период ожидания.
Есть две временные версии , которые перегружают метод join()
:
«public final void join(long
millis) throws InterruptedException Ожидает
самое
большее миллисекунды миллисекунды, пока этот поток не умрет. Тайм-аут, равный 0, означает ожидание вечности».
«public final void join(long
millis,int `nanos
) выдает InterruptedException Ожидает`
самое
большее миллисекунды плюс
наносекунды , пока этот поток не умрет».
Мы можем использовать timed join()
, как показано ниже:
@Test
public void givenStartedThread_whenTimedJoinCalled_waitsUntilTimedout()
throws InterruptedException {
Thread t3 = new SampleThread(10);
t3.start();
t3.join(1000);
assertTrue(t3.isAlive());
}
В этом случае вызывающий поток ожидает завершения потока t3 примерно 1 секунду. Если поток t3 не завершится в этот период времени, метод join()
возвращает управление вызывающему методу.
Timed join()
зависит от времени ОС. Таким образом, мы не можем предполагать, что join()
будет ждать ровно столько времени, сколько указано.
4. Методы Thread.join()
и синхронизация
В дополнение к ожиданию завершения вызов метода join()
имеет эффект синхронизации. join() создает отношение « происходит до »:
«Все действия в потоке происходят до того, как какой-либо другой поток успешно вернется из функции join() в этом потоке».
Это означает, что когда поток t1 вызывает t2.join(), все изменения, сделанные t2, видны в t1 по возвращении. Однако, если мы не вызываем join()
или не используем другие механизмы синхронизации, у нас нет никакой гарантии, что изменения в другом потоке будут видны текущему потоку, даже если другой поток завершился.
Следовательно, несмотря на то, что вызов метода join()
для потока, находящегося в прекращенном состоянии, возвращается немедленно, в некоторых ситуациях нам все равно нужно вызывать его.
Мы можем увидеть пример неправильно синхронизированного кода ниже:
SampleThread t4 = new SampleThread(10);
t4.start();
// not guaranteed to stop even if t4 finishes.
do {
} while (t4.processingCount > 0);
Чтобы правильно синхронизировать приведенный выше код, мы можем добавить timed t4.join()
внутри цикла или использовать какой-либо другой механизм синхронизации.
5. Вывод
Метод join()
весьма полезен для синхронизации между потоками. В этой статье мы обсудили методы join()
и их поведение. Мы также рассмотрели код, использующий метод join() .
Как всегда, полный исходный код можно найти на GitHub .