Jest!/ align = "left" /

Wiem, że operacje złożone, takie jak {[3] } nie są bezpieczne dla wątków, ponieważ obejmują wiele operacji.

Ale czy sprawdzanie referencji z samym sobą jest bezpiecznym działaniem wątku?

a != a //is this thread-safe

Próbowałem to zaprogramować i użyć wielu wątków, ale nie powiodło się. Chyba nie mogłem symulować wyścigu na mojej maszynie.

EDIT:

public class TestThreadSafety {
    private Object a = new Object();

    public static void main(String[] args) {

        final TestThreadSafety instance = new TestThreadSafety();

        Thread testingReferenceThread = new Thread(new Runnable() {

            @Override
            public void run() {
                long countOfIterations = 0L;
                while(true){
                    boolean flag = instance.a != instance.a;
                    if(flag)
                        System.out.println(countOfIterations + ":" + flag);

                    countOfIterations++;
                }
            }
        });

        Thread updatingReferenceThread = new Thread(new Runnable() {

            @Override
            public void run() {
                while(true){
                    instance.a = new Object();
                }
            }
        });

        testingReferenceThread.start();
        updatingReferenceThread.start();
    }

}

To jest program, którego używam do testowania bezpieczeństwa wątku.

Dziwne zachowanie

Jako mój program rozpoczyna się pomiędzy niektórymi iteracjami i otrzymuje wartość znacznika wyjściowego, co oznacza, że sprawdzenie referencji != nie powiedzie się w tym samym odwołaniu. Ale po kilku iteracjach wyjście staje się stałą wartością false i wtedy wykonywanie programu przez długi czas nie generuje ani jednego wyjścia true.

Jak sugeruje wynik po kilku N (nie stałych) iteracjach wyjście wydaje się być stałą wartością i nie zmienia się.

Wyjście:

Dla niektórych iteracja:

1494:true
1495:true
1496:true
19970:true
19972:true
19974:true
//after this there is not a single instance when the condition becomes true
Author: Narendra Pathai, 2013-08-27

8 answers

W przypadku braku synchronizacji ten kod

Object a;

public boolean test() {
    return a != a;
}

Może produkować true. To jest bajtowy kod test()

    ALOAD 0
    GETFIELD test/Test1.a : Ljava/lang/Object;
    ALOAD 0
    GETFIELD test/Test1.a : Ljava/lang/Object;
    IF_ACMPEQ L1
...

Jak widzimy ładuje pole a do lokalnych var dwa razy, jest to operacja niematomiczna, Jeśli {[4] } została zmieniona pomiędzy innym wątkiem porównanie może wytworzyć false.

Problem z widocznością pamięci jest tutaj istotny, nie ma gwarancji, że zmiany w a dokonane przez inny wątek będą widoczne dla bieżącego wątku.

 122
Author: Evgeniy Dorofeev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-15 12:57:05

Czy czek a != a jest bezpieczny?

If a może być potencjalnie zaktualizowany przez inny wątek (bez odpowiedniej synchronizacji!), następnie nie.

Próbowałem to zaprogramować i użyć wielu wątków, ale nie powiodło się. Chyba nie mogłem symulować wyścigu na mojej maszynie.

To nic nie znaczy! Problem polega na tym, że jeśli wykonanie, w którym a jest aktualizowane przez inny wątek, jest dozwolone przez JLS, to kod nie jest bezpieczny dla wątku. Na fakt, że nie można spowodować, aby stan wyścigu wydarzył się w konkretnym przypadku testowym na konkretnej maszynie i konkretnej implementacji Javy, nie wyklucza, że stanie się to w innych okolicznościach.

Czy to znaczy, że a != a może zwrócić true.

Tak, teoretycznie, w pewnych okolicznościach.

Alternatywnie, {[0] } może zwrócić false, mimo że a zmienia się jednocześnie.


Odnośnie " dziwnych zachowanie": {]}

Gdy mój program uruchamia się pomiędzy kilkoma iteracjami, otrzymuję wyjściową wartość flagi, co oznacza, że Referencja != sprawdzanie nie powiodło się w tym samym odnośniku. Ale po niektórych iteracjach wyjście staje się wartością stałą false i wtedy wykonywanie programu przez długi czas nie generuje ani jednego wyjścia true.

To "dziwne" zachowanie jest zgodne z następującym scenariuszem wykonania:

  1. Program jest wczytany i JVM się uruchamia interpretacja bajtowych kodów. Ponieważ (jak widzieliśmy z wyjścia javap) kod bajtowy wykonuje dwa ładowania, Od czasu do czasu (najwyraźniej) widzisz wyniki stanu wyścigu.

  2. Po pewnym czasie kod jest kompilowany przez kompilator JIT. JIT optimizer zauważa, że istnieją dwa ładunki tego samego gniazda pamięci (a) blisko siebie i optymalizuje drugi z dala. (W rzeczywistości jest szansa, że całkowicie zoptymalizuje test ...)

  3. Teraz stan wyścigu już się nie przejawia, ponieważ nie ma już dwóch ładunków.

