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

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.

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();
}

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.

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.

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.

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.

4

To może ja dorzucę swoje 3 grosze.

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

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

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ą

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.

2
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.

 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

1
rubaszny_karp napisał(a):
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.

 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).

1 użytkowników online, w tym zalogowanych: 0, gości: 1