Java: руководство для начинающих
Шрифт:
Как видите, после того как вызываемый метод j oin возвращает управление, исполнение потока прекращается. Приоритеты потоков
У каждого потока имеется свой приоритет, который отчасти определяет, насколько часто поток получает доступ к ЦП. Вообще говоря, низкоприоритетные потоки получают доступ к ЦП реже, чем высокоприоритетные. Таким образом, в течение заданного промежутка времени низкоприоритетному потоку будет доступно меньше времени ЦП, чем высокоприоритетному. Как и следовало ожидать, время ЦП, получаемое потоком, оказывает определяющее влияние на характер его исполнения и взаимодействия с другими потоками, исполняемыми в настоящий момент в системе.
Следует иметь в виду, что, помимо приоритета, на частоту доступа потока к ЦП оказывают влияние и другие факторы. Так, если высокоприоритетный поток ожидает доступа к некоторому ресурсу, например для ввода
При запуске порожденного потока его приоритет устанавливается равным приоритету родительского потока. Изменить приоритет можно, вызвав метод setPriority из класса Thread. Ниже приведено объявление этого метода, final void setPriority(int уровень)
В качестве параметра уровень данному методу передается новый приоритет для потока. Значение параметра уровень должно находиться в пределах от MIN PRIORITY до MAX PRIORITY. В настоящее время этим константам соответствуют числовые значения от 1 до 10. Для того чтобы восстановить приоритет потока по умолчанию, следует указать значение 5, которому соответствует константа N0RM PRI0RITY. Константы, определяющие приоритеты потоков, определены как static final в классе Thread.
Получить текущий приоритет можно с помощью метода getPriorityO из класса Thread, объявляемого следующим образом: final int getPriority
Ниже приведен пример программы, демонстрирующий использование двух потоков с разными приоритетами. Потоки создаются как экземпляры класса Priority. В методе run содержится цикл, отсчитывающий число своих шагов. Этот цикл завершает работу, когда значение счетчика достигает 10000000 или же когда статическая переменная stop принимает логическое значение true. Первоначально переменной stop присваивается логическое значение false, но первый же поток, заканчивающий отсчет, устанавливает в ней логическое значение true. В результате второй поток завершится, как только ему будет выделен квант времени. В цикле производится проверка символьной строки в переменной currentName на совпадение с именем исполняемого потока. Если они не совпадают, это означает, что произошло переключение задач. При этом отображается имя нового потока, которое присваивается переменной currentName. Это дает возможность следить за тем, насколько часто каждый поток получает время ЦП. После остановки обоих потоков выводится число шагов, выполненных в каждом цикле. // Демонстрация потоков с разными приоритетами. class Priority implements Runnable { int count; Thread thrd; static boolean stop = false; static String currentName; /* Построение нового потока. Обратите внимание на то, что конструктор не запускает поток на исполнение. */ Priority(String name) { thrd = new Thread(this, name); count = 0; currentName = name; } // начать исполнение нового потока public void run { System.out.println(thrd.getName + " starting."); do { count++; if(currentName.compareTo(thrd.getName) != 0) { currentName = thrd.getName; System.out.println("In " + currentName); } // Первый же поток, в котором достигнуто значение 10000000, // завершает остальные потоки. } while(stop == false && count < 10000000); stop = true; System.out.println("\n" + thrd.getName + " terminating."); } } class PriorityDemo { public static void main(String args[]) { Priority mtl = new Priority("High Priority"); Priority mt2 = new Priority("Low Priority"); // задать приоритеты // Поток mtl получает более высокий приоритет, чем поток mt2. mtl.thrd.setPriority(Thread.NORM_PRIORITY+2); mt2.thrd.setPriority(Thread.NORM_PRIORITY-2); // запустить потоки на исполнение mtl.thrd.start; mt2.thrd.start; try { mtl.thrd.join; mt2.thrd.join; } catch(InterruptedException exc) { System.out.println("Main thread interrupted."); } System.out.println("\nHigh priority thread counted to " + mtl.count); System.out.println("Low priority thread counted to " + mt2.count); } }
Результат выполнения данной программы выглядит следующим образом: High Priority starting. In High Priority Low Priority starting. In Low Priority In High Priority High Priority terminating. Low Priority terminating. High priority thread counted to 10000000 Low priority thread counted to 8183
В данном примере большую часть времени ЦП получает высокоприоритетный поток. Очевидно, что результат выполнения программы существенно зависит от быстродействия ЦП и их количества, типа операционной системы и наличия прочих задач, выполняющихся в системе. Синхронизация
Если в программе используется несколько потоков, то иногда приходится координировать действия двух потоков или более. Процесс достижения такой координации называется синхронизацией. Самой распространенной причиной для синхронизации является необходимость разделять среди двух или более потоков общий ресурс, который может быть одновременно доступен только одному потоку. Например, когда в одном потоке выполняется запись информации в файл, второму потоку должно быть запрещено делать это в тот же самый момент времени. Синхронизация требуется и в том случае, если один поток ожидает событие, вызываемое другим потоком. В подобной ситуации требуются какие-то средства, позволяющие приостановить один из потоков до тех пор, пока не произойдет событие в другом потоке. После этого ожидающий поток может возобновить свое выполнение.
Главным для синхронизации в Java является понятие монитора, контролирующего доступ к объекту. Монитор реализует принцип блокировки. Если объект заблокирован одним потоком, то он оказывается недоступным для других потоков. В какой-то момент объект разблокируется, и другие потоки могут обращаться к нему.
У каждого объекта в Java имеется свой монитор. Этот механизм встроен в сам язык. Следовательно, все объекты поддаются синхронизации. Для поддержки синхронизации в Java предусмотрено ключевое слово synchronized и ряд вполне определенных методов у каждого из объектов. А поскольку средства синхронизации встроены в язык, то пользоваться ими на практике очень просто — гораздо проще, чем может показаться на первый взгляд. Для многих программ средства синхронизации объектов по сути прозрачны.
Синхронизировать код можно двумя способами. Оба способа рассматриваются ниже, и в обоих используется ключевое слово synchronized. Применение синхронизированных методов
Для того чтобы синхронизировать метод, в его объявлении следует указать ключевое слово synchronized. Когда такой метод получает управление, вызывающий поток активизирует монитор, что приводит к блокированию объекта. Если объект блокирован, он недоступен из другого потока, а кроме того, его нельзя вызвать из других синхронизированных методов, определенных в классе данного объекта. Когда выполнение синхронизированного метода завершается, монитор разблокирует объект, что позволяет другому потоку использовать этот метод. Таким образом, для достижения синхронизации программирующему на Java не приходится прилагать каких-то особых усилий.
Ниже приведен пример программы, демонстрирующий контролируемый доступ к методу sumArray . Этот метод суммирует элементы целочисленного массива. // Применение ключевого слова synchronize для управления доступом. class SumArray { private int sum; // Метод sumArray синхронизирован. synchronized int sumArray(int nums[]) { sum = 0; // обнулить сумму for(int i=0; i<nums.length; i++) { sum += nums[i]; System.out.println("Running total for " + Thread.currentThread.getName + " is " + sum); try { Thread.sleep(10); // разрешить переключение задач } catch(InterruptedException exc) { System.out.println("Main thread interrupted."); } } return sum; } } class MyThread implements Runnable { Thread thrd; static SumArray sa = new SumArray; int a[]; int answer; // построить новый поток MyThread(String name, int nums[]) { thrd = new Thread(this, name); a = nums;. thrd.start; // начать поток } // начать исполнение нового потока public void run { int sum; System.out.println(thrd.getName + " starting."); answer = sa.sumArray(a); System.out.println("Sum for " + thrd.getName + " is " + answer); System.out.println(thrd.getName + " terminating."); } } class Sync { public static void main(String args[]) { int a[] = {1, 2, 3, 4, 5}; MyThread mtl = new MyThread("Child #1", a); MyThread mt2 = new MyThread("Child #2", a); } }
Выполнение этой программы дает следующий результат: Child #1 starting. Running total for Child #1 is 1 Child #2 starting. Running total for Child #1 is 3 Running total for Child #1 is 6 Running total for Child #1 is 10 Running total for Child #1 is 15 Sum for Child #1 is 15 Child #1 terminating. Running total for Child #2 is 1 Running total for Child #2 is 3 Running total for Child #2 is 6 Running total for Child #2 is 10 Running total for Child #2 is 15 Sum for Child #2 is 15 Child #2 terminating.