Wiele serwisów - zablokowanie jednoczesnego wykonywania danej operacji na tym samym obiekcie

0

W mojej aplikacji (Spring) użytkownicy mogą dostać się do obiektów (nie chodzi o obiekty Javowe, a o aplikacje, którymi użytkownicy zarządzają) za pomocą UUID. Jest pewna operacja, która nie powinna zostać jednocześnie wiele razy uruchamiana dla danego UUID.

Mam takie proste rozwiązanie

    @Service
    class Facade {
        private static final Set<String> locks = Collections.newSetFromMap(new ConcurrentHashMap<>());

        Either<Error, Result> foo(String uuid) {
            if (locks.contains(uuid)) {
                return "Error: operation in progress";
            }
            locks.add(uuid);
            try {
                return bar(uuid); //rozne operacje, miedzy innymi na bazie
            } finally {
                locks.remove(uuid);
            }
        }
    }

To będzie działać, jeżeli wystawię użytkownikom tylko jedną instancję mojej aplikacji (tytułowy serwis). Co, gdybym chciał to przeskalować? Co, gdyby skala była poważna?

  1. W moim przypadku operacja bar(uuid) jest wykonywana rzadko, a jej wielokrotne uruchomienie w tym samym czasie to efekt przypadku lub złośliwości użytkownika. Myślę, żeby po prostu dodać kolumnę w bazie, w której będzie przechowywany lock.
  2. I do sedna tego wątku. Co w odwrotnej sytuacji - częste próby wykonania jakiejś operacji (niewynikające z przypadków i złośliwości użytkowników), wielu użytkowników, wiele instancji serwisu na różnych maszynach? Słyszałem kiedyś o Redis - nie używałem. Czy to byłoby dobre rozwiązanie? Jak się rozwiązuje takie problemy?
2

Z wielu możluwych rozwiązań zwykle dobry wystarczająco jest optimistic lock.

Jest to bazodanowy odpowiednik compare and swap.

4

Twój kod nie działa, masz race condition między sprawdzeniem seta a dodaniem elementu.
Nie wspomnę już o potencjalnym out of band exception i niewykonaniu finally (i niezwolnieniu blokady).

0
Afish napisał(a):

Twój kod nie działa, masz race condition między sprawdzeniem seta a dodaniem elementu.
Nie wspomnę już o potencjalnym out of band exception i niewykonaniu finally (i niezwolnieniu blokady).

To pierwsze można chyba naprawić wywołując tylko add:

@Service
class Facade {
    private static final Set<String> locks = Collections.newSetFromMap(new ConcurrentHashMap<>());

    Either<Error, Result> foo(String uuid) {
        if (!locks.add(uuid)) {
            return "Error: operation in progress";
        } else {
            try {
                return bar(uuid); //rozne operacje, miedzy innymi na bazie
            } finally {
                locks.remove(uuid);
            }
        }
    }
}
0

@jarekr000000: ciekawy sposób, chociaż chodziło mi trochę o coś innego - o dosłowne zablokowanie wykonywania jakiejś czynności po stronie serwera (nie tylko transakcji w bazie).
@Afish u mnie działa :P A na poważnie to niezła kaszana. Ale skąd tam out of band exception?
@cs to jest dobre pytanie.

1
Potat0x napisał(a):

Ale skąd tam out of band exception?

Coś ubije wątek, poleci OOM, poleci access violation. Pewnie jeszcze inne rzeczy by się znalazły.

4
Potat0x napisał(a):

Słyszałem kiedyś o Redis - nie używałem. Czy to byłoby dobre rozwiązanie?

Redis jest tylko cachem, taką rozbudowaną hashmapą. Bywa używany do synchronizacji/blokowania ponieważ zawiera TTLe (Time to Live). Jeśli blokujesz zasób za pomocą bazy danych i stanie się coś, że użytkownik jednak nie odblokuje zasobu to zasób już nigdy nie zostanie odblokowany. Bedziesz musiał wjechać do bazy danych na białym koniu i usunąć stare locki ręcznie. Ewentualnie możesz mieć skrypt puszczany w cronie, który będzie usuwać stare locki.

Ale jeśli używasz redisa to możesz tworzyć locki jako obiekty TTL. Czyli jeśli użytkowni nie odblokuje zasobu to, gdy minie czas TTL lock zostanie usunięty automatycznie.

