класс ThreadGroup Все потоки находятся в группах, представляемых экземплярами класса ThreadGroup. Группа указывается при создании потока. Если группа не была указана, то поток помещается в ту же группу, где находится поток, породивший его.
Методы activeCount() и enumerate() возвращают количество и полный список, соответственно, всех потоков в группе.
sleep() Этот статический метод класса Thread приостанавливает выполнение текущего потока на указанное количество миллисекунд. Обратите внимание, что метод требует обработки исключения InterruptedException. Он связан с возможностью активизировать метод, который приостановил свою работу. Например, если поток занят выполнением метода sleep(), то есть бездействует на протяжении указанного периода времени, его можно вывести из этого состояния, вызвав метод interrupt() из другого потока выполнения. В результате метод sleep () прервется исключением InterruptedException.
Кроме метода sleep(), существует еще один статический метод yield() без параметров. Когда поток вызывает его, он временно приостанавливает свою работу и позволяет отработать другим потокам. Один из методов обязательно должен применяться внутри бесконечных циклов ожидания, иначе есть риск, что такой ничего не делающий поток затормозит работу остальных потоков.
Синхронизация
При многопоточной архитектуре приложения возможны ситуации, когда несколько потоков будут одновременно работать с одними и теми же данными, используя их значения и присваивая новые. В таком случае результат работы программы становится невозможно предугадать, глядя только на исходный код. Финальные значения переменных будут зависеть от случайных факторов, исходя из того, какой поток какое действие успел сделать первым или последним.
Рассмотрим пример:
public class ThreadTest { private int a=1, b=2; public void one() { a=b; } public void two() { b=a; } public static void main(String s[]) { int a11=0, a22=0, a12=0; for (int i=0; i<1000; i++) { final ThreadTest o = new ThreadTest(); // Запускаем первый поток, который // вызывает один метод new Thread() { public void run() { o.one(); } }.start(); // Запускаем второй поток, который // вызывает второй метод new Thread() { public void run() { o.two(); } }.start(); // даем потокам время отработать try { Thread.sleep(100); } catch (InterruptedException e) {} // анализируем финальные значения if (o.a==1 && o.b==1) a11++; if (o.a==2 && o.b==2) a22++; if (o.a!=o.b) a12++; } System.out.println(a11+' '+a22+' '+a12); } } Пример 12.3.
В этом примере два потока исполнения одновременно обращаются к одному и тому же объекту, вызывая у него два разных метода, one() и two(). Эти методы пытаются приравнять два поля класса a и b друг другу, но в разном порядке. Учитывая, что исходные значения полей равны 1 и 2, соответственно, можно было ожидать, что после того, как потоки завершат свою работу, поля будут иметь одинаковое значение. Однако понять, какое из двух возможных значений они примут, уже невозможно. Посмотрим на результат программы:
135 864 1
Первое число показывает, сколько раз из тысячи обе переменные приняли значение 1. Второе число соответствует значению 2. Такое сильное преобладание одного из значений обусловлено последовательностью запусков потоков. Если ее изменить, то и количества случаев с 1 и 2 также меняются местами. Третье же число сообщает, что на тысячу случаев произошел один, когда поля вообще обменялись значениями!
При количестве итераций, равном 10000, были получены следующие данные, которые подтверждают сделанные выводы: