Средства создания многопоточных программ
Как в Java, так и в C# возможно создание многопоточных приложений. Вообще говоря, каждая программа на этих языках представляет собой набор потоков (threads), выполняющихся параллельно. Каждый поток является исполняемым элементом, имеющим свой собственный поток управления и стек вызовов операций. Все потоки в рамках одного процесса (одной виртуальной машины Java или одного процесса среды .NET) имеют общий набор ресурсов, общую память, общий набор объектов, с которыми могут работать.
Каждый поток представляется в языке объектом некоторого класса ( java.lang.Thread в Java и System.Threading.Thread в C#). Для запуска некоторого кода в виде отдельного потока необходимо определить особую операцию в таком объекте и выполнить другую его операцию.
В Java это можно сделать двумя способами.
Первый — определить класс-наследник java.lang.Thread и перегрузить в этом классе метод public void run(). Этот метод, собственно, и будет выполняться в виде отдельного потока.
Другой способ — определить класс, реализующий интерфейс java.lang.Runnable и его метод void run(). После чего построить объект класса Thread на основе объекта только что определенного класса.
В обоих случаях для запуска выполнения потока нужно вызвать в объекте класса Thread (в первом случае — его наследника) метод void start().
|
В C# также можно использовать два способа. С помощью первого можно создать обычный поток, с помощью второго — поток, которому при запуске нужно передать какие-то данные.
Для этого нужно определить метод, который будет выполняться в рамках потока. Этот метод должен иметь тип результата void. Список его параметров в первом случае должен быть пустым, во втором — состоять из одного параметра типа object.
В первом варианте на основе этого метода создается делегат типа System.Threading.ThreadStart, во втором — типа System.Threading. ParameterizedThreadStart.
Этот делегат передается в качестве аргумента конструктору объекта класса System.Thread.
Поток запускается выполнением метода Start() у объекта класса Thread в первом случае, или метода Start(object) во втором.
|
class T extends Thread
{
int id = 0;
public T(int id)
{ this.id = id; }
public void run()
{
System.out.println
("Thread " + id + " is working");
}
}
public class A
{
public static void main(String[] args)
{
Thread th1 = new T(1),
th2 = new T(2),
th3 = new Thread(
new Runnable() {
public void run()
{
System.out.println
("Runnable is working");
}
});
th1.start();
th2.start();
th3.start();
}
}
|
using System;
using System.Threading;
class T
{
int id;
public T(int id)
{ this.id = id; }
public void m()
{
Console.WriteLine
("Thread " + id + " is working");
}
}
public class A
{
static void m()
{
Console.WriteLine
("Nonparameterized thread" +
" is working");
}
static void m(object o)
{
Console.WriteLine
("Thread with object " + o +
" is working");
}
public static void Main()
{
Thread th1 = new Thread(
new ThreadStart(m)),
th2 = new Thread(
new ThreadStart(new T(1).m)),
th3 = new Thread(
new ParameterizedThreadStart(m));
th1.Start();
th2.Start();
th3.Start(2);
}
}
|
При разработке приложений, основанных на параллельном выполнении нескольких потоков, большое значение имеют вопросы синхронизации работы этих потоков. Синхронизация позволяет согласовывать их действия и аккуратно передавать данные, полученные в одном потоке, в другой. И недостаточная синхронизация, и избыточная приводят к серьезным проблемам. При недостаточной синхронизации один поток может начать работать с данными, которые еще находятся в обработке у другого, что приведет к некорректным итоговым результатам. При избыточной синхронизации как минимум производительность приложения может оказаться слишком низкой, а в большинстве случаев
приложение просто не будет работать из-за возникновения тупиковых ситуаций (deadlocks), в которых два или более потоков не могут продолжать работу, поскольку ожидают друг от друга освобождения необходимых им ресурсов.
В обоих языках имеются конструкции, которые реализуют синхронизационный примитив, называемый монитором (monitor). Монитор представляет собой объект, позволяющий потокам "захватывать" и "отпускать" себя. Только один поток может "держать" монитор в некоторый момент времени — все остальные, попытавшиеся захватить монитор после его захвата этим потоком, будут приостановлены до тех пор, пока этот поток не отпустит монитор.
Для синхронизации используется конструкция, гарантирующая, что некоторый участок кода в каждый момент времени выполняется не более чем одним потоком. В начале этого участка нужно захватить некоторый монитор, в качестве которого может выступать любой объект ссылочного типа, в конце — отпустить его. Такой участок помещается в блок (или представляется в виде одной инструкции), которому предшествует указание объекта-монитора с ключевым словом synchronized в Java или lock в C#.
public class PingPong extends Thread
{
boolean odd;
PingPong (boolean odd)
{ this.odd = odd; }
static int counter = 1;
static Object monitor = new Object();
public void run()
{
while(counter < 100)
{
synchronized(monitor)
{
if(counter%2 == 1 && odd)
{
System.out.print("Ping ");
counter++;
}
if(counter%2 == 0 && !odd)
{
System.out.print("Pong ");
counter++;
}
}
}
}
public static void main
(String[] args)
{
Thread th1 = new PingPong (false),
th2 = new PingPong (true);
th1.start();
th2.start();
}
}
|
using System;
using System.Threading;
public class PingPong
{
bool odd;
PingPong (bool odd) { this.odd = odd; }
static int counter = 1;
static object monitor = new object();
public void Run()
{
while(counter < 100)
{
lock(monitor)
{
if(counter%2 == 1 && odd)
{
Console.Write("Ping ");
counter++;
}
if(counter%2 == 0 && !odd)
{
Console.Write("Pong ");
counter++;
}
}
}
}
public static void Main()
{
Thread th1 = new Thread(
new ThreadStart(
new PingPong(false).Run)),
th2 = new Thread(
new ThreadStart(
new PingPong(true).Run));
th1.Start();
th2.Start();
}
}
|
Кроме того, в Java любой метод класса может быть помечен как synchronized. Это значит, что не более чем один поток может выполнять этот метод в каждый момент времени в рамках объекта, если метод нестатический и в рамках всего класса, если он статический.
Такой модификатор эквивалентен помещению всего тела метода в блок, синхронизированный по объекту this, если метод нестатический, а если метод статический — по выражению this.getClass(), возвращающему объект, который представляет класс данного объекта.
В Java также имеется стандартный механизм использования любого объекта ссылочного типа в качестве монитора для создания более сложных механизмов синхронизации.
Для этого в классе java.lang.Object имеются методы wait(), приостанавливающие текущий поток до тех пор, пока другой поток не вызовет метод notify() или notifyAll() в том же объекте, или пока не пройдет указанное время. Все эти методы должны вызываться в блоке, синхронизированном по данному объекту.
Однако это механизм достаточно сложен в использовании и не очень эффективен. Для реализации более сложной синхронизации лучше пользоваться библиотечными классами из пакетов java.util.concurrent и java.util.concurrent.locks, появившихся в JDK версии 5 (см. ниже).
|
|