Java se сервлеты и jsp паттерны Gof junit Log4j ant


Глава 11 ПОТОКИ ВЫПОЛНЕНИЯ



Pdf көрінісі
бет17/22
Дата11.12.2019
өлшемі8,99 Mb.
#53432
1   ...   14   15   16   17   18   19   20   21   22
Байланысты:
JAVA Methods Programming v2.march2015 (2)

Глава 11
ПОТОКИ ВЫПОЛНЕНИЯ
Соседняя очередь всегда движется быстрее.
Наблюдение Этторе
Как только вы перейдете в другую очередь,  
ваша начнет двигаться быстрее.
Наблюдение О’Брайена
Класс Thread и интерфейс Runnable
К  большинству  современных  распределенных  приложений  (Rich  Client) 
и веб-приложений (Thin Client) выдвигаются требования одновременной под-
держки многих пользователей, каждому из которых выделяется отдельный по-
ток, а также разделения и параллельной обработки информационных ресурсов. 
Потоки — средство, которое помогает организовать одновременное выполне-
ние нескольких задач, каждой в независимом потоке. Потоки представляют со-
бой  экземпляры  классов,  каждый  из  которых  запускается  и  функционирует 
самостоятельно, автономно (или относительно автономно) от главного потока 
выполнения программы. Существует два способа создания и запуска потока: 
на основе расширения класса Thread или реализации интерфейса Runnable.
// # 1 # расширение класса Thread # TalkThread.java
package
 by.bsu.threads;
public class
 TalkThread extends Thread {
 @Override
 public
 void run() {
  for
 (int i = 0; i < 10; i++) {
   System.out.println("Talking");
   try
 {
    Thread.sleep(7); // остановка на 7 миллисекунд
   } 
catch
 (InterruptedException e) {
    System.err.print(e);
   }
  }
 }
}
При реализации интерфейса Runnable необходимо определить его единст-
венный абстрактный метод run(). Например:

ПОТОКИ ВЫПОЛНЕНИЯ
291
/* # 2 # реализация интерфейса Runnable # WalkRunnable.java # WalkTalk.java */
package
 by.bsu.threads;
public class
 WalkRunnable implements Runnable {
 @Override
 public
 void run() {
  for
 (int i = 0; i < 10; i++) {
   System.out.println("Walking");
   try
 {
    Thread.sleep(7);
   } 
catch
 (InterruptedException e) {
    System.err.println(e);
   }
  }
 }
}
package
 by.bsu.threads;
