Смекни!
smekni.com

Общие представления о языке Java 5 (стр. 58 из 68)

thread1.start();

Второй вариант – использование класса, в котором реализован интерфейс java.lang.Runnable. Этот интерфейс, как уже говорилось, имеет единственный метод public void run(). Реализовав его в классе, можно создать поток с помощью перегруженного варианта конструктора Thread:

public class R1 implements Runnable{

public void run(){

...

}

...

}

Thread thread1= Thread( new R1() );

thread1.start();

Обычно таким способом пользуются гораздо чаще, так как в разрабатываемом классе не приходится заниматься дублированием конструкторов класса Thread. Кроме того, этот способ можно применять в случае, когда уже имеется класс, принадлежащий иерархии, в которой базовым классом не является Thread или его наследник, и мы хотим использовать этот класс для работы внутри потока. В результате от этого класса мы получаем метод run(), в котором реализован нужный алгоритм, и этот метод работает внутри потока типа Thread, обеспечивающего необходимое поведение в многопоточной среде. Однако в данном случае затрудняется доступ к методам из класса Thread – требуется приведение типа.

Например, чтобы вывести информацию о приоритете потока, в первом способе создания потока в методе run() надо написать оператор

System.out.println("Приоритет потока="+this.getPriority());

А во втором способе приходиться это делать в несколько этапов. Во-первых, при задании класса нам следует добавить в объекты типа R1 поле thread:

public class R1 implements Runnable{

public Thread thread;

public void run() {

System.out.println("Приоритет потока="+thread.getPriority());

}

}

С помощью этого поля мы будем добираться до объекта-потока. Но теперь после создания потока необходимо не забыть установить для этого поля ссылку на созданный объект-поток. Так что создание и запуск потока будет выглядеть так:

R1 r1=new R1();

Thread thread1=new Thread(r1, "thread1");

r1.thread=thread1;

thread1.start();//либо, что то же, r1.thread.start()

Через поле thread мы можем получать доступ к потоку и всем его полям и методам в алгоритме, написанном в методе run(). Указанные выше дополнительные действия – это всего три лишних строчки программы (первая - R1 r1=new R1(); вторая - r1.thread=thread1; третья - объявление в классе R1 - public Thread thread;) .

Как уже говорилось ранее, напрямую давать доступ к полю данных – дурной тон программирования. Исправить этот недостаток нашей программы просто: в дереве элементов программы окна Projects в разделе Fields (“поля”) щёлкнем правой кнопкой мыши по имени thread и выберем в появившемся всплывающем меню Refactor/Encapsulate Fields… (“Провести рефакторинг”/ “Инкапсулировать поля…”). В появившемся диалоге нажмём на кнопку “Next>” и проведём рефакторинг, подтвердив выбор в нижнем окне.

В классе Thread имеется несколько перегруженных вариантов конструктора с параметром типа Runnable:

public Thread(Runnable target) – с именем “system” по умолчанию.

public Thread(Runnable target, String name) – с заданием имени.

Также имеются варианты с заданием группы потоков.

Поля и методы, заданные в классе Thread

В классе Thread имеется ряд полей данных и методов, про которые надо знать для работы с потоками.

Важнейшие константы и методы класса Thread:

  • MIN_PRIORITY – минимально возможный приоритет потоков. Зависит от операционной системы и версии JVM. На компьютере автора оказался равен 1.
  • NORM_PRIORITY - нормальный приоритет потоков. Главный поток создаётся с нормальным приоритетом, а затем приоритет может быть изменён. На компьютере автора оказался равен 5.
  • MAX_PRIORITY – максимально возможный приоритет потоков. На компьютере автора оказался равен 10.
  • static int activeCount() - возвращает число активных потоков приложения.
  • static Thread currentThread() – возвращает ссылку на текущий поток.
  • boolean holdsLock(Object obj) – возвращает true в случае, когда какой-либо поток (то есть текущий поток) блокирует объект obj.
  • static boolean interrupted() – возвращает состояние статуса прерывания текущего потока, после чего устанавливает его в значение false.

