Пътуване за писане на ефективен многонишков код

Ако не сте запознати с многопоточността, вижте предишната ми статия, за да разберете как можете да я приложите към приложението си. Показва ви начини за лесно повишаване на производителността на вашето приложение.



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

Тази статия е началото на поредица, „Добре синхронизирани, но асинхронни“. В тази серия ще преминем през стъпки за изграждане на ефективно многонишково приложение. Всяка статия ще обхваща една техника за синхронизиране. Да започнем пътуването!

Bigtaurant отваря!

Боб е собственик на голям ресторант, Bigtaurant. С безброй клиенти, посещаващи всеки ден, Боб иска да създаде приложение за управление на ресторанта. Има толкова много неща, които се случват по едно и също време в Bigtaurant, така че Боб иска нишките да играят роля.

За да избегне бъркотия, Боб поставя всичко в synchronized блока, което прави всяка част от приложението безопасна за нишки. Това включва:

  1. Показване на меню
  2. Приемане на поръчки
  3. Готвене и сервиране
  4. Контрол на музиката
  5. Плащане

Щастието от стартирането на ново приложение не продължи дълго. Боб открива, че приложението изостава и само една от многото нишки се изпълнява в даден момент, оставяйки други нишки да чакат да приключи.

Грешките на Боб

Боб постави всичко в synchronized блока. Най-лесният начин да направите нещо безопасно за нишки е наистина да маркирате метода synchronized и е лесно да бъдете привлечени или изкушени. Твърде честото използване на synchronized, когато не е необходимо обаче, прави нишките безполезни.

Помислете за следния код, който Боб написа.

public class BigtaurantApplication {
 public synchronized void order(Customer customer, Food food) { ... }

 public synchronized void changeMusic(Music music) { ... }
}

synchronized метода са заключени в екземпляра. В този случай екземплярът на BigtaurantApplication. Това означава, че само един синхронизиран метод може да бъде извикан едновременно. Виждате, че е доста неефективно. Правенето на поръчка и промяната на музиката нямат нищо общо помежду си и не трябва да се изпълняват едно до едно. synchronized методите могат много лесно да отстранят този вид неефективност и винаги да се използват внимателно.

Да кажем, че Боб някак си успя да ги раздели и ги накара да работят паралелно. Ето какво написа Боб за метода order().

public void order(Customer customer, MenuItem menuItem) {
  synchronized (orderMap) {
    Queue<Customer> customersWaitingForFood = orderMap.get(menuItem);
    customersWaitingForFood.add(customer);
  }
}

Боб използва Map и Queue, за да управлява клиентите, които са поръчали всеки елемент от менюто. Изглежда по-добре от предишния код, тъй като може да работи едновременно с changeMusic, но все още не е достатъчно ефективен.

Тъй като е заключен на екземпляра orderMap, само един клиент може да поръча храната си наведнъж. Ако един клиент поръчва храна, всички клиенти, готови да направят поръчка, трябва да изчакат.

Боб отчаяно иска приложението да бъде по-бързо и се нуждае от много помощ при синхронизирането. В следващите няколко статии ще помогнем на Боб да възстанови своето приложение, като покрием следните техники за синхронизация:

  • Неизменни
  • Едновременни колекции
  • Модел на производител и потребител
  • Ключалка
  • атоми

След като приключим пътуването, ще можете да използвате всеки метод за синхронизиране в подходящата им ситуация. Боб също ще хареса резултата.

Останете на линия!