Основное различие между потоками и процессами состоит в том, что процессы защищены от воздействия друг на друга средствами операционной системы (каждый процесс выполняется в своем адресном пространстве). Использование потоков, лишенных подобной защиты, позволяет быстро запускать новые потоки и способствует их производительности. Однако здесь есть и отрицательный эффект – любой из потоков может получить доступ и даже внести изменения в данные, которые другой поток считает принадлежащими только ему. Решение этой проблемы состоит в синхронизации потоков. Ситуация, когда много потоков, обращающихся к некоторому общему ресурсу, начинают мешать друг другу, очень часта. Например, когда два потока записывают информацию в файл/объект/поток. Синхронизация кода реализуется двумя основными способами.
1. Если критическим участком является метод, то можно просто указать ключевое слово sychronized в объявлении метода, т.е.
synchronized void myMethod () { … }
Эквивалентный код можно представить в виде
void myMethod ()
{synchronized (this)
{…………….}}
2. Если нет доступа к классу, в котором объявлен метод, то для его синхронизации можно использовать следующий прием:
synchonized (object)
{ // операторы критического участка, в том числе и вызовы метода}
Здесь object – ссылка на объект, который нужно синхронизировать, т.е. на объект элементом которого является вызываемый метод. При синхронизации одного оператора фигурные скобки можно опускать.
В Java кроме использования блока synchonized разработаны и эффективные средства межпроцессового взаимодействия. Например, метод
public final void wait () throws InterruptedException;
осуществляет перевод вызывающего потока в режим ожидания, пока
некоторый другой поток не введет notify().
Существуют и другие варианты метода, например:
public final void wait (long timeout) throws InterruptedException;
осуществляет задержку на определенное время.
Метод
public final void notify();
«пробуждает» на том же объекте первый поток, который вызвал ожидание – wait();
Следующий метод
public final void notifyAll();
пробуждает все потоки, для которых вызван – wait() и первым будет выполняться поток с наибольшим приоритетом.
Рассмотрим несколько примеров программ с использованием потоков.
Пример 2.1 является модификацией примера апплета, приведенного в лабораторной работе №1 (см. пример 1.7). Листинг кода ниже демонстрирует, как можно нарисовать тот же домик с использованием потоков.
Пример 2.1
Листинг файла DrawHouseThreadApplet.java
import java.awt.*;
import java.applet.*;
public class DrawHouseThreadApplet extends Applet implements Runnable
{
int level = 0;
Thread t;
//функция инициализации апплета
public void init()
{
this.setBackground(Color. white);
t = new Thread(this);
t.start();
}
//функция перерисовки апплета
public void paint(Graphics g)
{
g.setColor(Color.DARK_GRAY);
if(level == 1)
{
g.drawLine(50, 150, 200, 50);
g.drawLine(200, 50, 350, 150);
g.drawLine(350, 150, 50, 150);
}
if(level == 2)
{
g.drawLine(50, 150, 200, 50);
g.drawLine(200, 50, 350, 150);
g.drawLine(350, 150, 50, 150);
g.drawRect(100, 150, 200, 200);
}
if(level == 3)
{
g.drawLine(50, 150, 200, 50);
g.drawLine(200, 50, 350, 150);
g.drawLine(350, 150, 50, 150);
g.drawRect(100, 150, 200, 200);
g.drawRect(170, 200, 60, 100);
}
if(level == 4)
{
g.drawLine(50, 150, 200, 50);
g.drawLine(200, 50, 350, 150);
g.drawLine(350, 150, 50, 150);
g.drawRect(100, 150, 200, 200);
g.drawRect(170, 200, 60, 100);
g.drawLine(200, 200, 200, 300);
g.drawLine(170, 250, 230, 250);
g.setColor(Color.MAGENTA);
g.drawString("Домик", 190, 30);
}
}
//запуск потока
public void run()
{
System.out.println("Run");
while(true)
{
level ++;
repaint();
try{
Thread.currentThread().sleep(3000);
}
catch(Exception ex){}
if(level == 4)
{
return;
}
}
}
}
Файл DrawHouseThreadApplet.html
<HTML>
<HEAD>
</HEAD>
<BODY bgcolor=white>
<CENTER>
<h1><font color=lightblue> <I>House</I></font></h1>
<APPLET
code = "DrawHouseThreadApplet.class"
width = "400"
height = "400"
>
</APPLET>
</CENTER>
</BODY>
</HTML>
Следующий пример демонстрирует управление приоритетами.
Пример 2.2
Листинг TryPriorThread.java
public class TryPriorThread extends Thread{
public TryPriorThread(String threadName){
super(threadName);
System.out.println("Thread '"+threadName+"' created!");
}
public void run(){
for(int i=0;i<10;i++){
System.out.println("Thread '"+getName()+"' "+i);
try{
sleep(1); //ожидать одну миллисекунду
}
catch(InterruptedException e){
System.out.print("Error:"+e);
}
}
}
public static void main(String [ ] args){
// создать три потока выполнения
Thread min_thr = new TryPriorThread("ThreadMin");
Thread max_thr = new TryPriorThread("ThreadMax");
Thread norm_thr = new TryPriorThread("ThreadNorm");
System.out.println("Starting threads...");
min_thr.setPriority(Thread.MIN_PRIORITY); //задать потоку
//минимальный приоритет
//задать потоку максимальный приоритет
max_thr.setPriority(Thread.MAX_PRIORITY);
//задать потоку нормальный приоритет
norm_thr.setPriority(Thread.NORM_PRIORITY);
min_thr.start(); // запустить первый поток
max_thr.start(); // запустить второй поток
norm_thr.start(); // запустить третий поток
}
}
Результат работы программы:
Thread 'ThreadMin' created!
Thread 'ThreadMax' created!
Thread 'ThreadNorm' created!
Starting threads...
Thread 'ThreadMax' 0
Thread 'ThreadNorm' 0
Thread 'ThreadMin' 0
Thread 'ThreadMax' 1
Thread 'ThreadMax' 2
Thread 'ThreadMax' 3
Thread 'ThreadMax' 4
Thread 'ThreadMax' 5
Thread 'ThreadMax' 6
Thread 'ThreadMax' 7
Thread 'ThreadMax' 8
Thread 'ThreadMax' 9
Thread 'ThreadNorm' 1
Thread 'ThreadMin' 1
Thread 'ThreadNorm' 2
Thread 'ThreadMin' 2
Thread 'ThreadNorm' 3
Thread 'ThreadMin' 3
Thread 'ThreadNorm' 4
Thread 'ThreadMin' 4
Thread 'ThreadNorm' 5
Thread 'ThreadMin' 5
Thread 'ThreadNorm' 6
Thread 'ThreadMin' 6
Thread 'ThreadNorm' 7
Thread 'ThreadMin' 7
Thread 'ThreadNorm' 8
Thread 'ThreadMin' 8
Thread 'ThreadNorm' 9
Thread 'ThreadMin' 9
Пример, демонстрирующий синхронизацию доступа к файлу.
Пример 2.3
Листинг SynchroThreads.java
import java.io.*;
public class SynchroThreads{
public static void main(String [ ] args){
SynchroFile sf= new SynchroFile(); //объект класса SynchroFile
FileThread ft1=new FileThread("FisrtThread",sf); //первый поток
FileThread ft2=new FileThread("SecondThread",sf); //второй поток
ft1.start(); //стартовать первый поток
ft2.start(); //стартовать второй поток
}
}
class FileThread extends Thread{
String str;
SynchroFile sf;
public FileThread(String str,SynchroFile sf){
this.str=str;
this.sf=sf;
}
public void run(){
for(int i=0;i<10;i++){
sf.writing(str,i);
}
}
}
class SynchroFile{
File f=new File("file.txt");
public SynchroFile(){
System.out.println("Object SynchroFile creating...");
try{
f.delete(); //удалить файл если он есть
f.createNewFile(); //создать новый файл
}
catch(IOException ioe){
ioe.printStackTrace();
}
}
public synchronized void writing(String str,int i){
try{
RandomAccessFile raf=new RandomAccessFile(f,"rw");
raf.seek(raf.length()); //переместить указатель в конец
System.out.print(str);
raf.writeBytes(str); //записать в файл
// на случайное значение приостанавить поток
Thread.sleep((long)(Math.random()*15));
raf.seek(raf.length()); //переместить указатель в конец
System.out.print("->"+i+" \n");
raf.writeBytes("->"+i+" \n"); //записать в файл
}
catch(IOException ioe){
ioe.printStackTrace();
}
catch(InterruptedException ie){
ie.printStackTrace();
}
notify(); //известить об окончании работы с методом
}
}
Работа программы будет выглядеть на экране следующим образом:
Object SynchroFile creating...
FisrtThread->0
SecondThread->0
FisrtThread->1
SecondThread->1
FisrtThread->2
SecondThread->2
FisrtThread->3
SecondThread->3
FisrtThread->4
SecondThread->4
FisrtThread->5
SecondThread->5
FisrtThread->6
SecondThread->6
FisrtThread->7
SecondThread->7
FisrtThread->8
SecondThread->8
FisrtThread->9
SecondThread->9
В каталоге приложения будет создан файл file.txt, дублирующий информацию, выведенную на экран.
Следующий пример демонстрирует применение потоков в апплете. Создается апплет, в разных потоках осуществляется движение строки, квадрата и овала, а также зарисовка фона апплета.
Пример 2.4
Листинг AppletThreadSample.java
import java.awt.*;
import java.applet.*;
//создать класс апплета, который реализует интерфейс Runnable
public class AppletThreadSample extends Applet implements Runnable{
private Thread T; //создать объект потока
//объявление переменных
private ShapeString m_ShapeString = null; //для строки
private ShapeOval m_ShapeOval = null; //для овала
private ShapeRect m_ShapeRect = null; //для квадрата
public void run() { //реализация метода run, точка входа в поток
setBackground(Color.yellow); //фон апплета зарисовывается желтым
while (true){ //бесконечный цикл
repaint(); //перерисовка апплета или вызов метода paint
try{
T.sleep(10); //приостановка апплета на 10 миллисекунл
}
catch (InterruptedException e){ }
}
}
public void init() { //метод инициализации апплета
T = new Thread(this); //создание потока и привязка его к текущему классу
T.start(); //запуск потока (вызывается run)
//создание объектов
m_ShapeString= new ShapeString();
m_ShapeOval= new ShapeOval();
m_ShapeRect= new ShapeRect();
}
public void paint(Graphics g) { //метод прорисовки апплета
//прорисовка строки
g.drawString("This is ShapeString",
m_ShapeString.x_String,m_ShapeString.y_String);
//прорисовка квадрата
g.setColor(Color.red);
g.drawRect(m_ShapeRect.x_Rect,m_ShapeRect.y_Rect,
m_ShapeRect.w_Rect,m_ShapeRect.h_Rect);
//прорисовка овала
g.setColor(Color.CYAN);
g.fillOval(m_ShapeOval.x_Oval,m_ShapeOval.y_Oval,
m_ShapeOval.w_Oval,m_ShapeOval.h_Oval);
}
//класс ShapeString реализующий интерфейс Runnable
class ShapeString implements Runnable{
Thread T;
int x_String, y_String; //координаты строки
public ShapeString(){ //конструктор
T = new Thread(this); //создание объекта Thread
//установление начальных координат строки
x_String=100; y_String=100;
T.start(); //запуск потока (вызов метода run)
}
public void run(){ //метод run
for(;;){
x_String+=15; //изменение координаты строки
try{
T.sleep(1000); //приостановка работы потока на 1000 миллисекунд
}
catch (InterruptedException e){}
}
}
}
//класс ShapeRect реализующий интерфейс Runnable
class ShapeRect implements Runnable{
Thread T;
int x_Rect,y_Rect,w_Rect,h_Rect; //координаты и размеры квадрата
public ShapeRect(){ //конструктор
T = new Thread(this); //создание объекта Thread
//установление начальных координат квадрата
x_Rect=350;y_Rect=50;w_Rect=100;h_Rect=100;
T.start();//запуск потока (вызов метода run)
}
public void run(){ //метод run
for(;;){
x_Rect-=15; //изменение координаты квадрата
try{
T.sleep(500); //приостановка работы потока на 1000 миллисекунд
}
catch (InterruptedException e){}
}
}
}
//класс ShapeOval реализующий интерфейс Runnable
class ShapeOval implements Runnable{
Thread T;
int x_Oval, y_Oval,w_Oval,h_Oval; //координаты и размеры овала
public ShapeOval(){ //конструктор
T = new Thread(this); //создание объекта Thread
//установление начальных координат овала
x_Oval=30; y_Oval=30;w_Oval=100;h_Oval=90;
T.start(); //запуск потока (вызов метода run)
}
public void run(){//метод run
for(;;){//изменение координат овала
x_Oval+=8;
y_Oval+=7;
try{
T.sleep(100); //приостановка работы потока на 100 миллисекунд
}
catch (InterruptedException e){ }
} } } }
Откомпилируйте программу. Не забудьте создать соответствующий
html -файл. Результат работы программы показан на рис. 2.2.
Рис. 2.2. Результат работы программы AppletThreadSample.java