Zauważ, że jest to Wszystko zgodne z tym, co JLS pozwala na implementację Javy.


@kriss skomentował (a):

Wygląda na to, że może to być to, co programiści C lub C++ nazywają "niezdefiniowanym zachowaniem" (zależnym od implementacji). Wygląda na to, że może być kilka UB w Javie w takich przypadkach jeden.

Model pamięci Java (określony w JLS 17.4) określa zestaw warunków wstępnych, zgodnie z którymi jeden wątek ma gwarancję wyświetlenia wartości pamięci zapisanych przez inny wątek. Jeśli jeden wątek próbuje odczytać zmienną zapisaną przez Inny, a te warunki wstępne nie są spełnione, to może być kilka możliwych egzekucji ... niektóre z nich mogą być nieprawidłowe (z punktu widzenia wymagań aplikacji). Innymi słowy, zbiór możliwych zachowań (tj. zbiór "dobrze uformowanych egzekucji") jest zdefiniowany, ale nie możemy powiedzieć, które z tych zachowań nastąpi.

Kompilator może łączyć i porządkować Ładunki oraz zapisywać (i robić inne rzeczy) pod warunkiem, że efekt końcowy kodu jest taki sam:

  • podczas wykonywania przez pojedynczy wątek, oraz
  • gdy wykonywane przez różne wątki, które synchronizują się poprawnie(zgodnie z Modelem pamięci).

Ale jeśli kod nie poprawnie zsynchronizować (i dlatego relacje "happens before" nie ograniczają wystarczająco zestawu dobrze uformowanych egzekucji) kompilator może zmieniać kolejność załadowań i zapisywać w sposób, który dałby "nieprawidłowe" wyniki. (Ale to tak naprawdę po prostu mówi, że program jest nieprawidłowy.)

 45
Author: Stephen C,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-13 04:52:17

Udowodnione za pomocą test-ng:

public class MyTest {

  private static Integer count=1;

  @Test(threadPoolSize = 1000, invocationCount=10000)
  public void test(){
    count = new Integer(new Random().nextInt());
    Assert.assertFalse(count != count);
  }

}
Mam 2 porażki na 10 000 inwokacji. Więc NIE , to NIE thread safe
 27
Author: Arnaud Denoyelle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-27 08:48:04

Nie, Nie jest. Dla porównania maszyna wirtualna Java musi umieścić dwie wartości do porównania na stosie i uruchomić instrukcję compare (która zależy od typu "a").

Java VM może:

  1. przeczytaj "a" dwa razy, umieść każdą na stosie, a następnie porównaj wyniki
  2. przeczytaj "a" tylko jeden raz, umieść go na stosie, zduplikuj (Instrukcja "dup") i uruchom porównanie
  3. całkowicie wyeliminuj wyrażenie i zastąp je false

W pierwszym przypadku inny wątek może zmodyfikować wartość " a " między dwoma odczytami.

Wybrana strategia zależy od kompilatora Java i środowiska Java Runtime (zwłaszcza kompilatora JIT). Może się nawet zmienić podczas wykonywania programu.

Jeśli chcesz się upewnić, W Jaki Sposób jest dostępna zmienna, musisz ją utworzyć volatile (tzw. "bariera pół pamięci") lub dodać pełną barierę pamięci (synchronized). Możesz również użyć niektórych API poziomu hgiher (np.]} jak wspomina Juned Ahasan).

Aby uzyskać szczegółowe informacje na temat bezpieczeństwa wątku, przeczytaj JSR 133 (Java Memory Model).

 15
Author: stefan.schwetschke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-27 08:42:47

Wszystko zostało dobrze wyjaśnione przez Stephena C. dla zabawy możesz spróbować uruchomić ten sam kod z następującymi parametrami JVM:

-XX:InlineSmallCode=0

Powinno to zapobiec optymalizacji wykonywanej przez JIT (robi się to na serwerze hotspot 7) i zobaczysz true na zawsze (zatrzymałem się na 2,000,000, ale przypuszczam, że dalej po tym).

