Рина Сергеева
29 ноября 2018 г. 0:58

Несколько способов синхронизации процессов и потоков в Java

Предлагаю рассмотреть несколько способов синхронизации потоков и процессов, наиболее часто используемых в Java. Они отличаются реализацией и вариантами использования. Мы рассмотрим все методы на интересных примерах.

Критическая секция

Этот метод подходит вам, если:

  • параллельные потоки работают с общим ресурсом;
  • требуется синхронизация между потоками, а не процессами;

Этот метод синхронизации называется синхронизацией ресурсов («синхронизация «открыть — закрыть»). Идея этого метода заключается в том, что каждый объект в Java имеет связанный с ним монитор. Монитор — это своего рода инструмент для управления доступом к объекту.


Синхронизированный оператор используется для создания критической секции. Когда выполнение кода достигает синхронизированного оператора, монитор объекта блокируется. В момент блокировки монопольный доступ к блоку кода имеет только один поток, выполнивший блокировку. После завершения работы блока кода объектный монитор освобождается и становится доступным для других потоков.

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

Пример

Представьте, как работает розничный интернет-магазин. После того, как один из покупателей положил товар в корзину, следует пересчитать оставшийся товар. Только после этого другой покупатель сможет положить в корзину желаемое количество товара. После этого купите их.

  1. public class Program {
  2.  
  3. private static final Boolean key = true; //the object will be used for synchronization
  4. //it must be final
  5. private static Integer amountOfGoods = 20;
  6.  
  7.  
  8. public static class Buyer implements Runnable {
  9.  
  10. @Override
  11. public void run() {
  12. final int goodsInTheShoppingCart = (int) (1 + Math.random() * amountOfGoods); //the number of goods that the buyer will put in the basket
  13. synchronized (key) {
  14. if (amountOfGoods - goodsInTheShoppingCart >= 0) {
  15. System.out.println("The " + Thread.currentThread().getName() + " placed " + goodsInTheShoppingCart + " items in the basket.");
  16. if ((amountOfGoods = amountOfGoods - goodsInTheShoppingCart) < 0) {
  17. System.out.println("Whoops! The product is out of stock, but the " + Thread.currentThread().getName() + " didn't know about it.");
  18. return;
  19. } else {
  20. System.out.println("There are " + amountOfGoods + " items left in the store.");
  21. }
  22. } else {
  23. System.out.println("The " + Thread.currentThread().getName() + " is notified that the goods are over.");
  24. return;
  25. }
  26. }
  27. System.out.println("The " + Thread.currentThread().getName() + " made a purchase.");
  28. }
  29. }
  30.  
  31. public static void main(String[] args) {
  32.  
  33. for (int i = 1; i <= 5; i++) {
  34. Thread t = new Thread(new Buyer()); // create five threads
  35. t.setName("buyer " + i);
  36. System.out.println("The " + t.getName() + " went to the store website.");
  37. t.start();
  38. }
  39. }
  40. }

Работа программы до использования блока синхронизации

Работа программы до использования блока синхронизации

Мы видим, что потоки выполняют программный код случайным образом. Из-за этого «покупатели» складывают товар в корзины, но остаются без товара.

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

Work of the program using the synchronization unit

Теперь программа работает именно так, как мы и ожидали. Большой!

Интерфейсный канал

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

Канал представляет собой открытое соединение с объектом, таким как аппаратное устройство, файл, сетевой сокет или программный компонент, который способен выполнять одну или несколько различных операций ввода-вывода, например чтение или запись.
Канал либо открыт, либо закрыт. Канал открыт при создании и после закрытия остается закрытым. Как только канал закрыт, любая попытка вызвать для него операцию ввода-вывода вызовет исключение ClosedChannelException . Открыт ли канал, можно проверить, вызвав его методы isOpen() и tryLock(). ).

Java package.nio.channels содержит такие каналы, как:

  • AsynchronousChannel
  • ByteChannel
  • NetworkChannel
  • FileChannel
  • InterruptibleChannel
  • MulticastChannel and other.

Пример

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

