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

на главную

Жанры

Программирование на Java

Вязовик Н.А.

Шрифт:

Кроме метода 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, были получены следующие данные, которые подтверждают сделанные выводы:

494 9498 8

А если убрать задержку перед анализом результатов, то получаемые данные радикально меняются:

0 3 997

Видимо, потоки просто не успевают отработать.

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

Для более глубокого понимания принципов многопоточной работы в Java рассмотрим организацию памяти в виртуальной машине для нескольких потоков.

Хранение переменных в памяти

Виртуальная машина поддерживает основное хранилище данных (main storage), в котором сохраняются значения всех переменных и которое используется всеми потоками. Под переменными здесь понимаются поля объектов и классов, а также элементы массивов. Что касается локальных переменных и параметров методов, то их значения не могут быть доступны другим потокам, поэтому они не представляют интереса.

Для каждого потока создается его собственная рабочая память (working memory), в которую перед использованием копируются значения всех переменных.

Рассмотрим основные операции, доступные для потоков при работе с памятью:

* use – чтение значения переменной из рабочей памяти потока;

* assign – запись значения переменной в рабочую память потока;

* read – получение значения переменной из основного хранилища;

* load – сохранение значения переменной, прочитанного из основного хранилища, в рабочей памяти;

* store – передача значения переменной из рабочей памяти в основное хранилище для дальнейшего хранения;

* write – сохраняет в основном хранилище значение переменной, переданной командой store.

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

Поток, работая с переменной, регулярно применяет команды use и assign для использования ее текущего значения и присвоения нового. Кроме того, должны осуществляться действия по передаче значений в основное хранилище и из него. Они выполняются в два этапа. При получении данных сначала основное хранилище считывает значение командой read, а затем поток сохраняет результат в своей рабочей памяти командой load. Эта пара команд всегда выполняется вместе именно в таком порядке, т.е. нельзя выполнить одну, не выполнив другую. При отправлении данных сначала поток считывает значение из рабочей памяти командой store, а затем основное хранилище сохраняет его командой write. Эта пара команд также всегда выполняется вместе именно в таком порядке, т.е. нельзя выполнить одну, не выполнив другую.

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

Последовательность команд подчиняется следующим правилам:

* все действия, выполняемые одним потоком, строго упорядочены, т.е. выполняются одно за другим;

* все действия, выполняемые с одной переменной в основном хранилище памяти, строго упорядочены, т.е. следуют одно за другим.

Поделиться:
Популярные книги

Магия камня

Назимов Константин Геннадьевич
6. Травник
Фантастика:
фэнтези
6.00
рейтинг книги
Магия камня

Братья Карамазовы

Достоевский Федор Михайлович
Проза:
классическая проза
русская классическая проза
9.24
рейтинг книги
Братья Карамазовы

Изгой Проклятого Клана. Том 2

Пламенев Владимир
2. Изгой
Фантастика:
попаданцы
аниме
фэнтези
фантастика: прочее
5.00
рейтинг книги
Изгой Проклятого Клана. Том 2

Найденыш

Шмаков Алексей Семенович
2. Светлая Тьма
Фантастика:
юмористическое фэнтези
городское фэнтези
аниме
5.00
рейтинг книги
Найденыш

Телохранитель Генсека. Том 2

Алмазный Петр
2. Медведев
Фантастика:
попаданцы
альтернативная история
6.25
рейтинг книги
Телохранитель Генсека. Том 2

Горменгаст (переводчик Ильин)

Пик Мервин
2. Горменгаст
Фантастика:
социально-философская фантастика
5.00
рейтинг книги
Горменгаст (переводчик Ильин)

Газлайтер. Том 21

Володин Григорий Григорьевич
21. История Телепата
Фантастика:
боевая фантастика
аниме
попаданцы
5.25
рейтинг книги
Газлайтер. Том 21

Третий. Том 3

INDIGO
Вселенная EVE Online
Фантастика:
боевая фантастика
космическая фантастика
попаданцы
5.00
рейтинг книги
Третий. Том 3

Ратник

Ланцов Михаил Алексеевич
3. Помещик
Фантастика:
альтернативная история
7.11
рейтинг книги
Ратник

Локки 5. Потомок бога

Решетов Евгений Валерьевич
5. Локки
Фантастика:
юмористическое фэнтези
аниме
фэнтези
5.00
рейтинг книги
Локки 5. Потомок бога

Газлайтер. Том 6

Володин Григорий Григорьевич
6. История Телепата
Фантастика:
попаданцы
альтернативная история
аниме
5.00
рейтинг книги
Газлайтер. Том 6

Дважды одаренный. Том VIII

Тарс Элиан
8. Дважды одаренный
Фантастика:
боевая фантастика
альтернативная история
аниме
попаданцы
5.00
рейтинг книги
Дважды одаренный. Том VIII

Виконт. Книга 1. Второе рождение

Юллем Евгений
1. Псевдоним `Испанец`
Фантастика:
фэнтези
боевая фантастика
попаданцы
6.43
рейтинг книги
Виконт. Книга 1. Второе рождение

Глава рода

Шелег Дмитрий Витальевич
5. Живой лёд
Фантастика:
боевая фантастика
6.55
рейтинг книги
Глава рода