4
Potat0x napisał(a):

@jarekr000000: ciekawy sposób, chociaż chodziło mi trochę o coś innego - o dosłowne zablokowanie wykonywania jakiejś czynności po stronie serwera (nie tylko transakcji w bazie).

Generalnie chodziło mi o nie blokowanie. W wielu wypadkach opłaca się tak naprawdę nie blokować, a normalnie przeprowadzać obliczenia itp... i dopiero na koniec, jeśli okaże się, że ktoś nas ubiegł i coś wcześniej przeliczył to po prostu porzucamy wyniki (np. przez rollback).

W ten sposób zwiększamy zużycie CPU i innych zasobów (na nonsensowne obliczenia), ale za to zwięszamy poziom zrównoleglenia (skalowalność). Im więcej nodów / rdzeni tym ten drugi sposób jest bardziej sensowny.

Synchronized może się zabawnie skończyć, co widziałem na jednej produkcji - 16 serwerów nic nie robi, kilka baz danych oracle nic nie robi, w IO nic się nie dzieje, a z aplikacji nie da się korzystać - wszystko czeka na synchronized (finalnie) na clusterowym locku :-)

0
Kamil Żabiński napisał(a):

Ale jeśli używasz redisa to możesz tworzyć locki jako obiekty TTL. Czyli jeśli użytkowni nie odblokuje zasobu to, gdy minie czas TTL lock zostanie usunięty automatycznie.

Tu warto dodać, że po wzięciu takiego locka proces i tak musi sprawdzać, czy nie dostaliśmy pauzy przez GC lub wirtualizację i TTL w międzyczasie wygasł. Ogólnie to nie jest hop siup i łatwo zepsuć.

0

W odpowiedzi do tego komentarza

    private static final Set<String> redeployLocks = Collections.newSetFromMap(new ConcurrentHashMap<>());
    

    Either<ErrorMessage, AppResponseDto> redeployApp(String appUuid, AppRequestDto requestDto) {
        try {
            if (redeployLocks.add(appUuid)) {
                return redeploy(appUuid, requestDto);
            } else {
                return Either.left(message("Redeploy already started", 429));
            }
        } finally {
            redeployLocks.remove(appUuid);
        }
    }

PS: właśnie zauważyłem nowy problem. Kto wie jaki? (zagadka :P)

0

Teraz usuwasz locka wziętego przez kogoś innego.

0

Dokładnie. Teraz nie ma tego problemu:

    Either<ErrorMessage, AppResponseDto> redeployApp(String appUuid, AppRequestDto requestDto) {
        if (redeployLocks.add(appUuid)) {
            try {
                return redeploy(appUuid, requestDto);
            } finally {
                redeployLocks.remove(appUuid);
            }
        } else {
            return Either.left(message("Redeploy already started", 429));
        }
    }

ale dalej może się zdarzyć równoległe wykonanie wnętrza if dla tego samego appUuid przez wiele wątków jednocześnie.

0

Ale właśnie cały wątek jest o tym, że nie możesz tak zrobić. Musisz atomowo sprawdzić i założyć locka, aby żaden wątek się nie prześlizgnął. Poczytaj o klasie ReentrantLock i ogólnie czym jest sekcja krytyczna - na pewno zaprocentuje na przyszłość. (W niektórych scenariuszach możesz też jako implementację locka wykorzystać bazę danych).

0

@Charles_Ray: właśnie z tego skorzystałem, teraz musi być ok.

        boolean redeployNotRunning;
        redeployLocksSetLock.lock();
        try {
            redeployNotRunning = redeployLocks.add(appUuid);
        } finally {
            redeployLocksSetLock.unlock();
        }
0

Teraz też nie masz pewności, że nie dostaniesz out of band między skończeniem lock a wejściem do try (chyba, że masz, to wtedy podaj źródła). W dotnecie na przykład JIT wstawiał nopa przed try i był deadlock.

0

@Afish: możesz rozwinąć dlaczego nie ma takiej gwarancji w takim przypadku? Ten lock jest ogarniamy na poziomie systemu operacyjnego w tym przypadku i IMO to zadziała. To jest chyba najbardziej podstawowe użycie locków w Javie.

0

@Afish: ale mówimy o jakichś sytuacjach typu błąd w JVM? Ciężko znaleźć w Google coś konkretnego na temat out of band (a na początku myślałem, że to literówka i chodzi o out of bounds :P).
Nawet w dokumentacji Javy jest

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }

więc o jeżeli nie ma błędu w Javie (zakładam, że nie ma), to po prostu musi działać poprawnie.

2

Try-catch jest obsługiwany przez metadane mówiące skąd dokąd jest blok try. Jak skompilujesz kod javowy, to dostajesz bajtkod i tam jest podane, że od instrukcji X do Y jest try. To oznacza, że jeżeli nie zaczniesz wykonywać instrukcji X, to nie jesteś w bloku try, więc finally nie będzie wykonywany w przypadku wyjątku.

Teraz trzeba sobie uświadomić, że nasz kod javowy „nie ma znaczenia” — javac wykonuje optymalizacje, JIT optymalizuje jeszcze bardziej, potem jest poziom CPU z optymalizacjami read-write i podobnymi. To, co się wykonuje w rzeczywistości jest nierzadko czymś zupełnie innym od tego, co zaklepaliśmy. Proponuję poczytać o modelu pamięci i wielowątkowości.

JIT musi wziąć kod + metadane i w jakiś sposób obsłużyć wyjątek. Tam nie ma magii pod spodem, wyjątek jest zgłaszany na przykład przerwaniem sprzętowym i JVM sprawdza, co się stało i gdzie — może być użyty SEH, sygnały, SJLJ, ale w gruncie rzeczy sprowadza się to nieraz do porównywania rejestru IP lub tego typu podobnych. Teraz biorąc Twój kod:

redeployLocksSetLock.lock();
try {

wszystko wygląda spoko dopóki metoda lock jest poprawna, czyli bierze locka i potem jesteśmy w bloku try. Ale jeżeli JIT skompiluje to do czegoś w stylu:

1: call redeployLocksSetLock.lock()
2: nop
3: cokolwiek z try

czyli wstawi instrukcję nop między wywołaniem metody a blokiem try, to jeżeli jesteśmy w instrukcji 2 i dostajemy out-of-band exception, to formalnie nie jesteśmy jeszcze w try i finally się nie wykona.

Nie masz kontroli nad tym, co zrobi JIT, musisz mieć albo gwarancję od JVM lub kompilatora, że takich rzeczy nie robi, albo założyć, że może się to zdarzyć. Szukałem i pytałem na SO o takowe i nic nie dostałem, być może się mylę (jak ktoś ma źródło, to z chęcią przyjmę).

I teraz pytanie, czy teoretyzuję, czy to się dzieje? To był rzeczywisty bug w dotnecie związany z optymalizacjami: https://stackoverflow.com/a/34876564/1543037
I od razu dodam, że to nie jest coś „dotnet specific”, tylko tak działają komputery na pewnym poziomie, więc ten bug może kiedyś wrócić.

Sytuację, gdy JIT generuje nop dla łatwiejszego debugowania pominę, bo chyba nie wszystkie to robią, poza tym na produkcji raczej nie odpalamy wersji debug, więc jest to raczej bezpieczne (aczkolwiek też warto być tego świadomym).

1

Teraz trzeba sobie uświadomić, że nasz kod javowy „nie ma znaczenia”

No tak, zabieramy rzeczy i idziemy do domu, nic tu po nas... Oczywiście, że ma znaczenie - każda optymalizacja musi zachowywać semantykę programu który napisaliśmy. Gdyby tak nie było, Java i JVM byłyby chyba bezużyteczne, prawda?

Nie znam się na .NET, ale w przykładzie który przytoczyłeś dla mnie głównym problemem jest istnienie takiej funkcji jak Thread.Abort() - w Javie zrozumiano tak ze 20 lat temu, że przerywanie pracy wątku w dowolnym momencie to proszenie się o kłopoty i metoda Thread.stop() została oznaczona jako deprecated (w Javie 11 całkowicie usunięta).

0
damianem napisał(a):

Teraz trzeba sobie uświadomić, że nasz kod javowy „nie ma znaczenia”

No tak, zabieramy rzeczy i idziemy do domu, nic tu po nas... Oczywiście, że ma znaczenie - każda optymalizacja musi zachowywać semantykę programu który napisaliśmy. Gdyby tak nie było, Java i JVM byłyby chyba bezużyteczne, prawda?

Oczywiście, co nie znaczy, że kod wykonuje się tak, jak go napisaliśmy. Ponownie - model pamięci.

Nie znam się na .NET, ale w przykładzie który przytoczyłeś dla mnie głównym problemem jest istnienie takiej funkcji jak Thread.Abort() - w Javie zrozumiano tak ze 20 lat temu, że przerywanie pracy wątku w dowolnym momencie to proszenie się o kłopoty i metoda Thread.stop() została oznaczona jako deprecated (w Javie 11 całkowicie usunięta).

  1. Thread.Abort został wzięty z Javy (jak cały dotnet, który zerżnął nawet zwalone tablice), też był deprecated i też był usunięty. Nic to nie zmienia w kwestii out of band exceptions.
  2. Wątek ciągle można ubić z zewnątrz.
2
Afish napisał(a):
  1. Thread.Abort został wzięty z Javy (jak cały dotnet, który zerżnął nawet zwalone tablice), też był deprecated i też był usunięty. Nic to nie zmienia w kwestii out of band exceptions.
  2. Wątek ciągle można ubić z zewnątrz.

Można też pomazać po pamięci, mogą promienie kosmiczne locka zdjąć.
Sorry, ale już bardzo odjechałeś - model pamięci to jedno, ale podstawa JVM to właśnie fakt, że ma jakąś semantykę (bajtkod) i nie rozważamy w normalnym kodzie sytuacji abnormalnych, gdzie JVM funkcjonuje niepoprawnie. Nagłe ubicie wątku i pojawienie się wyjątku z d...y - to jest sytuacja nienormalna.

Pomijająć ten kod powyżej - ponieważ cały JVM jest pełny tak popisanych locków (podobnie, na ogół jest wprost CAS), to znaczy, że po ubiciu wątków z dużym prawdopodobieństwem powywala się IO, wewnętrzne struktury JVM, nie mówiąc już o bibliotekach typu Spring i podobne.

W praktyce oczywiście JVM ma bugi, a ludzie robią dziwne rzeczy (np. wywołania JNI) - zwykle wtedy JVM bardzo ładnie się składa core dumpem i problem się kończy.
Szansa, że ktoś będzie się tutaj nad deadlockiem męczył jest 0.

Tutak jako przykład podajesz ciągle, że ktoś może wywołać metodę, która jest oznaczona jako niebezpieczna. Poza twoim zespołem nie słyszałem o takiej akcji z Thread.stop() :-)
Typowe watchdogi i podobne w javie - raczje operują na interrupt, które jest bezpieczniejsze (choć wymaga jakiejś kooperacji od wątku, z tym jeśli to nasz wątek to nie jest to raczej proplem).

