Чтение онлайн

ЖАНРЫ

Java: руководство для начинающих
Шрифт:

Результат выполнения данной программы выглядит следующим образом: 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.

Рассмотрим подробнее эту программу. В ней определены три класса. Имя первого — SumArray. В нем содержится метод sumArray , вычисляющий сумму элементов целочисленного массива. Во втором классе MyThread используется статический объект sa типа SumArray для получения суммы элементов массива. А поскольку он статический, то все экземпляры класса MyThread используют одну его копию. И наконец, в классе Sync создаются два потока, в каждом из которых должна вычисляться сумма элементов массива.

В методе sumArray вызывается метод sleep . Он нужен лишь для того, чтобы обеспечить переключение задач. Метод sumArray синхронизирован, и поэтому в каждый момент времени он может использоваться только одним потоком. Следовательно, когда второй порожденный поток начинает свое исполнение, он не

может вызвать метод sumArray до тех пор, пока этот метод не завершится в первом потоке. Благодаря этому обеспечивается правильность получаемого результата.

Для того чтобы лучше понять эффект от использования ключевого слова synchronized, попробуйте удалить его из объявления метода sumArray . В итоге метод sumArray потеряет синхронизацию и может быть использован в нескольких потоках одновременно. Это приведет к затруднению в связи с тем, что результат расчета суммы сохраняется в переменной sum, значение которой изменяется при каждом вызове метода sumArray для статического объекта sa. Так, если в двух потоках одновременно сделать вызов sa. sumArray , расчет суммы окажется неверным, поскольку в переменной sum накапливаются результаты суммирования, выполняемого одновременно в двух потоках. Ниже приведен результат выполнения той же самой программы, где из объявления метода sumArray удалено ключевое слово synchronized. (Вследствие отличий в вычислительных средах у вас может получиться несколько иной результат.) Child #1 starting. Running total for Child #1 is 1 Child #2 starting Running total for Child #2 is 1 Running total for Child #1 is 3 Running total for Child #2 is 5 Running total for Child #2 is 8 Running total for Child #1 is 11 Running total for Child #2 is 15 Running total for Child #1 is 19 Running total for Child #2 is 24 Sum for Child #2 : Is 24 Child #2 terminating. Running total for Child #1 is 29 Sum for Child #1 : Ls 29 Child #1 terminating.

Нетрудно заметить, что вследствие одновременного вызова sa. sumArray из разных потоков результат искажается.

Прежде чем переходить к рассмотрению следующей темы, перечислим основные свойства синхронизированных методов.

Синхронизированный метод создается путем указания ключевого слова synchronized в его объявлении.

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

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

Когда синхронизированный метод завершается, разблокируется объект, для которого он вызывается. Синхронизированные блоки

Несмотря на то что создание синхронизированных методов в классах — простой и эффективный способ управления потоками, такой способ оказывается пригодным далеко не всегда. Иногда возникает потребность синхронизировать доступ к методам, в объявлении которых отсутствует ключевое слово synchronized. Подобная ситуация часто возникает при использовании классов, которые были созданы независимыми разработчиками и исходный код которых недоступен. В таком случае ввести в объявление нужного метода ключевое слово synchronized вряд ли удастся. Как же тогда синхронизировать объект класса, содержащего этот метод? К счастью, данное затруднение разрешается очень просто. Достаточно ввести вызов метода в синхронизированный кодовый блок типа synchronized.

Синхронизированный блок определяется следующим образом: synchronized{ссылка_на_объект) { // синхронизируемые операторы }

где ссылканаобъект обозначает ссылку на конкретный объект, который должен быть синхронизирован. Как только содержимое синхронизированного блока получит управление, ни один другой поток не сможет вызвать метод для объекта, на который делается ссылканаобъект9 до тех пор, пока этот кодовый блок не завершится.

Следовательно, обращение к методу sumArray можно синхронизировать, вызвав его из синхронизированного блока. Такой способ демонстрируется в приведенной ниже переделанной версии предыдущей программы. // Применение синхронизированного блока // для управления доступом к методу sumArray. class SumArray { private int sum; // Здесь метод sumArray не синхронизирован. int sumArray(int nums[]) { sum =0; // обнулить сумму for(int i=0; icnums.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."); // Здесь вызовы метода sumArray для объекта sa синхронизированы. synchronized(sa) { 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); try { mtl.thrd.join; mt2.thrd.join; } catch (InterruptedException exc) { System.out.println("Main thread interrupted."); } } }

Поделиться с друзьями: