Многопоточные приложения в

Download Report

Transcript Многопоточные приложения в

Многопоточные приложения
в Java
Лекция №8
Многопоточность
Многопоточность операционной системы –
возможность одновременного выполнения более
чем одной программы.
 Число одновременно выполняющихся процессов
не ограничено количеством процессоров.
 Многопоточные программы расширяют идею
многозадачности. Индивидуальные приложения
могут выполнять множество задач в одно и то же
время.
 Каждая задача называется потоком – thread.

Многопоточность

Существенная разница между многими
процессами и многими потоками
заключается в следующем:

каждый процесс имеет собственный набор
переменных, потоки могут разделят одни и те
же данные.
Потоки являются более «легковесными»,
чем процессы.
 Пример многопоточных приложений –
браузер, web-сервер, программы с
графическим пользовательским
интерфейсом.

Что такое потоки

Рассмотрим программу, которая не
использует средства многопоточности.



Программа анимирует прыгающий мяч.
При нажатии кнопки start программа выбросит мяч из
левого верхнего угла.
Обработчик кнопки вызывает метод addBall(), который
содержит цикл из 1000 движений. Каждый вызов move()
перемещает мяч на небольшое расстояние.
Ball ball = new Ball();
panel.add(ball);
Статический метод класса Thread.
for (int i = 1; i <= STEPS; i++)
Не создает нового потока, а приостанавливает
{ ball.move(panel.getBounds());
действие текущего
panel.paint(panel.getGraphics());
Thread.sleep(DELAY);
}
Программный код
// Класс кружок
import java.awt.geom.*;
public class Ball {
private static final int XSIZE = 15;
private static final int YSIZE = 15;
private double x = 0;
private double y = 0;
private double dx = 1;
private double dy = 1;
public void move(Rectangle2D bounds)
{ x += dx; y += dy;
if (x < bounds.getMinX())
{ x = bounds.getMinX(); dx = -dx;}
…….. // проверки на достижение границ
}
// Панель для хранения и отображения
//экземпляров класса Ball
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class BallComponent extends JPanel{
ArrayList<Ball> balls = new ArrayList<Ball>();
public void add(Ball b)
{ balls.add(b);
}
public Ellipse2D getShape()
{return new Ellipse2D.Double(x,y,XSIZE,YSIZE); }
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g); // очистка экрана
Graphics2D g2 = (Graphics2D) g;
for (Ball b : balls) {g2.fill(b.getShape());}
}
Программный код
// Класс фрейм, на котором все размещается
class BounceFrame extends JFrame{
private BallComponent comp;
public static final int STEPS = 1000;
public static final int DELAY = 3;
public void addButton(Container c, String title,
ActionListener listener)
{ JButton button = new JButton(title);
c.add(button);
button.addActionListener(listener);}
public BounceFrame() {
setSize(450, 350);
setTitle("Bounce");
comp = new BallComponent();
add(comp, BorderLayout.CENTER);
JPanel buttonPanel = new JPanel();
addButton(buttonPanel, "Start", new ActionListener()
{public void actionPerformed(ActionEvent event)
{addBall();}
});
public void addBall(){
try{
Ball ball = new Ball();
comp.add(ball);
for (int i = 1; i <= STEPS; i++) {
ball.move(comp.getBounds());
comp.paint(comp.getGraphics());
Thread.sleep(DELAY);}
}catch (InterruptedException e){;}
}}
addButton(buttonPanel, "Close", new ActionListener()
{ public void actionPerformed(ActionEvent event)
{System.exit(0);}
});
add(buttonPanel, BorderLayout.SOUTH);
}
public class Bounce{
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable(){
public void run(){
JFrame frame = new BounceFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_
frame.setVisible(true);}});
}
}
Процедура запуска задачи в
отдельном потоке

Поместить код задачи в метод run() класса,
реализующего интерфейс Runnable
class MyRunnable implements Runnable{
public void run(){
код задачи}
}

Сконструировать объект вашего класса
Runnable r = new MyRunnable();

Сконструировать объект Thread из Runnable
Thread t = new Thread(r);