Z ciekawostek - kotlinowy .withLock - czyli to samo lambdą. ( I standard w kodzie kotlinowym)

@kotlin.internal.InlineOnly
public inline fun <T> Lock.withLock(action: () -> T): T {
    lock()
    try {
        return action()
    } finally {
        unlock()
    }
}
0
jarekr000000 napisał(a):

nie rozważamy w normalnym kodzie sytuacji abnormalnych, gdzie JVM funkcjonuje niepoprawnie.

Ja też tego tutaj nie rozważam. Wszystkie opisane przez mnie sytuacje są w pełni dozwolone (jeżeli się mylę, to wskaż, może JVM rzeczywiście ma jakieś gwarancje, których nie mają inne platformy).

jarekr000000 napisał(a):

Nagłe ubicie wątku i pojawienie się wyjątku z d...y - to jest sytuacja nienormalna.

Nie mnie oceniać, czy normalna, czy nie. Takie rzeczy się dzieją i tyle.

jarekr000000 napisał(a):

Tutak jako przykład podajesz ciągle, że ktoś może wywołać metodę, która jest oznaczona jako niebezpieczna. Poza twoim zespołem nie słyszałem o takiej akcji z Thread.stop() :-)

Trudno, nic nie poradzę.

jarekr000000 napisał(a):

Z ciekawostek - kotlinowy .withLock - czyli to samo lambdą. ( I standard w kodzie kotlinowym)

@kotlin.internal.InlineOnly
public inline fun <T> Lock.withLock(action: () -> T): T {
    lock()
    try {
        return action()
    } finally {
        unlock()
    }
}

Dotnet też to tak implementował, a potem zrozumieli, że to się może wysypać i zmienili implementację.