Dla Informacji, Poniżej jest kod JIT'ed. Szczerze mówiąc, nie czytam assembly wystarczająco płynnie, aby wiedzieć, czy test jest rzeczywiście wykonywany lub skąd pochodzą te dwa obciążenia od. (linia 26 to test flag = a != a, a linia 31 to nawias zamykający while(true)).

  # {method} 'run' '()V' in 'javaapplication27/TestThreadSafety$1'
  0x00000000027dcc80: int3   
  0x00000000027dcc81: data32 data32 nop WORD PTR [rax+rax*1+0x0]
  0x00000000027dcc8c: data32 data32 xchg ax,ax
  0x00000000027dcc90: mov    DWORD PTR [rsp-0x6000],eax
  0x00000000027dcc97: push   rbp
  0x00000000027dcc98: sub    rsp,0x40
  0x00000000027dcc9c: mov    rbx,QWORD PTR [rdx+0x8]
  0x00000000027dcca0: mov    rbp,QWORD PTR [rdx+0x18]
  0x00000000027dcca4: mov    rcx,rdx
  0x00000000027dcca7: movabs r10,0x6e1a7680
  0x00000000027dccb1: call   r10
  0x00000000027dccb4: test   rbp,rbp
  0x00000000027dccb7: je     0x00000000027dccdd
  0x00000000027dccb9: mov    r10d,DWORD PTR [rbp+0x8]
  0x00000000027dccbd: cmp    r10d,0xefc158f4    ;   {oop('javaapplication27/TestThreadSafety$1')}
  0x00000000027dccc4: jne    0x00000000027dccf1
  0x00000000027dccc6: test   rbp,rbp
  0x00000000027dccc9: je     0x00000000027dcce1
  0x00000000027dcccb: cmp    r12d,DWORD PTR [rbp+0xc]
  0x00000000027dcccf: je     0x00000000027dcce1  ;*goto
                                                ; - javaapplication27.TestThreadSafety$1::run@62 (line 31)
  0x00000000027dccd1: add    rbx,0x1            ; OopMap{rbp=Oop off=85}
                                                ;*goto
                                                ; - javaapplication27.TestThreadSafety$1::run@62 (line 31)
  0x00000000027dccd5: test   DWORD PTR [rip+0xfffffffffdb53325],eax        # 0x0000000000330000
                                                ;*goto
                                                ; - javaapplication27.TestThreadSafety$1::run@62 (line 31)
                                                ;   {poll}
  0x00000000027dccdb: jmp    0x00000000027dccd1
  0x00000000027dccdd: xor    ebp,ebp
  0x00000000027dccdf: jmp    0x00000000027dccc6
  0x00000000027dcce1: mov    edx,0xffffff86
  0x00000000027dcce6: mov    QWORD PTR [rsp+0x20],rbx
  0x00000000027dcceb: call   0x00000000027a90a0  ; OopMap{rbp=Oop off=112}
                                                ;*aload_0
                                                ; - javaapplication27.TestThreadSafety$1::run@2 (line 26)
                                                ;   {runtime_call}
  0x00000000027dccf0: int3   
  0x00000000027dccf1: mov    edx,0xffffffad
  0x00000000027dccf6: mov    QWORD PTR [rsp+0x20],rbx
  0x00000000027dccfb: call   0x00000000027a90a0  ; OopMap{rbp=Oop off=128}
                                                ;*aload_0
                                                ; - javaapplication27.TestThreadSafety$1::run@2 (line 26)
                                                ;   {runtime_call}
  0x00000000027dcd00: int3                      ;*aload_0
                                                ; - javaapplication27.TestThreadSafety$1::run@2 (line 26)
  0x00000000027dcd01: int3   
 6
Author: assylias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-27 14:56:16

Nie, a != a nie jest bezpieczny. Wyrażenie to składa się z trzech części: load a, Load a I perform !=. Możliwe jest, że inny wątek uzyska wewnętrzną blokadę na rodzicu a i zmieni wartość a pomiędzy dwoma operacjami obciążenia.

Innym czynnikiem jest jednak to, czy a jest lokalne. Jeśli a jest lokalny, to żaden inny wątek nie powinien mieć do niego dostępu i dlatego powinien być bezpieczny dla wątku.

void method () {
    int a = 0;
    System.out.println(a != a);
}

Należy również zawsze drukować false.

Zadeklarowanie a jako volatile nie rozwiąże problemu, jeśli a jest static lub instancją. Problem nie polega na tym, że wątki mają różne wartości a, ale że jeden wątek ładuje a dwa razy z różnymi wartościami. To może sprawić, że sprawa będzie mniej bezpieczna.. Jeśli a nie jest volatile, to a może być buforowany, a zmiana w innym wątku nie wpłynie na buforowaną wartość.

 5
Author: DoubleMx2,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-27 20:17:11

Odnośnie dziwnego zachowania:

Ponieważ zmienna a nie jest oznaczona jako volatile, w pewnym momencie wartość a może być buforowana przez wątek. Obie a S z a != a są wtedy wersją buforowaną i dlatego zawsze są takie same (co oznacza, że flag jest teraz zawsze false).

 3
Author: Walter Laan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-08-27 10:41:25

Nawet proste czytanie nie jest atomowe. Jeśli a jest long i nie jest oznaczony jako volatile, to na 32-bitowym JVMs long b = a nie jest bezpieczny dla wątków.

 0
Author: ZhekaKozlov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-23 08:20:56