Запустить поток
t.start();
Модификация для добавления
многопоточности
Модифицируем код так, что бы каждый круг
двигался в своем потоке.
 Чтобы поместить программу в отдельный
поток, необходимо реализовать (добавить
новый) класс BallRunnable и поместить код
анимации в метод run().
 Классы Ball, BallComponent, Bounce
остаются неизменными.
 В классе BounceFrame модифицируется
лишь метод addBall().

Модификация программного кода
// Новый класс, реализующий интерфейс
class BallRunnable implements Runnable {
private Ball ball;
private Component component;
public static final int STEPS = 1000;
public static final int DELAY = 5;
public BallRunnable(Ball aBall,Component aC)
{ ball = aBall;
component = aC;}
public void run()
{ try
{
for (int i = 1; i <= STEPS; i++){
ball.move(component.getBounds());
component.repaint();
Thread.sleep(DELAY);}
}
catch (InterruptedException e){}
}
}
//метод addBall до «многопоточности»
public void addBall(){
try{
Ball ball = new Ball();
comp.add(ball);
for (int i = 1; i <= STEPS; i++) {
ball.move(comp.getBounds());
comp.paint(comp.getGraphics());
Thread.sleep(DELAY);}
}catch (InterruptedException e){;}
}
//метод addBall после введения
// «многопоточности»
public void addBall(){
Ball ball = new Ball();
comp.add(ball);
Runnable r = new BallRunnable(b, comp);
Thread t = new Thread(r);
t.start();
}
Прерывание потоков



Поток прерывается, когда его метод run()
возвращает управление, выполнив оператор return,
после последнего оператора или в случае
возникновения исключения.
Для принудительного прерывания вызовом метода
interrupt() выставляется статус прерывания
(interrupted status). Каждый поток периодически
проверяет этот статус.
Для проверки установки статуса прерывания
применяется статический метод isInterrupted():
while (!Thread.currentThread().isInterrupted() && есть еще работа){
выполнять работу}
Состояние потока
Существует 6 состояний потока.
 Новый. Как только поток был создан
операцией new, он находится в состоянии
“новый”.
 Работоспособный. Как только вызывается
метод start, поток оказывается в
работоспособном состоянии.
Работоспособный поток может в данный
момент выполняться, а может и нет
(зависит от ОС, поэтому он не называется
работающим).
Состояние потока
Когда поток заблокирован или находится в состоянии
ожидания, он временно не активен. Он не
выполняет никакого кода, потребляет минимум
ресурсов.
 Блокированный. Когда поток пытается получить
внутренний объект блокировки, он становится
блокированным. Поток разблокируется, когда все
остальные потоки освобождают объект блокировки
и планировщик потоков позволяет захватить его.
 Ожидание. Когда поток ожидает другого потока для
уведомления планировщика о наступлении
некоторого условия, он входит в состояние
ожидания. Разница между блокированным и
ожидающим на практике не велика.
Состояние потока
Временное ожидание. Поток входит в это
состояние вызовом некоторых методов,
имеющих параметр таймаута.
 Завершенный поток. Поток завершается по
одной из следующих причин:




при нормальном выходе из run()
неперехваченное исключение прерывает метод
run()
Можно уничтожить поток, вызвав метод
stop(), который генерирует ошибку
ThreadDeath. Но данный метод не
рекомендуется к использованию.
Диаграмма
состояний
потока
Свойства потока. Приоритет
Поток наследует приоритет потока, который его
создал.
 Метод setPriority() устанавливает приоритет
между MIN_PRIORITY (равен 1) и MAX_PRIORITY
(равен 10). NORM_PRIORITY равен 5.
 Когда планировщик потоков выбирает поток для
выполнения, он предпочитает потоки с более
высоким приоритетом (вытесняющее
планирование).
 Приоритеты потоков в значительной мере
зависимы от системы. В Windows 7
приоритетов, в Sun – приоритеты игнорируются!

Потоки-демоны
Обычный поток можно превратить в
поток-демона вызовом метода
setDemon(true).
 Демон – поток, основное назначение
которого - служить другим.
 Примеры – поток таймера,
отсчитывающего тики, очистка кэша…

Синхронизация
В практических многопоточных
приложениях часто необходимо двум или
более потокам разделить доступ к одним и
тем же данным.
 В такой ситуации возникает ошибка –
состояние гонки.
 Чтобы избежать этого, необходимо
синхронизировать доступ!
 Пример состояния гонок на следующем
слайде

Пример состояния гонок. Банк
Код примера
public class UnsynchBankTest{
public static final int NACC = 100;
public static final double INIT_BAL = 1000;
public static void main(String[] args){
Bank b = new Bank(NACC,INIT_BAL);
int i;
for (i = 0; i < NACC; i++){
TransferRunnable r = new
TransferRunnable(b, i, INIT_BAL);
Thread t = new Thread(r);
t.start();} }
}
public class Bank{
private final double[] accounts;
public Bank(int n, double initialBalance){
accounts = new double[n];
for (int i = 0; i < accounts.length; i++)
accounts[i] = initialBalance; }
public void transfer(int from, int to, double amount){
if (accounts[from] < amount) return;
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d",
mount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n",
getTotalBalance());
}
public double getTotalBalance() {
double sum = 0;
for (double a : accounts)
sum += a;
return sum; }
public int size(){ return accounts.length;}}
Продолжение кода примера
public class TransferRunnable implements Runnable{
private Bank bank;
private int fromAccount;
private double maxAmount;
private int DELAY = 10;
public TransferRunnable(Bank b, int from, double max){
bank = b;
fromAccount = from;
maxAmount = max; }
public void run(){
try{
while (true){
int toAccount = (int) (bank.size() * Math.random());
double amount = maxAmount * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (DELAY * Math.random())); }
}
catch (InterruptedException e){ }
}
}
Объяснение состояния гонок


Предположим, что два потока пытаются
выполнить одновременно инструкцию
account[to] += amount
Операция не атомарна!
1.
2.
3.

загрузить accounts[to] в регистр
добавить amount
перенести результат обратно в accounts[to]
Первый поток выполняет шаги 1,2, после чего
приостанавливается. Второй поток просыпается и
обновляет тот же элемент массива. Затем
просыпается первый поток и делает шаг3. В
результате шага 3 действия второго потока
уничтожаются.
Блокирующие объекты
Начиная с Java 5.0 существует 2
механизма для защиты блока кода от
параллельного доступа – synchronized
и ReentrantLock.
 Эскиз защиты выглядит следующим
образом:

myLock.lock(); // ReentrantLock
try{
критический раздел}
finally {
myLock.unlock();}
Пример
public void transfer(int from, int to, double amount){
bankLock.lock();
try{
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
}
finally{
bankLock.unlock();}}
….
private Lock bankLock=new ReentrantLock();
Объекты условий



Часто поток входит в критическую секцию для того,
чтобы обнаружить, что он не может работать, пока
не будет выполнено определенное условие.
В подобных случаях необходимо использовать
объект условия для управления потоками,
которые захватили блокировку, но не могут
выполнить полезную работу.
Возможна такая ситуация, когда вы сделали
проверку возможности выполнения операция
(баланс достаточен), а перед выполнением самой
операции поток заснул. После его активации
баланс уже может быть изменен и операцию по
переводу делать нельзя.
Объекты условий
public void transfer (int from, int to, double amount){
bankLock.lock();
try{
while (accounts[from]<amount){
ждать}
…
перевод
…
}
finally{
bankLock.unlock();}}


Объект блокировки может иметь один или более
ассоциированных с ним объектов условий.
Объект условия получается вызовом метода
newCondition()
Объекты условий



Если метод transfer() обнаружит, что средств на счете
недостаточно, то он вызовет метод await().
Теперь текущий поток деактивирован и отдаст блокировку.
Как только средств на счете станет достаточно, то другой
поток должен активировать первый поток вызовом метода
signallAll().
public void transfer (int from, int to, double amount){
bankLock.lock();
try{
while (accounts[from]<amount){
sufficientFunds.await();}
sufficientFunds.signalAll();
}
finally{
bankLock.unlock();}}
…
private Condition sufficientFunds;
sufficientFunds=bankLock.newCondition();

Благодарю за внимание!

Вопросы?