public
 class WalkTalk {
 public
 static void main(String[ ] args) {
  // 
новые объекты потоков 
 
 
TalkThread talk = new TalkThread();
 
 
Thread walk = new Thread(new WalkRunnable());
  // 
запуск потоков
  talk.start();
  walk.start();
  // WalkRunnable w = new WalkRunnable(); // просто объект, не поток 
  // 
w.run(); или talk.run(); // выполнится метод, но поток не запустится!
 }
}
Запуск  двух  потоков  для  объектов  классов  TalkThread  непосредственно 
и WalkRunnable через инициализацию экземпляра Thread приводит к выводу 
строк: Talking Walking. Порядок вывода, как правило, различен при несколь-
ких запусках приложения.
Интерфейс Runnable не имеет метода start(), а только единственный метод 
run(). Поэтому для запуска такого потока, как WalkRunnable следует создать 
экземпляр класса Thread с передачей экземпляра WalkRunnable его конструк-
тору. Однако при прямом вызове метода run() поток не запустится, выполнится 
только тело самого метода.
Жизненный цикл потока
При  выполнении  программы  объект  класса  Thread  может  быть  в  одном 
из четырех основных состояний: «новый», «работоспособный», «неработоспо-
собный» и «пассивный». При создании потока он получает состояние «новый» 
(NEW) и не выполняется. Для перевода потока из состояния «новый» в состо-
яние «работоспособный» (RUNNABLE) следует выполнить метод start(), ко-
торый вызывает метод run() — основной метод потока.

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
292
Поток  может  находиться  в  од-
ном  из  состояний,  соответствую-
щих элементам статически вложен-
ного перечисления Thread.State:
NEW — поток создан, но еще 
не запущен;
RUNNABLE  —  поток  выпол-
няется;
BLOCKED — поток блокиро-
ван;
WAITING — поток ждет окон-
чания работы другого потока;
TIMED_WAITING — поток некоторое время ждет окончания другого потока;
TERMINATED — поток завершен.
Получить  текущее  значение  состояния  потока  можно  вызовом  метода  
getState().
Поток  переходит  в  состояние  «неработоспособный»  в  режиме  ожидания 
(WAITING) вызовом методов join(), wait(), suspend() (deprecated-метод) или 
методов ввода/вывода, которые предполагают задержку. Для задержки потока 
на некоторое время (в миллисекундах) можно перевести его в режим ожидания 
по времени (TIMED_WAITING) с помощью методов yield(), sleep(long millis), 
join(long timeout) и wait(long timeout), при выполнении которых может гене-
рироваться прерывание InterruptedException. Вернуть потоку работоспособ-
ность  после  вызова  метода  suspend()  можно  методом  resume()  (deprecated-
метод), а после вызова метода wait() — методами notify() или notifyAll(). Поток 
переходит в «пассивное» состояние (TERMINATED), если вызваны методы 
interrupt(),  stop()  (deprecated-метод)  или  метод  run()  завершил  выполнение, 
и запустить его повторно уже невозможно. После этого, чтобы запустить по-
ток, необходимо создать новый объект потока. Метод interrupt() успешно за-
вершает поток, если он находится в состоянии «работоспособный». Если же 
поток неработоспособен, например, находится в состоянии TIMED_WAITING
то  метод  инициирует  исключение  InterruptedException.  Чтобы  это  не  про-
исходило, следует предварительно вызвать метод isInterrupted(), который про-
верит возможность завершения работы потока. При разработке не следует ис-
пользовать  методы  принудительной  остановки  потока,  так  как  возможны 
проблемы с закрытием ресурсов и другими внешними объектами.
Методы suspend(), resume() и stop() являются deprecated-методами и запрещены 
к использованию, так как они не являются в полной мере «потокобезопасными».
Рис. 11.1. 
Состояния потока