0

Offtopic się zrobił. Podążając tym tokiem myślenia należałoby w ogóle zrezygnować z JVM na rzecz bardziej niskopoziomowego rozwiązania :) reasumując, jeśli JVM, to podane rozwiązanie jest optymalne w tym zakresie.

2
Afish napisał(a):
jarekr000000 napisał(a):

nie rozważamy w normalnym kodzie sytuacji abnormalnych, gdzie JVM funkcjonuje niepoprawnie.

Ja też tego tutaj nie rozważam. Wszystkie opisane przez mnie sytuacje są w pełni dozwolone (jeżeli się mylę, to wskaż, może JVM rzeczywiście ma jakieś gwarancje, których nie mają inne platformy).

Wskazuję, że nie są dozwolone:
https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#stop--

Poza tym jeśli dopuszczamy możliwość, że część kodu jest niegodna zaufania to wtedy można włączyć SecurityManagera, który wprost uniemożliwi wywołanie Thread.stop i podobnych.
Robi sie to wyjątkowo, piszac serwery aplikacji czy kontener appletów itp..

Nadal nie chroni to, przed wyjściem z wątku przez OOM, ale tu z kolei mamy złożenie się JVM po takim przypadku + z tego co kojarze akurat to finally wtedy zadziała

Jeszcze mam propozycję, żebyś podał kod bezpieczniejszy.
.

0
jarekr000000 napisał(a):

Wskazuję, że nie są dozwolone:
https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#stop--

Ja mówię o optymalizacjach na poziomie JIT-a, a Ty mi pokazujesz komentarze w javadocu…

Nadal nie chroni to, przed wyjściem z wątku przez OOM, ale tu z kolei mamy złożenie się JVM po takim przypadku + z tego co kojarze akurat to finally wtedy zadziała

Finally wykona się w przypadku OOM użytkownika, tak samo jak wykona się w przypadku ThreadDeath. Możesz też złapać OOM w catchu i normalnie kontynuować. I zanim odbijesz mnie, że to herezja, to od razu powiem, że takie rzeczy widziałem.

Nie wiem, czy finally wykona się w przypadku OOM rzuconego przez alokację z JVM-a, ale takich sytuacji tu nie rozważamy póki co.

Jeszcze mam propozycję, żebyś podał kod bezpieczniejszy.

Weź blokadę w bloku try i odpowiednio zwolnij w finally.

@jarekr000000: Pokazałem potencjalny błąd z wyjaśnieniem jego istoty, a Ty cały czas odbijasz mnie, że nigdy nie widziałeś thread.stop w akcji. Dla mnie ta rozmowa nigdzie nie zmierza, więc jak nie masz innych argumentów, to ja już podziękuję.

1
Afish napisał(a):

Weź blokadę w bloku try i odpowiednio zwolnij w finally.

Czyli jak rozumiem postulujesz taki kod:

public void m() {
     try {
       lock.lock();  // block until condition holds
       // ... method body
     } finally {
       lock.unlock()
     }
   }

Jesteś świadom jakie są konsekwencje tego kwiatka? W kontekście scenariusza podanego przez OP.

0

Czym jest lock w Twoim kodzie? Jeżeli to coś nie śledzi właściciela, to w finally odblokujesz locka, którego wziął ktoś inny (czyli nie jest to "odpowiednio zwolnij w finally"). Jeżeli to coś śledzi, ale przy próbie odblokowania z innego wątku rzuca wyjątek, to też niedobrze (i znowu nie jest to "odpowiednio zwolnij w finally"). A jak śledzi i nie rzuca, to ciągle pozostaje problem rekurencji. W każdym razie nie wiem, czego próbujesz dowieść przez pokazanie błędnej implementacji.

Błąd może wystąpić (a przynajmniej nie udowodniłeś, że jest inaczej), wprawdzie szanse są minimalne, ale niezerowe i takie rzeczy się dzieją. To już Twój wybór, co z tym zrobisz.

2

@Afish

Cały czas próbuję dostać od Ciebie propozycję dobrej implementacji. Pokaż kod.
Jak działa lock możesz zobaczyć tutaj:
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html
Możesz sobie wybrać jedną z 2 dostępnych implementacji, albo napisać własną.

EDIT:
To nie jest tak, że się nie da, problem leży w czym innym, ale chętnie popatrzę jak się męczysz.