Создадим два проекта, отличающихся только тем, что они будут записаны в файл — «Программа 1» или «Программа 2». Мы запускаем их одновременно.

  1. public class Program {
  2.  
  3.  
  4. public static void main(String[] args) throws InterruptedException, IOException {
  5.  
  6. File file = new File("C:\\text.txt");
  7. FileChannel fileChannel = new FileOutputStream(file, true).getChannel();
  8.  
  9. while(fileChannel.tryLock() == null){ // the process checks if the channel is free to write to the file
  10. Thread.sleep(10000);
  11. }
  12. try {
  13. String lineSeparator = System.getProperty("line.separator");
  14.  
  15. for (int i = 0; i < 10; i++) {
  16.  
  17. String newData = "The program 2 is recording." + lineSeparator;
  18. ByteBuffer buf = ByteBuffer.allocate(200);
  19. buf.put(newData.getBytes());
  20. buf.flip();
  21. fileChannel.write(buf);
  22. Thread.sleep(1000);
  23. }
  24.  
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. fileChannel.close();
  29. }
  30. }

Если бы мы использовали классический способ записи в файл с помощью FileWriter, мы бы нашли в файле что-то похожее.

Теперь посмотрим, что будет в файле, если мы будем использовать каналы.

Это именно то, что нам нужно! Процессы получают доступ к файлу один за другим.

События

Кроме синхронизации данных есть синхронизация по событиям. Когда одновременно запущенные потоки приостанавливаются до тех пор, пока какое-либо событие не будет инициировано другим потоком. Основными операциями для этого типа синхронизации являются ожидание и уведомление.

Это «ожидание-уведомление» синхронизации:

  • параллельные потоки ждут некоторого события;
  • один из потоков отвечает за возникновение события и уведомляет остальные потоки (один с помощью notify или все - notifyAll,) разблокируя их:
  • подходит только для синхронизации потоков, а не процессов;

Пример

Представим, как работает автоматическая система перезарядки оружия. Например, на складе всего 15 патронов. Магазин вмещает только 6. Стрелок хочет использовать все патроны.

  1. public class Program {
  2.  
  3. public static void main(String[] args) {
  4.  
  5. WeaponStore weaponStore = new WeaponStore();
  6. Arsenal arsenal = new Arsenal(weaponStore);
  7. Shooter shooter = new Shooter(weaponStore);
  8. new Thread(arsenal).start();
  9. new Thread(shooter).start();
  10. }
  11. }
  12.  
  13.  
  14. class WeaponStore {
  15.  
  16. private int cartridges = 0;
  17.  
  18. public synchronized void shot() { // synchronization of the method is required
  19. // because two threads will have access to it
  20. while (cartridges < 1) {
  21. try {
  22. wait();
  23. }
  24. catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. //was thrown out notify by another thread, then the cartridge is received
  29. cartridges--;
  30. System.out.println("Produced 1 shot." + " Cartridges in the store: " + cartridges);
  31. notify();
  32. }
  33.  
  34. public synchronized void reload(int arsenalCartriges) { // synchronization of the method is required
  35. // because two threads will have access to it
  36. while (cartridges >= 6) {
  37. try {
  38. wait();
  39. }
  40. catch (InterruptedException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. cartridges++;
  45. System.out.println("1 cartridge has been added to the store." + " Cartridges in stock: " + arsenalCartriges);
  46. notify();
  47. }
  48. }
  49.  
  50. class Arsenal implements Runnable{
  51.  
  52. private int cartridges = 15;
  53.  
  54. private WeaponStore weapon;
  55.  
  56. Arsenal(WeaponStore weapon){
  57. this.weapon = weapon;
  58. }
  59.  
  60. public void run(){
  61. for (int cartridges = 15; cartridges > 0; cartridges--) {
  62. weapon.reload(cartridges);
  63. }
  64. }
  65. }
  66.  
  67.  
  68. class Shooter implements Runnable{
  69.  
  70. private WeaponStore weapon;
  71.  
  72. Shooter(WeaponStore weapon){
  73. this.weapon = weapon;
  74. }
  75.  
  76. public void run(){
  77. for (int i = 1; i <= 15; i++) {
  78. weapon.shot();
  79. }
  80. }
  81. }

Результат программы

  1. 1 cartridge has been added to the store. Cartridges in stock: 15
  2. 1 cartridge has been added to the store. Cartridges in stock: 14
  3. 1 cartridge has been added to the store. Cartridges in stock: 13
  4. 1 cartridge has been added to the store. Cartridges in stock: 12
  5. 1 cartridge has been added to the store. Cartridges in stock: 11
  6. Produced 1 shot. Cartridges in the store: 4
  7. Produced 1 shot. Cartridges in the store: 3
  8. Produced 1 shot. Cartridges in the store: 2
  9. Produced 1 shot. Cartridges in the store: 1
  10. Produced 1 shot. Cartridges in the store: 0
  11. 1 cartridge has been added to the store. Cartridges in stock: 10
  12. 1 cartridge has been added to the store. Cartridges in stock: 9
  13. 1 cartridge has been added to the store. Cartridges in stock: 8
  14. 1 cartridge has been added to the store. Cartridges in stock: 7
  15. 1 cartridge has been added to the store. Cartridges in stock: 6
  16. 1 cartridge has been added to the store. Cartridges in stock: 5
  17. Produced 1 shot. Cartridges in the store: 5
  18. Produced 1 shot. Cartridges in the store: 4
  19. Produced 1 shot. Cartridges in the store: 3
  20. Produced 1 shot. Cartridges in the store: 2
  21. Produced 1 shot. Cartridges in the store: 1
  22. Produced 1 shot. Cartridges in the store: 0
  23. 1 cartridge has been added to the store. Cartridges in stock: 4
  24. 1 cartridge has been added to the store. Cartridges in stock: 3
  25. 1 cartridge has been added to the store. Cartridges in stock: 2
  26. 1 cartridge has been added to the store. Cartridges in stock: 1
  27. Produced 1 shot. Cartridges in the store: 3
  28. Produced 1 shot. Cartridges in the store: 2
  29. Produced 1 shot. Cartridges in the store: 1
  30. Produced 1 shot. Cartridges in the store: 0

Вывод

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

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

По статье задано0вопрос(ов)

1

Вам это нравится? Поделитесь в социальных сетях!

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь