wielowątkowość, aktualizacja instancji obiektu dla wszystkich wątków

wielowątkowość, aktualizacja instancji obiektu dla wszystkich wątków
AR
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: PT
  • Postów: 7
1

Cześć
Piszę sobie aplikację, serwer wielowątkowy, który m.in. będzie udostępniał instancję pewnej klasy (nazwijmy ją GrupyUzytkownikow). I ta instancja będzie podawana jako parametr do konstruktora każdego z wątków (nazwijmy go Serwer).
Moje pytanie brzmi zatem w jaki sposób z poziomu wątku aktualizować tą instancję aby po aktualizacji była widoczna od razu we wszystkich wątkach?
Dodam tylko, że ta instancja jest przechowywana w ramach serializacji ale to chyba nie ma tu większego znaczenia. Obejściem problemu jest restart serwera ale tak jak napisałem jest to tylko obejście. Być może również nie jest to prawidłowe zaprojektowanie serwera i przyjmowanie parametrów. Jeśli tak to proszę o opinię.
Kawałek kodu w jaki sposób to wygląda.

Kopiuj
public static void main(String [] args){
        GrupyUzytkownikow gu = new GrupyUzytkownikow();
        int liczbaWatkow = 3;
        
        for(int i=0; i<liczbaWatkow;i++){
            Serwer sw = new Serwer(gu);
            Thread t = new Thread(sw);
            t.start();
}

Shalom
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: Space: the final frontier
  • Postów: 26433
MarekR22
  • Rejestracja: dni
  • Ostatnio: dni
0

Zanim się weźmiesz za wielowątkowość opanuj dobrze zwykłe programowanie.
Wielowątkowość jest trudna. Główny problem polega na tym, że błędy pojawiają się w sposób niedeterministyczny.
Większość początkujących kończy z kodem, z przekonaniem, że to nic trudnego (bo na ich komputerze działa w 99%), a mają morwie race codition i na innej maszynie leci crash w 70% przypadków.

Ba znam wielu zawodowych developerów, którym też się wydaje, że to nic trudnego, a ciągle łatam u nich błędy.

AR
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: PT
  • Postów: 7
0

Dzięki za udział w wątku.
Domyślam się, że wielowątkowość łatwa nie jest. Nie oczekuję również gotowej odpowiedzi natomiast chciałem uzyskać odpowiedź czy podejście jak pokazałem w kodzie wyżej ma sens czy wprost przeciwnie. I ewentualnie o wskazanie kierunku jak synchronizować dane, obiekt, cokolwiek co jest podawane w konstruktorze (a może nie powinno się stosować takiego podejścia).
Dziękuje również za linka @Shalom. Trochę wyjaśniło.

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4712
3

Gdybyś grupy użytkowników (gu) opakował w AtomicReference https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html to może dostaniesz to co chciałeś.
Atomic reference to kontener - tak jak List czy Optional. Kontener na jeden element, który każdorazowo wyciągasz przez get (wtedy dostaniesz aktualna wartość).
Acha i idealnie (w zasadzie do zrobienia zdrowego kodu to mus) jakby obiekty GrupyUzytkownikow były niemutowalne. (czyli bez seterów i innych dziwactw). Zmieniasz obiekt - to znaczy tworzysz nowy ze zmianą i podstawiasz nową zawartość przez set do AtomicReference.

  • Rejestracja: dni
  • Ostatnio: dni
0
jarekr000000 napisał(a):

Gdybyś grupy użytkowników (gu) opakował w AtomicReference https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html to może dostaniesz to co chciałeś.
Atomic reference to kontener - tak jak List czy Optional. Kontener na jeden element, który każdorazowo wyciągasz przez get (wtedy dostaniesz aktualna wartość).
Acha i idealnie (w zasadzie do zrobienia zdrowego kodu to mus) jakby obiekty GrupyUzytkownikow były niemutowalne. (czyli bez seterów i innych dziwactw). Zmieniasz obiekt - to znaczy tworzysz nowy ze zmianą i podstawiasz nową zawartość przez set do AtomicReference.

Super. To działa. Dziękuje za wskazówki.

rubaszny_karp
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 152
4

To może ja dorzucę swoje 3 grosze.

Najpierw zdefiniujmy sobie ciałka tych klas, bo to dość istotne.

Kopiuj
class GrupyUzytkownikow
{
  private final List list;
 
  public GrupyUzytkownikow()
  {
   this.list = new ArrayList(); 
  }
}

list jest mutowalna, zainicjalizowana przez pole final oraz dostępna przez gettety i settery (każdy wątek może dowolnie modifikować zawartość)

zostaje nam

Kopiuj
class Serwer
{
  final GrupyUzytkownikow gu;
  public Serwer(final GrupyUzytkownikow gu)
  {
    this.gu = gu;
  }
}

Teraz zacznijmy od tego jakie są gwarancje z JMM.

Wszystkie obiekty które tworzysz (GrupyUzytkownikow oraz Serwer) mają pola z modyfikatorem final - to nam gwarantuje store barrier zaraz za stworzeniem obiektu (tu można się odwołać do art, który @Shalom przeczytał https://mechanical-sympathy.b[...]07/memory-barriersfences.html - w np Concurrency in Practice opisane jest to jako safty publication) - teraz, dzięki tej gwarancji możemy te obiekty podawać dalej, pomiędzy wątkami, problem jest taki że nie możemy ich modyfikować, bo te zmiany nie będą widoczne (mogą być, ale nie ma gwarancji JMM)

Co więcej, przez przypadek wprowadziłeś jeszcze jedną gwarancje, mianowicie happens-before( więcej tutaj -> https://docs.oracle.com/javase/tutorial/essential/concurrency/memconsist.html), pomiędzy wątkiem głównym (wykonującym metode main) oraz wątkami które tworzysz, to znaczy że wątki które stworzyłeś i odpaliłeś na nich .start() widzą wszystko co do tej porty zostało zmodyfikowane/stworzone przez główny wątek.

To znaczy że możesz bezpiecznie czytać z obiektów które stworzyłeś, w takim stanie jakie są teraz.

Ale po co nam obiekty których nie możemy bezpiecznie modyfikować ? Jedynym niebezpiecznym obiektem jest aktualnie pole List list z klasy GrupyUzytkownikow ponieważ obiekty które dodasz za jakiś czas mogą nie być widoczne przez wątki serwerów. Masz kilka opcji,

tak jak opisał @jarekr000000 użyć AtomicReference który zapewnia taką widzialność (ale tylko widzialność, nie ma sychronizacji)
możesz użyć volatile - to takie lekkie AtomicReference.

Ale żeby bez wyścigów wiele wątków mogło operować na takiej liście, potrzebne jest thread-safty,

Możesz opisać bardziej use-casy tych obiektów, pola jakie posiadają, można to napisać wydajniej :)

Niech safty-publication będzie z Tobą

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4712
0

Dygresja :
Ja wolę używać List i ogólnie kolekcji z VAVR zamiast java util concurrent.
(ogólnie przyjąłem już taką zasade - jeśli używasz kolekcji z java.util.* w 2017 i dalej to lepiej żebyś miał dobry powód).

Do czasu, kiedy nie muszę walczyć o cykle daje mi to większą kontrolę nad mutowalnością (bo np. w całym serwerze mam 2-3 zmienne - oparte o niemutowalne typy). Wtedy te zmienne sa volatile albo atomicreference.
CopyOnWriteArrayList też nie ratuje - przenosi problem poziom niżej, zawsze lepiej, ale jak dwa wątki modyfikują obiekt pod tym samym indeksem to coś zginie. Jak ma byc safe to w końcu jakieś loki będą potrzebne - albo synchronized albo jakiś optimistic lock (na CopyOnWriteArrayList - akurat można miec dość łatwo) itp...
Ale myśle, że w kontekście problemu, który ma OP to zupełnie nieistotna dygresja.

rubaszny_karp
  • Rejestracja: dni
  • Ostatnio: dni
  • Postów: 152
2
Kopiuj
Jak ma byc safe to w końcu jakieś loki będą potrzebne - albo synchronized albo jakiś optimistic lock (na CopyOnWriteArrayList - akurat można miec dość łatwo) itp...

Nie musi - zawsze można osiągnąć lock free - np: przy pomocy https://github.com/JCTools/JCTools - w dobie thread-per-core, staje sie to coraz bardziej popularne.

Kopiuj
 jeśli używasz kolekcji z java.util.* w 2017 i dalej to lepiej żebyś miał dobry powód).

Czemu ? to jest naprawdę dopicowany pakiet - pod spodem są mega wypasione optymalizacje, na przykład https://stackoverflow.com/questions/18732088/how-does-the-piggybacking-of-current-thread-variable-in-reentrantlock-sync-work #java #magic #love #makejavagreatagain

jarekr000000
  • Rejestracja: dni
  • Ostatnio: dni
  • Lokalizacja: U krasnoludów - pod górą
  • Postów: 4712
1
rubaszny_karp napisał(a):
Kopiuj
Jak ma byc safe to w końcu jakieś loki będą potrzebne - albo synchronized albo jakiś optimistic lock (na CopyOnWriteArrayList - akurat można miec dość łatwo) itp...

Nie musi - zawsze można osiągnąć lock free - np: przy pomocy https://github.com/JCTools/JCTools - w dobie thread-per-core, staje sie to coraz bardziej popularne.

no tak - i zapomniałem o całym CRDT i okolicach.

Kopiuj
 jeśli używasz kolekcji z java.util.* w 2017 i dalej to lepiej żebyś miał dobry powód).

Czemu ? to jest naprawdę dopicowany pakiet - pod spodem są mega wypasione optymalizacje, na przykład https://stackoverflow.com/questions/18732088/how-does-the-piggybacking-of-current-thread-variable-in-reentrantlock-sync-work #java #magic #love #makejavagreatagain

No bo właśnie kolekcje skupiaja się mocno na wydajności, co zrozumiałe - to niejako low level Javy.
Na poziomie biznesu jednak wygodniej operuje się kolekcjami i obiektami niemutowalnymi, których wydajność często obsysa... ale po prawdzie jak większość List w kodzie biznesowym ma do 10 elementów to naprawdę wygodniejsze jest bezpieczeństwo. (zwłaszcza w kontekście współbieżności).
Szczególnie w kodzie, gdzie używa się streamów(), monad itp. - api kolekcji z java util dodatkowo ssie.
Poprawić na mutowalne jak się okaże, że trzeba, jest zawsze nietrudno (i zwykle wystarczy dla kilku kluczowych kolekcji).

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.