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

Таким образом, необходимо синхронизировать действие нескольких потоков и убедиться, что только один поток может получить доступ к ресурсу в заданный момент времени. Это реализовано с помощью концепции, называемой мониторами. Каждый объект в Java связан с монитором, который поток может блокировать или разблокировать. Только один поток одновременно может удерживать блокировку на мониторе.

Язык программирования Java предоставляет очень удобный способ создания потоков и синхронизации их задач с помощью синхронизированных блоков. Вы храните общие ресурсы в этом блоке. Ниже приводится общая форма синхронизированного оператора.

Синтаксис

synchronized(objectidentifier) {
		// Доступ к общим переменным и другим общим ресурсам
}

Здесь objectidentifier – это ссылка на объект, блокировка которого связана с монитором, который представляет синхронизированный оператор. Теперь мы рассмотрим два примера, в которых мы выведем счетчик, используя два разных потока. Когда потоки не синхронизированы в Java, они выводят значение счетчика, которое не находится в последовательности, но если мы выводим счетчик, помещая внутрь блока synchronized(), он выводит счетчик последовательно для обоих потоков.

Пример многопоточности без синхронизации

Вот простой пример, который может или не может выводить значение счетчика последовательно, и каждый раз, когда мы его запускаем, он дает другой результат в зависимости от доступности процессора для потока.

Пример

class PrintDemo {
   public void printCount() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Счетчик   ---   "  + i );
         }
      } catch (Exception e) {
         System.out.println("Поток прерван.");
      }
   }
}

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   PrintDemo  PD;

   ThreadDemo( String name,  PrintDemo pd) {
      threadName = name;
      PD = pd;
   }
   
   public void run() {
      PD.printCount();
      System.out.println("Поток " +  threadName + " завершается.");
   }

   public void start () {
      System.out.println("Запуск " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {
   public static void main(String args[]) {

      PrintDemo PD = new PrintDemo();

      ThreadDemo T1 = new ThreadDemo( "Поток - 1 ", PD );
      ThreadDemo T2 = new ThreadDemo( "Поток - 2 ", PD );

      T1.start();
      T2.start();

      // ждем, пока потоки завершатся
         try {
         T1.join();
         T2.join();
      } catch ( Exception e) {
         System.out.println("Прерван");
      }
   }
}

Это дает разные результаты каждый раз, когда вы запускаете эту программу:

Запуск Поток - 1
Запуск Поток - 2
Счетчик --- 5
Счетчик --- 4
Счетчик --- 3
Счетчик --- 5
Счетчик --- 2
Счетчик --- 1
Счетчик --- 4
Поток Поток - 1 завершается.
Счетчик --- 3
Счетчик --- 2
Счетчик --- 1
Поток Поток - 2 завершается.

Пример многопоточности с синхронизацией

Вот тот же пример, который последовательно выводит значение счетчика, и каждый раз, когда мы его запускаем, он дает одинаковый результат.

Пример

class PrintDemo {
   public void printCount() {
      try {
         for(int i = 5; i > 0; i--) {
            System.out.println("Счетчик   ---   "  + i );
         }
      } catch (Exception e) {
         System.out.println("Поток прерван.");
      }
   }
}

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   PrintDemo  PD;

   ThreadDemo( String name,  PrintDemo pd) {
      threadName = name;
      PD = pd;
   }
   
   public void run() {
      synchronized(PD) {
         PD.printCount();
      }
      System.out.println("Поток " +  threadName + " завершается.");
   }

   public void start () {
      System.out.println("Запуск " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}

public class TestThread {

   public static void main(String args[]) {
      PrintDemo PD = new PrintDemo();

      ThreadDemo T1 = new ThreadDemo( "Поток - 1 ", PD );
      ThreadDemo T2 = new ThreadDemo( "Поток - 2 ", PD );

      T1.start();
      T2.start();

      // ждем, пока потоки завершатся
      try {
         T1.join();
         T2.join();
      } catch ( Exception e) {
         System.out.println("Прерван");
      }
   }
}

Это дает один и тот же результат каждый раз, когда вы запускаете эту программу:

Запуск Поток - 1
Запуск Поток - 2
Счетчик --- 5
Счетчик --- 4
Счетчик --- 3
Счетчик --- 2
Счетчик --- 1
Поток Поток - 1 завершается.
Счетчик --- 5
Счетчик --- 4
Счетчик --- 3
Счетчик --- 2
Счетчик --- 1
Поток Поток - 2 завершается.

Оглавление