잘못된 정보가 있다면, 꼭 댓글로 알려주세요(비로그인 익명도 가능).
여러분의 피드백이 저와 방문자 모두를 올바른 정보로 인도할 수 있습니다.
감사합니다. -현록
현록의 기록저장소
Java - volatile 키워드, synchronized 키워드 본문
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/Modifier.html
package testvolatile;
public abstract class ObjectTest {
public abstract int getInteger();
public abstract void setInteger(int integer);
}
package testvolatile;
public class ObjectTestVolatile extends ObjectTest {
private volatile int integer;
public ObjectTestVolatile() {
super();
this.integer = 0;
}
@Override
public int getInteger() {
return integer;
}
@Override
public void setInteger(int integer) {
this.integer = integer;
}
}
package testvolatile;
public class ObjectTestSync extends ObjectTest {
private int integer;
public ObjectTestSync() {
super();
this.integer = 0;
}
@Override
public synchronized int getInteger() {
return integer;
}
@Override
public synchronized void setInteger(int integer) {
this.integer = integer;
}
}
package testvolatile;
import java.util.concurrent.atomic.AtomicInteger;
public class ObjectTestAtomic extends ObjectTest {
private AtomicInteger integer;
public ObjectTestAtomic() {
super();
this.integer = new AtomicInteger(0);
}
@Override
public int getInteger() {
return integer.get();
}
@Override
public void setInteger(int integer) {
this.integer.set(integer);
}
}
package testvolatile;
import java.util.concurrent.atomic.AtomicBoolean;
public class TestMain {
boolean bool = true;
volatile boolean volatileBool = true;
AtomicBoolean atomicBoolean = new AtomicBoolean(true);
public void test() {
new Thread(() -> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
bool = false;
System.out.println(String.format("non-volatile: %s", bool));
}).start();
new Thread(() -> {
while (bool) {
// 아무 명령도 없는 경우에, bool을 갱신하지 않고 캐시 그대로 사용해서 종료되지 않음
}
System.out.println("non-volatile 종료됨");
}).start();
new Thread(() -> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
volatileBool = false;
System.out.println(String.format("volatile: %s", volatileBool));
}).start();
new Thread(() -> {
while (volatileBool) {
// 아무 명령도 없는 경우에도.
}
System.out.println("volatile 종료됨");
}).start();
new Thread(() -> {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
atomicBoolean.set(false);
System.out.println(String.format("atomicBoolean: %s", atomicBoolean.get()));
}).start();
new Thread(() -> {
while (atomicBoolean.get()) {
// 아무 명령도 없는 경우에도.
}
System.out.println("atomicBoolean 종료됨");
}).start();
}
public static void main(String[] args) {
TestMain testMain = new TestMain();
testMain.test();
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
}
System.out.println("--------------------------------------------------");
ObjectTestVolatile objectTestVolatile = new ObjectTestVolatile();
for (int i = 0; i < 200; ++i) {
new Thread(() -> {
objectTestVolatile.setInteger(objectTestVolatile.getInteger() + 1);
System.out.println(String.format("%10s: %03d", "volatile", objectTestVolatile.getInteger()));
}).start();
}
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
}
System.out.println("--------------------------------------------------");
ObjectTestSync objectTestSync01 = new ObjectTestSync();
for (int i = 0; i < 200; ++i) {
new Thread(() -> {
objectTestSync01.setInteger(objectTestSync01.getInteger() + 1);
System.out.println(String.format("%10s: %03d", "sync", objectTestSync01.getInteger()));
}).start();
}
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
}
System.out.println("--------------------------------------------------");
ObjectTestSync objectTestSync02 = new ObjectTestSync();
for (int i = 0; i < 200; ++i) {
new Thread(() -> {
synchronized (objectTestSync02) {
objectTestSync02.setInteger(objectTestSync02.getInteger() + 1);
System.out.println(String.format("%10s: %03d", "sync-block", objectTestSync02.getInteger()));
}
}).start();
}
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
}
System.out.println("--------------------------------------------------");
ObjectTestAtomic objectTestAtomic = new ObjectTestAtomic();
for (int i = 0; i < 200; ++i) {
new Thread(() -> {
objectTestAtomic.setInteger(objectTestAtomic.getInteger() + 1);
System.out.println(String.format("%10s: %03d", "atomic", objectTestAtomic.getInteger()));
}).start();
}
try {
Thread.sleep(5000L);
} catch (InterruptedException e) {
}
System.out.println("--------------------------------------------------");
}
}
atomicBoolean 종료됨 volatile 종료됨 (non-volatile 종료됨은 출력되지 않았고, 쓰레드도 계속 도는 상황!) non-volatile: false atomicBoolean: false volatile: false -------------------------------------------------- volatile: 002 volatile: 003 volatile: 001 volatile: 004 volatile: 005 volatile: 006 volatile: 008 volatile: 009 .......... volatile: 197 volatile: 198 volatile: 199 volatile: 200 -------------------------------------------------- sync: 003 sync: 005 sync: 002 sync: 005 sync: 001 sync: 006 sync: 007 sync: 009 .......... sync: 118 sync: 178 sync: 176 sync: 174 -------------------------------------------------- sync-block: 001 sync-block: 002 sync-block: 003 sync-block: 004 sync-block: 005 sync-block: 006 sync-block: 007 sync-block: 008 .......... sync-block: 197 sync-block: 198 sync-block: 199 sync-block: 200 -------------------------------------------------- atomic: 001 atomic: 002 atomic: 003 atomic: 005 atomic: 004 atomic: 006 atomic: 007 atomic: 008 .......... atomic: 193 atomic: 194 atomic: 196 atomic: 196 atomic: 197 atomic: 198 atomic: 199 atomic: 200 -------------------------------------------------- |
ㆍ코드로 보면 "non-volatile 종료됨"이 출력되어야할 것 같지만,
while (bool)에서 bool을 캐시에 둔 값만 사용해서 계속 true로 사용하여 종료되지 않는다.
ㆍ반면 while (volatileBool)은 volatileBool을 메인 메모리에 두기 때문에, 정상적으로 종료된다. (volatile 종료됨)
ㆍwhile (bool)도 내부에 코드가 있으면 캐시를 새로 고치려 하기 때문에,
일부러 이런 상황을 만들기도 어렵지만, 실제로 발생할 수 있는 상황이다.
ㆍvolatile 키워드는 변수를 메인 메모리에 저장할 뿐, 그렇다고 thread-safe한 결과를 내진 않는다.
값을 읽을 때는 캐시와의 차이는 없더라도, 쓰레드간 순서는 보장하지 않는다.
volatile이 multi-thread 상황에서 각자의 캐시간 값 차이를 없앨 수 있는 수단 중 하나일 뿐,
그 순서를 보장해주진 않는다. 순서는 lock의 개념으로 가야한다.
multi-thread 상황 때문에 둘을 한 범주에 두고 생각해버릴 수 있다.
ㆍvolatile 멤버 변수를 둔 개체라도, multi-thread에서 순서는 보장 되지 않는 것을 결과로 확인할 수 있다.
ㆍgetter()와 setter()가 synchronized 메서드라도,
System.out.println()이 synchronized 메서드가 아니기 때문에,
출력에서 순서가 보장되지 않는다.
(사용하는 모든 메서드가 동일한 개체에 synchronized여야 순서를 보장받을 수 있다.)
ㆍsynchronized () {} 블록으로 묶은 후에야 순서가 보장되었다.
ㆍAtomicWrapper 클래스를 역시 내부에 volatile 키워드를 사용하여 thread별 캐시간 값 차이를 없앨을 뿐,
순서는 보장해주지 않는다.
ㆍ여러 Thread에서 동시에 접근하는 변수라면, volatile 키워드를 사용한다.
ㆍvolatile 변수라도 자체적으로 lock을 해주는 것은 아니다.
multi-thread에서 순서를 보장하려면 synchronized 키워드나 Semaphore 클래스가 필요하다.
ㆍ한 mutex에 묶인 synchronized가 아니라면, 그 사이에는 synchronized 되지 않은 채 진행되므로,
원하는 결과를 얻지 않을 수 있다.
사용하는 메서드와 흐름을 잘 파악하고 묶어야한다.
'Study > Java' 카테고리의 다른 글
Java의 hashCode (0) | 2020.11.04 |
---|
잘못된 정보가 있다면, 꼭 댓글로 알려주세요(비로그인 익명도 가능).
여러분의 피드백이 저와 방문자 모두를 올바른 정보로 인도할 수 있습니다.
감사합니다. -현록