synchronized
๊ฐ์ ๋ฝ ๊ธฐ๋ฐ ์ ๊ทผ ๋ฐฉ์๋ ์์ง๋ง, ์ฑ๋ฅ์ ๊ณ ๋ คํ๋ฉด ๋น์ฐจ๋จ ๋๊ธฐํ(Non-blocking Synchronization) ๊ธฐ๋ฒ์ด ๋์ฑ ์ ํฉํ ๋๊ฐ ์์ต๋๋ค.
์ ๋ฒ ๋ธ๋ก๊น ์์ ๋์์ฑ์ ์ ์ดํ๋ ๋ฐฉ๋ฒ์ ๋ํด์ ์์๋ณด์๋๋ฐ Java๋ฅผ ์ด์ฉํ ๋์์ฑ ์ ์ด ๋ฐฉ์์ CAS ์ฐ์ฐ์ด ์งง๊ฒ ๋์์ด์ CAS ์ฐ์ฐ๊ณผ ์ถ๊ฐ๋ก volatile์ ์ด์ฉํ ๋ฐฉ์์ ๋ํด์ ํฌ์คํ ํด๋ณด๋ ค๊ณ ํฉ๋๋ค.
์ด ๊ธ์์๋ CAS(Compare-And-Swap)์ volatile
์ ํ์ฉํ์ฌ ๋ฐ์ดํฐ ์ ํฉ์ฑ์ ๋ณด์ฅํ๋ ๋ฐฉ๋ฒ์ ๋ค๋ฃจ๊ณ , ํ
์คํธ๋ฅผ ํตํด ๊ฒ์ฆํด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์ ์ฒด ์ฝ๋๋ ๊นํ๋ ํฌ์์ ํ์ธํด์ฃผ์ธ์
volatile
์ JVM์ด ๋ณ์์ ๊ฐ์ ์ง์ ๋ฉ์ธ ๋ฉ๋ชจ๋ฆฌ(Main Memory)์์ ์ฝ๊ณ ์ฐ๋๋ก ๋ณด์ฅํ๋ ํค์๋์ด๋ค.
์ฆ, ์บ์๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๋ชจ๋ ์ค๋ ๋๊ฐ ํญ์ ์ต์ ๊ฐ์ ๋ณผ ์ ์๋๋ก ๊ฐ์์ฑ(visibility)์ ์ ๊ณตํ๋ค.
private volatile int count = 0;
โ
๊ฐ์์ฑ(Visibility) ๋ณด์ฅ: ๋ชจ๋ ์ค๋ ๋๊ฐ ๋์ผํ ๊ฐ์ ๋ณด์ฅ๋ฐ์
โ ์์์ฑ(Atomicity) ๋ฏธ๋ณด์ฅ: count++
๊ฐ์ ์ฐ์ฐ์ ์ฌ์ ํ ์์ ํ์ง ์์
โ ๋๊ธฐํ(Synchronization) ๋ฏธ๋ณด์ฅ: ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ๊ฐ์ ๋ณ๊ฒฝํ๋ฉด ์ ํฉ์ฑ์ด ๊นจ์ง ์ ์์
CAS๋ ์์์ ์ผ๋ก ๊ฐ์ ๋ณ๊ฒฝํ ์ ์๋ ์ฐ์ฐ์ด๋ค.
๋ค์๊ณผ ๊ฐ์ ๊ณผ์ ์ผ๋ก ๋์ํ๋ค.
โ
์์์ฑ(Atomicity) ๋ณด์ฅ: CAS ์ฐ์ฐ ์์ฒด๊ฐ ์์์ ์ด๋ฏ๋ก ๋ฝ ์์ด ๋์์ฑ์ ์ฒ๋ฆฌํ ์ ์์
โ ๊ฐ์์ฑ(Visibility) ๋ฏธ๋ณด์ฅ: ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ CAS๋ฅผ ์ํํ๋ฉด ์ต์ ๊ฐ์ ๋ณด์ฅํ์ง ๋ชปํ ์ ์์
โ ABA ๋ฌธ์ ๋ฐ์ ๊ฐ๋ฅ: ์์ ๊ฐ์ด ๋ฐ๋์๋ค๊ฐ ๋ค์ ๋์์จ ๊ฒฝ์ฐ์๋ CAS๊ฐ ์ฑ๊ณตํ ์ ์์
CAS ์ฐ์ฐ์ ์ํํ๋ ค๋ฉด JVM ๋ด๋ถ์์ ์ ๊ณตํ๋ Unsafe
ํด๋์ค๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
ํ์ง๋ง Unsafe.getUnsafe()
๋ฅผ ์ง์ ํธ์ถํ๋ฉด SecurityException์ด ๋ฐ์ํ๊ธฐ ๋๋ฌธ์,
๋ฆฌํ๋ ์
์ ์ฌ์ฉํ์ฌ Unsafe
๊ฐ์ฒด๋ฅผ ๊ฐ์ ธ์ค๋ UnsafeUtil
์ ๊ตฌํํด์ผ ํ๋ค.
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeUtil {
private UnsafeUtil() {
// ์ ํธ๋ฆฌํฐ ํด๋์ค๋ ๊ฐ์ฒด ์์ฑ ๋ฐฉ์ง
}
public static Unsafe getUnsafe() {
try {
// Unsafe ํด๋์ค์ theUnsafe ํ๋๋ฅผ ๊ฐ์ ธ์ด (์ด ํ๋๋ private static final ๋ณ์)
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true); // ์ ๊ทผ ๊ถํ ํด์
return (Unsafe) field.get(null); // ํ๋ ๊ฐ์ ๊ฐ์ ธ์ Unsafe ๊ฐ์ฒด๋ก ์บ์คํ
} catch (Exception e) {
throw new RuntimeException("Unsafe instance cannot be obtained", e);
}
}
}
โ
์ด์ UnsafeUtil.getUnsafe()
๋ฅผ ํธ์ถํ๋ฉด ์ด๋์๋ Unsafe
๊ฐ์ฒด๋ฅผ ์์ ํ๊ฒ ์ป์ ์ ์๋ค.
์ด๋ฒ ์คํ์์๋ Product
ํด๋์ค๋ฅผ ๋ง๋ค๊ณ ,
count
)volatileCount
)countCAS()
)volatileCountCAS()
)public class VolatileProduct {
private int count = 0;
private volatile int volatileCount = 0;
private static final Unsafe unsafe = UnsafeUtil.getUnsafe();
private final long countOffset;
private final long volatileCountOffset;
public VolatileProduct() throws NoSuchFieldException {
this.countOffset = unsafe.objectFieldOffset(VolatileProduct.class.getDeclaredField("count"));
this.volatileCountOffset = unsafe.objectFieldOffset(VolatileProduct.class.getDeclaredField("volatileCount"));
}
public void incrementCount() {
count++;
}
public void incrementVolatileCount() {
volatileCount++;
}
public void incrementCountCAS() {
int oldValue;
do {
oldValue = unsafe.getInt(this, countOffset);
} while (!unsafe.compareAndSwapInt(this, countOffset, oldValue, oldValue + 1));
}
public void incrementVolatileCountCAS() {
int oldValue;
do {
oldValue = unsafe.getIntVolatile(this, volatileCountOffset);
} while (!unsafe.compareAndSwapInt(this, volatileCountOffset, oldValue, oldValue + 1));
}
public int getCount() {
return count;
}
public int getVolatileCount() {
return volatileCount;
}
}
@Nested
@DisplayName("incrementCount ๋ฉ์๋๋")
class IncrementCountMethod {
@Test
@DisplayName("์ผ๋ฐ ๋ณ์๋ฅผ ์ฆ๊ฐ ์ํค๋๋ฐ 10๊ฐ์ ์ค๋ ๋๋ก 1000๋ฒ ์คํํด๋ 10000์ด ๋์ง ์๋๋ค")
void should_not_ensure_consistency_with_plain_variable()
throws InterruptedException, NoSuchFieldException {
// Given
VolatileProduct volatileProduct = new VolatileProduct();
ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);
// When
for (int i = 0; i < THREAD_COUNT; i++) {
VolatileProduct finalVolatileProduct = volatileProduct;
executor.submit(() -> {
for (int j = 0; j < INCREMENT_PER_THREAD; j++) {
finalVolatileProduct.incrementCount();
}
});
}
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
// Then
assertNotEquals(EXPECTED_COUNT, volatileProduct.getCount());
}
}
ํ ์คํธ ๋์ | ๊ฒฐ๊ณผ | ๋์์ฑ ๋ณด์ฅ ์ฌ๋ถ | ์ค๋ช |
---|---|---|---|
incrementCount() | โ ์คํจ | โ | ์ผ๋ฐ ๋ณ์๋ ๋๊ธฐํ ๋ฌธ์ ๋ฐ์ |
incrementVolatileCount() | โ ์คํจ | โ | volatile์ ๊ฐ์์ฑ๋ง ๋ณด์ฅ, ์์์ฑ ๊นจ์ง |
incrementCountCAS() | โ ๏ธ ๋ถ๋ถ ์ฑ๊ณต | โ ๏ธ ๋ถ๋ถ ๋ณด์ฅ | CAS๋ง์ผ๋ก ์์์ฑ์ ์ ์งํ์ง๋ง ์ต์ ๊ฐ ๋ณด์ฅ X |
incrementVolatileCountCAS() | โ ์ฑ๊ณต | โ | CAS + volatile๋ก ๊ฐ์์ฑ๊ณผ ์์์ฑ ๋ชจ๋ ํด๊ฒฐ |
AtomicInteger
๋ฑ CAS ๊ธฐ๋ฐ ํด๋์ค๋ ๋ด๋ถ์ ์ผ๋ก volatile
์ ์ฌ์ฉํจ. ๐ํ ์คํธ ์ฝ๋๊ฐ ์๋นํ ๋ถ์์ ํฉ๋๋ค. ์บ์ฑ์ ์ํฅ์ธ์ง CAS์ฐ์ฐ์ ๋๋ฐํ๋ ๋ฉ์๋๋ ์์ ์ ์ผ๋ก ํต๊ณผํ์ง๋ง CAS์ฐ์ฐ์ด ์๋ ํ ์คํธ๋ ๊ฐ๊ฐ ๋ฐ๋ก ์งํํ๋ฉด ํต๊ณผํ์ง๋ง ์ ์ฒด์ ์ผ๋ก ๋๋ฆฌ๋ฉด ๋งค๋ฒ ํ ์คํธ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ ธ ์คํจํ ํ ์คํธ ์ฝ๋๋ผ๊ณ ํ ์ ์๊ฒ ์ต๋๋ค. ๋ ๋ฐฉ๋ฒ์ ์ฐพ์๋ณด๊ฒ ์ต๋๋ค.