Московский государственный университет имени М.В.Ломоносова
Опубликован: 18.09.2006 | Доступ: свободный | Студентов: 1878 / 119 | Оценка: 4.32 / 3.36 | Длительность: 27:14:00
ISBN: 978-5-9556-0067-3
Лекция 11:

Основные конструкции языков Java и C# (продолжение)

Средства создания многопоточных программ

Как в 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 (см. ниже).

Владислав Нагорный
Владислав Нагорный

Подскажите, пожалуйста, планируете ли вы возобновление программ высшего образования? Если да, есть ли какие-то примерные сроки?

Спасибо!

Лариса Парфенова
Лариса Парфенова

1) Можно ли экстерном получить второе высшее образование "Программная инженерия" ?

2) Трудоустраиваете ли Вы выпускников?

3) Можно ли с Вашим дипломом поступить в аспирантуру?