Важнейшие методы объектов типа Thread:

  • void run() – метод, который обеспечивает последовательность действий во время жизни потока. В классе Thread задана его пустая реализация, поэтому в классе потока он должен быть переопределён. После выполнения метода run()поток умирает.
  • void start() – вызывает выполнение текущего потока, в том числе запуск его метода run() в нужном контексте. Может быть вызван всего один раз.
  • void setDaemon(boolean on) – в случае on==true устанавливает потоку статус демона, иначе – статус пользовательского потока.
  • boolean isDaemon() - возвращает true в случае, когда текущий поток является демоном.
  • void yield() – “поступиться правами” – вызывает временную приостановку потока, с передачей права другим потокам выполнить необходимые им действия.
  • long getId() – возвращает уникальный идентификатор потока. Уникальность относится только ко времени жизни потока - после его завершения (смерти) данный идентификатор может быть присвоен другому создаваемому потоку.
  • String getName() – возвращает имя потока, которое ему было задано при создании или методом setName.
  • void setName(String name) – устанавливает новое имя потока.
  • int getPriority() - возвращает приоритет потока.
  • void setPriority(int newPriority) – устанавливает приоритет потока.
  • void checkAccess() – осуществление проверки из текущего потока на позволительность доступа к другому потоку. Если поток, из которого идёт вызов, имеет право на доступ, метод не делает ничего. Иначе – возбуждает исключение SecurityException.
  • String toString() – возвращает строковое представление объекта потока, в том числе – его имя, группу, приоритет.
  • void sleep(long millis) – вызывает приостановку (“засыпание”) потока на millis миллисекунд. При этом все блокировки (мониторы) потока сохраняются. Перегруженный вариант sleep(long millis,int nanos)- параметр nanos задаёт число наносекунд. Досрочное пробуждение осуществляется методом interrupt() – с возбуждением исключения InterruptedException.
  • void interrupt() –прерывает “сон” потока, вызванный вызовами wait(…) или sleep(…), устанавливая ему статус прерванного (статус прерывания=true). При этом возбуждается проверяемая исключительная ситуация InterruptedException.
  • boolean isInterrupted() - возвращает текущее состояние статуса прерывания потока без изменения значения статуса.
  • void join() – “слияние”. Переводит поток в режим умирания - ожидания завершения (смерти). Это ожидание – выполнение метода join() - может длиться сколь угодно долго, если соответствующий поток на момент вызова метода join() блокирован. То есть если в нём выполняется синхронизованный метод или он ожидает завершения синхронизованного метода. Перегруженный вариант join(long millis) - ожидать завершения потока в течение millis миллисекунд. Вызов join(0)эквивалентен вызову join(). Ещё один перегруженный вариант join(long millis,int nanos)- параметр nanos задаёт число наносекунд. Ожидание смерти может быть прервано другим потоком с помощью метода interrupt() – с возбуждением исключения InterruptedException. Метод join() является аналогом функции join в UNIX. Обычно используется для завершения главным потоком работы всех дочерних пользовательских потоков (“слияния” их с главным потоком).
  • boolean isAlive() - возвращает true в случае, когда текущий поток жив (не умер). Отметим, что даже если поток завершился, от него остаётся объект-“призрак”, отвечающий на запрос isAlive()значением false – то есть сообщающий, что объект умер.

Следует отметить, что все ведущие разработчики процессоров перешли на многоядерную технологию. При этом в одном корпусе процессора расположено несколько процессорных ядер, способных независимо выполнять вычисления, но они имеют доступ к одной и той же общей памяти. В связи с этим программирование в многопоточной среде признано наиболее перспективной моделью параллелизации программ и становится одним из важнейших направлений развития программных технологий. Модель многопоточности Java позволяет весьма элегантно реализовать преимущества многоядерных процессорных систем. Во многих случаях программы Java, написанные с использованием многопоточности, эффективно распараллеливаются автоматически на уровне виртуальной машины - без изменения не только исходного, но даже скомпилированного байт-кода приложений.

Но программирование в многопоточной среде является сложным и ответственным занятием, требующим очень высокой квалификации. Многие алгоритмы, кажущиеся простыми, естественными и надёжными, в многопоточной среде оказываются неработоспособными. Из-за чего способы решения даже самых простых задач становятся необычными и запутанными, не говоря уж о проблемах, возникающих при решении сложных задач. В связи с этим автор рекомендует на начальном этапе не увлекаться многопоточностью, а только ознакомиться с данной технологией.

Тем, кто всё же хочет заняться таким программированием, рекомендуется сначала прочитать главу 9 в книге Джошуа Блоха [8].

Подключение внешних библиотек DLL.“Родные” (native) методы*

*- данный параграф приводится в ознакомительных целях

Для прикладного программирования средств Java в подавляющем большинстве случаев хватает. Однако иногда возникает необходимость подключить к программе ряд системных вызовов. Либо обеспечить доступ к библиотекам, написанным на других языках программирования. Для таких целей в Java используются методы, объявленные с модификатором native –“родной”. Это слово означает, что при выполнении метода производится вызов “родного” для конкретной платформы двоичного кода, а не платформо-независимого байт-кода как во всех других случаях. Заголовок “родного” метода описывается в классе Java, а его реализация осуществляется на каком-либо из языков программирования, позволяющих создавать динамически подключаемые библиотеки (DLL – Dynamic Link Library под Windows, Shared Objects под UNIX-образными операционными системами).

Правило для объявления и реализации таких методов носит название JNI – Java Native Interface.

Объявление “родного” метода в Java имеет вид

Модификаторы native ВозвращаемыйТип имяМетода(список параметров);

Тело “родного” метода не задаётся – оно является внешним и загружается в память компьютера с помощью загрузки той библиотеки, из которой этот метод должен вызываться: