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
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.
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.
To nic nie znaczy! Problem polega na tym, że jeśli wykonanie, w którymPró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.
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.
Tak, teoretycznie, w pewnych okolicznościach.Czy to znaczy, że a != a może zwrócić
true
.
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:
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.
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 ...)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.)
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 safeWarning: 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:
- przeczytaj "a" dwa razy, umieść każdą na stosie, a następnie porównaj wyniki
- przeczytaj "a" tylko jeden raz, umieść go na stosie, zduplikuj (Instrukcja "dup") i uruchom porównanie
- 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).
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
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ść.
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
).
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.
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