Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
отчёт-философы.doc
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
301.57 Кб
Скачать

2.2. Описание основных классов

При решении задачи было разработано 3 класса показанные на диаграмме (рисунок 2.)

Рисунок 2. Диаграмма классов для решения задачи о обедающих философах.

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

/**

* Объект класса Fork (вилка) может быть захвачен одним потоком один раз.

* Следующие потоки, которые будут пытаться ухватить объект, будут поставлены в fifo очередь.

*/

public class Fork

{

/**

* очередь потоков, которые хотят заблокировать этот объект

*/

private Vector<Thread> queue = new Vector<>();

/**

* текущий владелец этой блокировки

*/

private Thread owner = null;

/**

* возвращает true если поток заблокирован, иначе - false

*/

public synchronized boolean locked()

{

return owner != null;

}

/**

* Запросить блокировку. Если блокировка доступна, то поток становится владельцем блокировки

* Иначе поток ожидает до тех пор, пока блокировка не освободится.

*/

public synchronized void lock()

{

Thread caller = Thread.currentThread();

if ( owner != null ) {

queue.addElement( caller );

while ( owner != null || caller != queue.firstElement() )

try { wait(); }

catch ( InterruptedException e ) {}

queue.removeElement( caller );

}

owner = caller;

}

/**

* Снять блокировку. Снять блокировку может только поток, который её изначально установил.

*/

public synchronized void unlock()

{

Thread caller = Thread.currentThread();

if ( caller == owner ) {

owner = null;

notifyAll();

}

}

}

У каждого из философов есть по два объекта Fork (показано на диаграмме классов при помощи композиции), которые он должен заблокировать (lock) прежде чем приступить к приёму пищи. Если он не может их заблокировать сейчас то он становиться в очередь и ждёт пока объект освободиться. Функции lock и unlock отмечены synchronized что обеспечивает гарантию того что в этот код может одновременной войти лишь один поток что исключит вариант одновременного захвата ресурса.

Класс Философ (Philosopher) моделирует поведение одного из философов. Так как каждый из философов живёт своей жизнью, то этот класс наследуется от Thread и может быть запущен и действовать независимо от других потоков (будет выполняться функция run). Приведём код класса Философ (некоторые участки кода опущены):

public class Philosopher extends Thread

{

//настройки времени размышления/кушания (верхние границы)

private int thinkTime = 7000;

private int eatTime = 5000;

/**

* номер философа

*/

private int number;

/**

* левая вилка

*/

private Fork leftFork;

/**

* Правая вилка

*/

private Fork rightFork;

/**

* Текущее состояние философа (думает, голоден, кушает)

*/

private int state;

/**

* запустить поток действий философа

*/

public void run()

{

//запускаем вечный цикл

while ( true ) {

//сначала философ думает

think();

//если он перестал думать значит он проголодался

//следовательно он хватает первую вилку

takeFirstFork();

//потом хватает вторую вилку

takeSecondFork();

//и кушает

eat();

//после чего кладёт столовые приборы на место и

//перечисленные действия повторяются опять putDownForks();

}

}

/**

* моделирование процесса размышлений философа

*/

public void think()

{

//меняем состояние

state = THINKING;

//используя генератор случайных чисел, считаем, сколько философ будет кушать

int time = (int)(thinkTime*(0.5 + Math.random()));

//поток засыпает на время пока философ кушает

try { sleep( time ); }

catch ( InterruptedException e ) {}

}

/**

* Позволяет философу ухватить первый столовый инструмент

*/

public void takeFirstFork()

{

//если номер философа число парное, то он хватает левую вилку первой

if ( number % 2 == 0 ) {

state = TAKING_FIRST_FORK_LEFT;

leftFork.lock();

}

//иначе он пытается сначала ухватить правую вилку

else {

state = TAKING_FIRST_FORK_RIGHT;

rightFork.lock();

}

}

/**

* Позволяет философу взять вторую вилку

*/

public void takeSecondFork()

{

if ( number % 2 == 0 ) {

state = TAKING_SECOND_FORK_RIGHT;

rightFork.lock();

}

else {

state = TAKING_SECOND_FORK_LEFT;

leftFork.lock();

}

}

/**

* Позволяет философу кушать

*/

public void eat()

{

state = EATING;

int time = (int)(eatTime*(0.5 + Math.random()));

try { sleep( time ); }

catch ( InterruptedException e ) {}

}

/**

* Позволяет философу положить вилки

*/

public void putDownForks()

{

leftFork.unlock();

rightFork.unlock();

}

}

