Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
[Назад]

Библиотека примеров приложений Java

Оглавление
Простейший пример
Создание двух потоков
Управление потоками
Спрайтовая анимация
Панели с двигающимся текстом
Бегущая строка с мерцанием
Устранение мерцания
Поток для записи в файл
Контроль за выводом в файл
Чтение с сервера Web

Назад Вперед

6.9. Контроль за выводом в файл

В примере создаются два потока, один из которых записывает в файл некоторые данные, а другой контролирует этот процесс. Пример демонстрирует синхронизацию потоков.

Исходный текст примера

Архив проекта для Java WorkShop 2.0

Немного теории

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

Тем не менее, в ряде случаев требуются более мощные средства синхронизации. В качестве такого средства вы можете использовать методы wait и notify.

Синхронизованный метод run потока может перейти в режим ожидания извещения о возникновении события, связанного с другим потоком, вызвав метод wait:

public synchronized void run()
{
  . . .
  try
  {
    this.wait();
  }
  catch(InterruptedException ie)
  {
    System.out.println(ie.toString());
  }  
  . . .
}    

Так как при работе этого метода возможно возникновение исключения InterruptedException, необходимо предусмотреть блок try-catch.

Для извещения ожидающего события поток должен вызвать метод notify, как это показано ниже:

. . .
synchronized(ioc)
{
  ioc.notify();
}
. . .

Такая операция должна выполняться синхронизированным методом либо внутри синхронизированного блока.

Описание примера

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

pic1.gif (4855 bytes)

Рис. 1. Контроль за процессом записи в файл

Рассмотрим исходный текст примера.

Главный класс приложения FileSynchro

Главный класс определен следующим образом:

import java.io.*;
import java.util.*;

public class FileSynchro
{
  . . .
}

В нем предусмотрено три поля:

int nBlocks = 0;
int i;
fileWriter ioThread;

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

Метод main класса FileSynchro

Этот метод получает управление сразу после запуска приложения.

Прежде всего он создает объект класса FileSynchro:

FileSynchro fs = new FileSynchro();

Далее у пользователя запрашивается количество записываемых блоков:

System.out.println(
  "*Multithread file writer*\r\n");
System.out.print(
  "Enter number of blocks: ");
fs.nBlocks = getKbdInt();

Записав полученное значение в поле fs.nBlocks, метод main создает объект класса fileWriter, созданного на базе класса Thread, и запускает его на выполнение:

fs.ioThread = new fileWriter(fs.nBlocks);
fs.ioThread.start();

Далее метод main дожидается завершения работы только что запущенного потока:

try
{
  fs.ioThread.join();
}
catch(InterruptedException ie)
{
  System.out.println(ie.toString());
}  
   
System.out.print(
  "All done!\r\nPress <Enter>...");
getKbdByte();

Здесь пока все делается таким же образом, что и в предыдущем примере. Посмотрим, однако, на исходный текст класса fileWriter.

Класс fileWriter

Как мы уже говорили, этот класс создан на базе класса Thread:

class fileWriter extends Thread
{
  . . .
}

В нем мы определили всего одно поле nBlocks:

int nBlocks;

Конструктор класса fileWriter записывает сюда количество блоков, которые необходимо сохранить в файле:

public fileWriter(int n)
{
  nBlocks = n;
}
Метод run класса fileWriter

Когда метод main главного класса приложения запускает объект fileWriter на выполнение, метод run, определенный в классе fileWriter, начинает работать в отдельном потоке.

Прежде всего он создает объект класса ioControl, который унаследован нами от класса Thread, и запускает его на выполнение:

int i;
DataOutputStream os;
    
ioControl ioc = new ioControl(this);
ioc.start();

Объект ioControl следит за процессом записи в файл и выводит контрольные сообщения на консоль.

Обратите внимание, что мы передаем конструктору ссылку на объект класса fileWriter. Эта ссылка нужна объекту ioControl чтобы "знать", какой объект контролировать.

Как осуществляется этот контроль?

После создания потока вывода, связанного с файлом, метод run класса fileWriter при записи каждого сотого блока посылает в объект ioc класса ioControl извещение, вызывая для этого метод notify:

try
{
  os = new DataOutputStream(
    new FileOutputStream("testfile.txt"));
        
  for(i = 0; i < nBlocks; i++)
  {
    os.writeBytes("Test string\r\n");
        
    if(i%100 == 0)
    {
      synchronized(ioc)
      {
        ioc.setPosition(i);
        ioc.notify();
      }  
    }
  }
  . . .

Перед извещением метод run передает объекту ioc номер текущего записанного блока, вызывая для этого метод setPosition, определенный в классе ioControl.

После завершения цикла записи в файл мы передаем объекту ioc номер последнего записанного блока, посылаем извещение, а затем завершаем поток:

synchronized(ioc)
{
  ioc.setPosition(i);
  ioc.notify();
  ioc.end();
  ioc.join();
}

После завершения мы ждем, пока поток действительно перестанет работать, вызывая метод join.

Класс ioControl

Этот класс создан на базе класса Thread:

class ioControl extends Thread
{
  . . .
}

Поле nPosition хранит последний номер записанной строки, полученный от объекта класса fileWriter:

int nPosition;

В поле ioThread находится ссылка на поток, выполняющий запись в файл:

Thread ioThread;

И, наконец, в поле end хранится флаг завершения работы контролирующего потока:

static boolean end = false;
Конструктор класса ioControl

В качестве единственного параметра конструктору класса ioControl передается ссылка на объект, выполняющий запись в файл. Эта ссылка сохраняется в поле ioThread:

public ioControl(Thread th)
{
  ioThread = th;
}
Метод setPosition

Этот метод просто сохраняет в поле nPosition номер блока, записанного в файл:

public void setPosition(int i)
{
  nPosition = i;
}
Метод end

Когда поток, выполняющий запись в файл, хочет завершить контролирующий поток, он вызывает метод end:

public void end()
{
  end = true;
}

Данный метод устанавливает флаг end, проверяемый методом run.

Метод run

Метод run запускает цикл, условием завершения которого является установка флага end:

public synchronized void run()
{
  while(!end)
  {
    . . .
  }
}

Внутри цикла выполняется ожидание извещения от потока, занятого записью данных в файл:

try
{
  this.wait();
}
catch(InterruptedException ie)
{
  System.out.println(ie.toString());
}  

Когда такое извещение возникает, метод run класса ioControl выводит на консоль номер текущего блока:

System.out.println("String = " + nPosition); 

Назад Вперед

[Назад]