0

Dzięki, nie skorzystam. Pisałem już locki do specyficznych potrzeb i wiem, że to nie jest łatwe, a tego problemu w Javie jeszcze nie musiałem rozwiązywać (w ważnych miejscach obchodziłem go na wyższym poziomie), więc nie mam kodu, który pasowałby do podanego przez Ciebie interfejsu i jednocześnie robił robotę.

Jak taka implementacja miałaby wyglądać? Gdyby musiał to pisać w Javie, to pewnie szedłbym w stronę CAS na współdzielonej pamięci, gdzie zapisywałbym ciasteczko od wątku. Inne wątki musiałyby jeszcze wykryć, że wątek trzymający blokadę umarł i zwalniać po jakimś czasie. Kiedyś używałem czegoś podobnego (między procesami), jak jesteś zainteresowany, to popatrz tutaj: https://blog.adamfurmanek.pl/?p=3148&preview=1&_ppp=11d8547ad4 Gdybym musiał to pisać inaczej, to pewnie zrobiłbym cywilizowany mechanizm ustawiający atomowo flagę (zerżnąłbym go pewnie z dotneta). I zapewne przy implementacji nadziałbym się na masę problemów (chociażby różne platformy lub utrzymywanie sprawiedliwej kolejki), nie przeczę.

Mam wrażenie, że skupiłeś się na atakowaniu mnie, zamiast na analizie problemu. Ja nie mówię, żebyś przepisał cały swój kod, ani nie zależy mi na tym, żebyś mi uwierzył, że problem występuje, ba, nawet zgadzam się, że zazwyczaj nie ma co z tym walczyć, tylko lepiej ubić JVM. Jak nie masz thread.stop i pozwalasz maszynie umrzeć, to pewnie nawet w przypadku wystąpienia problemu nie odczujesz konsekwencji. Ale jednocześnie pracowałem w projekcie, gdzie śmierć procesu była bardzo niepożądana i różne sytuacje były brane pod rozwagę, a Twoje podejście „Mogę zrobić taki test w ile minut po wrzucieniu Thread.stop do źródeł dostanę wypowiedzenie...” nie byłoby żadnym usprawiedliwieniem. Tylko, że ten temat dotyczy locka między różnymi maszynami, a tam problemy w stylu „process.kill” występują nagminnie i warto o tym pamiętać, więc zwróciłem autorowi uwagę, że jego kod może nie być idealny (w rozumieniu, że gdyby taki kod przeniósł na poziom wyżej, to miałby o wiele większą szansę awarii).

A odnośnie „chętnie popatrzę jak się męczysz”, to mam nadzieję, że nie prezentujesz takiego podejścia w pracy.

1

Ja w ogóle zrobiłbym to raczje bez blockad - tylko coś nieblokujacego w stylu CAS - bo zwykle się da.
Ale jak juz ktoś chce się bawić w locki to podane rozwiązanie (try po lock) jest dość optymalne, ponieważ potencjalne ryzyko problemu jest znikowe ( złośliwy kod, złe działanie JVM), a straty małe (zablokowany jeden zasób),
Alternatywy to np:

  • lock w try to równie małe prawdpowodobienstwo problemu, przy potencjalnie gorszych szkodach (wielokrotne użycie zasobu)
  • dużo bardziej skomplikowany kod (tak jak piszesz wyżej) - IMO nie warto.

Rozumiem, że masz jakiś uraz do tego Thread.stop, może w .NET problem zdychania wątków bardziej występuje, (przez unsafe? ) ale nie warto się zabezpieczać na wszystko, trzeba mierzyć środki do wybranego problemu i szkód.
Inaczej wpadamy w paranoję i efekt ostateczny to poryty, pełny bugów, kod blokad, którego nikt nie ogarnia, a szczególnie autor. Przy okazji - dawno, dawno temu widziałem to w akcji - więc akurat ja mam osobisty uraz do tego podejścia.
Jak będę pisał kod krytyczny w elektrowni atomowej: - a) to takie problemy rozważę, b) nie zrobię tego w javie :-)

(ogólnie to kwestie sekcji krytycznej z pewnymi gwarancjami rozwiązuje w javie słówko synchronized, ale akurat tu nie widzę prostego użycia no i synchronized też ma swoje problemy).

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