Когда выполняется запуск потока (Thread.start()) вызывается функция run, которая уходит в вечный цикл и будет выполняться до тех пор, пока поток породивший поток Philosopher не завершиться. Функции think / eat предназначены для моделирования размышлений и приёма пищи соответственно. Особенностью класса Философ является то, что в зависимости от номера он хватает первой либо левую, либо правую вилку (функция takeFirstFork) чтобы исключить взаимоблокировку ресурса Fork с соседом.

Класс DinningPhilosopher предназначен для управления самой программой, именно он создаёт объекты Fork и Philosopher и помнит их адреса, чтобы после можно было к ним обратиться и проверить их состояние. На диаграмме классов при помощи композиции показано, что DinningPhilosopher включает в себя 5 объектов Fork и 5 объектов Philosopher и координирует их работу. Основные участки кода класса DinningPhilosopher представлены ниже.

public class DiningPhilosophers extends JFrame

{

static final long serialVersionUID = 1L;

//размеры окна

private int width;

private int height;

/**

* Количество философов

*/

public int nphils = 5;

/**

* Радиус стола, возле которого рисуются философы

*/

public int radius = 170;

/**

* Философы

*/

private Philosopher[] philosophers;

/**

* Вилки

*/

private Fork[] forks;

/**

* Изображения философов

*/

private Image[] philImages;

/**

* Изображения вилок

*/

private Image[] forkImages;

/**

* Изображение стола

*/

private Image[] tableImages;

public DiningPhilosophers(){

//инициализация приложения, загрузка изображений,

// запуск потоков, отображение окна

}

/**

* функция рисования окна

*/

public void paint( Graphics g )

{

// рисуем философов

for ( int i = 0; i < nphils; i++ ) {

double a = 2*i*Math.PI/nphils - Math.PI/2;

int k = Math.sin( a ) <= 0 ? 0 : 1;

Image image = null;

//в зависимости от состояния философа

//используем разные изображения

switch ( philosophers[i].state() ) {

//в зависимости от статуса рисуем соответствующее изображение философа

}

//вычисляем координату где нарисовать i-того философа относительно круга

int x = (int)(width/2 + radius*Math.cos( a ) - 50);

int y = (int)(height/2 + radius*Math.sin( a ) - 60);

//рисуем нашего философа

g.drawImage( image, x, y, this );

}

// рисуем вилки вокруг стола

for ( int i = 0; i < nphils; i++ ) {

Image image = forkImages[0];

if ( forks[i].locked() ) image = forkImages[1];

double a = (2*i-1)*Math.PI/nphils - Math.PI/2;

int x = (int)(width/2 + radius*Math.cos( a ) - 10);

int y = (int)(height/2 + radius*Math.sin( a ) - 25);

g.drawImage( image, x, y, this );

}

// рисуем стол

Image image = tableImages[0];

g.drawImage( image, width/2 - 80, height/2 - 40, this );

// перирисовываем всю область

repaint();

}

/**

* Загружает массив изображений

* @param name название изображения

* @param num номер изображения

*/

private Image[] loadImages( String name, int num )

{

//выделяем место

Image[] images = new Image[num];

//загружаем i-тую картинку

for ( int i = 0; i < num; i++ ) {

String filename = "img/" + name + i + ".gif";

images[i] = new ImageIcon(filename).getImage();

}

//возвращаем массив изображений

return images;

}

}

Объект DinningPhilosopher при его создании настраивает размеры окна и условия завершения приложения (EXIT ON CLOSE), а также загружает необходимые для работы изображения из файлов при помощи функции loadImages, создаёт объекты Fork / Philosopher и запускает 5 потоков чтобы философы начали действовать. Этот же объект и будет проверять статусы философов и перерисовывать их текущее состояние. Так как задача определения когда необходимо перерисовывать окно весьма сложная (не просто уследить за 5 потоками чтобы определить изменение состояния одного из них) то было принято решение перерисовывать окно постоянно при помощи вызова repaint() в конце paint. Это решение имеет как плюсы так и минусы, минусом является то что теперь есть проблемы с отображением каких-либо компонентов swing так как они банально не будут успевать перерисовываться (им нужно больше времени на отрисовку чем на рисование наших нескольких картинок), а также то что постоянная перерисовка будет съедать свободное время процессора пустыми циклами (аналогично плохой идее при WINAPI программированию написать в цикле обработки сообщений while(true) dispatch(event)) плюсом же этого решения является простота: не нужно городить огород с реализацией слушателей потоков философов и сложными алгоритмами определения времени перерисовки окна.