ПОТОКИ ВЫПОЛНЕНИЯ
293
Управление приоритетами и группы потоков
Потоку  можно  назначить  приоритет  от  1  (константа  MIN_PRIORITY
до 10 (MAX_PRIORITY) с помощью метода setPriority(int prior). Получить 
значение приоритета потока можно с помощью метода getPriority().
// # 3 # демонстрация приоритетов # PriorityRunner.java # PriorThread.java 
package
 by.bsu.priority;
public
 class PriorThread extends Thread {
 public
 PriorThread(String name) {
  super
(name);
 }
 public
 void run() {
  for
 (int i = 0; i < 71; i++) {
   System.out.println(getName() + " " + i);
   try
 { 
    Thread.sleep(1); // попробовать sleep(0),sleep(10)
   } 
catch
 (InterruptedException e) {
    System.err.print(e);
   }
  }
 }
}
package
 by.bsu.priority;
public
 class PriorityRunner {
 public
 static void main(String[ ] args) {
 
 
PriorThread min = new PriorThread("Min");
 
 
PriorThread max = new PriorThread("Max");
 
 
PriorThread norm = new PriorThread("Norm");
 
 
min.setPriority(Thread.MIN_PRIORITY); // 1
 
 
max.setPriority(Thread.MAX_PRIORITY); // 10
 
 
norm.setPriority(Thread.NORM_PRIORITY); // 5
  min.start();
  norm.start();
  max.start();
 }
}
Поток с более высоким приоритетом в данном случае, как правило, монопо-
лизирует вывод на консоль.
Потоки объединяются в группы потоков. После создания потока нельзя из-
менить его принадлежность к группе.
ThreadGroup tg = new ThreadGroup("Группа потоков 1");
Thread t0 = new Thread(tg, "поток 0");
Все потоки, объединенные в группу, имеют одинаковый приоритет. Чтобы опре-
делить, к какой группе относится поток, следует вызвать метод getThreadGroup()

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
294
Если поток до включения в группу имел приоритет выше приоритета группы 
потоков, то после включения значение его приоритета станет равным приори-
тету группы. Поток же со значением приоритета, более низким, чем приоритет 
группы после включения в оную, значения своего приоритета не изменит.
Управление потоками
Приостановить  (задержать)  выполнение  потока  можно  с  помощью  метода 
sleep(int millis) класса Thread. Менее надежный альтернативный способ состо-
ит в вызове метода yield(), который может сделать некоторую паузу и позволя-
ет другим потокам начать выполнение своей задачи. Метод join() блокирует 
работу потока, в котором он вызван, до тех пор, пока не будет закончено выпол-
нение вызывающего метод потока или не истечет время ожидания при обраще-
нии к методу join(long timeout).
// # 4 # задержка потока # JoinRunner.java 
package
 by.bsu.management;
class
 JoinThread extends Thread {
public
 JoinThread (String name) {
super
(name);
}
public
 void run() {
String nameT = getName();
long
 timeout = 0;
System.out.println("Старт потока " + nameT);
try
 {
switch
 (nameT) {
    case "First":
      timeout = 5_000;
break;
case 
 "Second":
timeout = 1_000;
}
Thread.sleep(timeout);
System.out.println("завершение потока " + nameT);

catch
 (InterruptedException e) {
e.printStackTrace();
}
}
}
public
 class JoinRunner {
   static
 { 
System.out.println("Старт потока main"); 
}
public
 static void main(String[ ] args) {
JoinThread t1 = new JoinThread("First");
JoinThread t2 = new JoinThread("Second");

ПОТОКИ ВЫПОЛНЕНИЯ
295
t1.start();
t2.start();
try
 {
    t1.join()
// поток main остановлен до окончания работы потока t1

catch
 (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.сurrentThread().getName()); // имя текущего потока
}
}
Возможно, будет выведено:
Старт потока main
Старт потока First
Старт потока Second
завершение потока Second
завершение потока First
main
Несмотря  на  вызов  метода  join()  для  потока  t1,  поток  t2  будет  работать, 
в отличие от потока main, который сможет продолжить свое выполнение толь-
ко по завершении потока t1.
Если  вместо  метода  join()  без  параметров  использовать  версию  join(long 
timeout),  то  поток  main  будет  остановлен  только  на  указанный  промежуток 
времени. При вызове t1.join(500) вывод будет другим:
Старт потока First
Старт потока Second
main
завершение потока Second
завершение потока First
Статический метод 
currentThread() возвращает ссылку на текущий поток, 
т. е. на поток, в котором данный метод был вызван.
Вызов статического метода yield() для исполняемого потока должен приво-
дить к приостановке потока на некоторый квант времени, чтобы другие потоки 
могли выполнять свои действия. Например, в случае потока с высоким приори-
тетом после обработки части пакета данных, когда следующая еще не готова, 
стоит уступить часть времени другим потокам. Однако если требуется надеж-
ная остановка потока, то следует использовать его крайне осторожно или вооб-
ще применить другой способ.
// # 5 # задержка потока # YieldRunner.java 
package
 by.bsu.yield;
public
 class YieldRunner {
public
 static void main(String[ ] args) {

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
296
  new
 Thread() { // анонимный класс
   public
 void run() {
    System.out.println("старт потока 1");
    Thread.yield();
    System.out.println("завершение 1");
   }
  }.start(); 
// запуск потока
  new
 Thread() {
   public
 void run() {
    System.out.println("старт потока 2");
    System.out.println("завершение 2");
   }
  }.start();
 }
}
В результате может быть выведено:
старт потока 1
старт потока 2
завершение 2
завершение 1
Активизация метода yield() в коде метода run() первого объекта потока приве-
дет к тому, что, скорее всего, первый поток будет остановлен на некоторый квант 
времени, что даст возможность другому потоку запуститься и выполнить свой код.
Потоки–демоны
Потоки-демоны используются для работы в фоновом режиме вместе с про-
граммой, но не являются неотъемлемой частью логики программы. Если ка-
кой-либо процесс может выполняться на фоне работы основных потоков вы-
полнения и его деятельность заключается в обслуживании основных потоков 
приложения, то такой процесс может быть запущен как поток-демон. С помо-
щью метода setDaemon(boolean value), вызванного вновь созданным потоком 
до его запуска, можно определить поток-демон. Метод boolean isDaemon() по-
зволяет определить, является ли указанный поток демоном или нет.
// # 6 # запуск и выполнение потока-демона # SimpleThread.java # DaemonRunner.java
package
 by.bsu.daemons;
public class 
SimpleThread extends Thread {
 public
 void run() {
  try
 {
   if
 (isDaemon()) {
    System.out.println("старт потока-демона");
    Thread.sleep(10_000); // заменить параметр на 1

ПОТОКИ ВЫПОЛНЕНИЯ
297
   } 
else 
{
    System.out.println("старт обычного потока");
   }
  } 
catch
 (InterruptedException e) {
   System.err.print(e);
  } 
finally
 {
   if
 (!isDaemon()) {
    System.out.println("завершение обычного потока");
   } else {
    System.out.println("завершение потока-демона");
   }
  }
 }
}
package
 by.bsu.daemons;
public
 class DaemonRunner {
 public
 static void main(String[ ] args) {
 
 
SimpleThread usual = new SimpleThread();
 
 
SimpleThread daemon = new SimpleThread();
  daemon.setDaemon(true);
  daemon.start();
  usual.start();
  System.out.println("последний оператор main");
 }
}
В результате компиляции и запуска, возможно, будет выведено:
последний оператор main
старт потока-демона
старт обычного потока
завершение обычного потока
Поток-демон (из-за вызова метода sleep(10000)) не успел завершить выпол-
нение своего кода до завершения основного потока приложения, связанного 
с методом main(). Базовое свойство потоков-демонов заключается в возможно-
сти основного потока приложения завершить выполнение потока-демона (в от-
личие от обычных потоков) с окончанием кода метода main(), не обращая вни-
мания на то, что поток-демон еще работает. Если уменьшать время задержки 
потока-демона, то он может успеть завершить свое выполнение до окончания 
работы основного потока.
Потоки и исключения
В процессе функционирования потоки являются в общем случае независи-
мыми друг от друга. Прямым следствием такой независимости будет коррект-
ное продолжение работы потока main после аварийной остановки запущенно-
го из него потока после генерации исключения.

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
298
/* # 7 # генерация исключения в созданном потоке # ExceptThread.java # 
ExceptionThreadDemo.java */
package
 by.bsu.thread;
public class
 ExceptThread extends Thread {
 
public
 void run() {
  boolean
 flag = true;
  if
 (flag) {
   throw new RuntimeException();
  }
  System.out.println("end of ExceptThread");
 }
}
package
 by.bsu.thread;
public
 class ExceptionThreadDemo {
 
public
 static void main(String[ ] args) throws InterruptedException {
  new ExceptThread().start();
  Thread.sleep(1000);
  System.out.println("end of main");
 
}
}
Основной  поток  избавлен  от  необходимости  обрабатывать  исключения 
в порожденных потоках.
В данной ситуации верно и обратное: если основной поток прекратит свое 
выполнение из-за необработанного исключения, то это не скажется на работо-
способности порожденного им потока.
/* # 8 # генерация исключения в потоке main # SimpleThread.java # 
ExceptionMainDemo.java */
package
 by.bsu.thread;
public class
 SimpleThread extends Thread {
 
public
 void run() {
  try {
   Thread.sleep(1000);
  } 
catch
 (InterruptedException e) {
   System.err.print(e);
  }
  System.out.println("end of SimpleThread");
 }
}
package
 by.bsu.thread;
public
 class ExceptionMainDemo {
 
public
 static void main(String[ ] args) {
  new SimpleThread().start();
  System.out.println("end of main with exception");
  throw new RuntimeException();
 }
}

ПОТОКИ ВЫПОЛНЕНИЯ
299
Атомарные типы и модификатор volatile
Все данные приложения находятся в основном хранилище данных. При за-
пуске нового потока создается копия хранилища и именно ею пользуется этот 
поток. Изменения, произведенные в копии, могут не сразу находить отражение 
в основном хранилище, и наоборот. Для получения актуального значения сле-
дует прибегнуть к синхронизации. Наиболее простым приемом будет объявле-
ние поля класса с модификатором volatile. Данный модификатор вынуждает 
потоки  производить  действия  по  фиксации  изменений  достаточно  быстро. 
То есть другой заинтересованный поток, скорее всего, получит доступ к уже 
измененному значению. Для базовых типов до 32 бит этого достаточно. При 
использовании со ссылкой на объект — синхронизировано будет только значе-
ние  самой  ссылки,  а  не  объект,  на  который  она  ссылается.  Синхронизация 
ссылки будет эффективной в случае, если она указывает на перечисление, так 
как  все  элементы  перечисления  существуют  в  единственном  экземпляре. 
Решением проблемы с доступом к одному экземпляру из разных потоков явля-
ется блокирующая синхронизация. Модификатор volatile обеспечивает небло-
кирующую синхронизацию.
Существует целая группа классов пакета java.util.concurrent.atomic, обеспе-
чивающая неблокирующую синхронизацию. Атомарные классы созданы для 
организации неблокирующих структур данных. Классы атомарных перемен-
ных AtomicIntegerAtomicLongAtomicReference и др. расширяют нотацию 
volatile значений, полей и элементов массивов. Все атомарные классы являют-
ся изменяемыми в отличие от соответствующих им классов-оболочек. При ре-
ализации классов пакета использовались эффективные атомарные инструкции 
машинного уровня, которые доступны на современных процессорах. В некото-
рых ситуациях могут применяться варианты внутреннего блокирования.
Экземпляры классов, например, AtomicInteger и AtomicReference, предо-
ставляют доступ и разного рода обновления к одной-единственной перемен-
ной соответствующего типа. Каждый класс также обеспечивает набор методов 
для этого типа. В частности, класс AtomicInteger — атомарные методы инкре-
мента и декремента. Инструкции при доступе и обновлении атомарных пере-
менных, в общем, следуют правилам для volatile.
Не следует классы атомарных переменных использовать как замену соот-
ветствующих классов-оболочек.
Пусть  имеется  некоторая  торговая  площадка,  представленная  классом 
Market,  работающая  в  непрерывном  режиме  и  информирующая  о  разнона-
правленных  изменениях  биржевого  индекса  (поле  index  типа  AtomicLong
дважды за один цикл с интервалом до 500 миллисекунд. Изменения поля index 
фиксируются  методом  addAndGet(long  delta)  атомарного  добавления  пере-
данного значения к текущему.

ИСПОЛЬЗОВАНИЕ КЛАССОВ И БИБЛИОТЕК
300
/* # 9 # класс с атомарным полем # Market.java */
package
 by.bsu.market;
import
 java.util.Random;
import
 java.util.concurrent.atomic.AtomicLong;
public
 class Market extends Thread {
 
private
 AtomicLong index;
 
public
 Market(AtomicLong index) {
  this.index = index;
 }
 

Достарыңызбен бөлісу:
1   ...   14   15   16   17   18   19   20   21   22




©engime.org 2024
әкімшілігінің қараңыз

    Басты бет