๋ฉํฐ ์ฐ๋ ๋๋ ํ๋ก๊ทธ๋จ์ ์ฑ๋ฅ์ ํฅ์์ํค๋ ๊ฐ์ฅ ํ์คํ ๋ฐฉ๋ฒ์ด๋ค. ํ์ง๋ง ์ฌ๋ฌ ์ฐ๋ ๋๊ฐ ์์์ ๊ณต์ ํ๋ค๋ฉด ์๊ธฐ์น ์์ ๋ฌธ์ ๋ฅผ ๋ฐ์์ํฌ ์ ์๋ค.
Java์์ ์ด๋ป๊ฒ Thread-Safeํ๊ฒ ๊ฐ๋ฐํ ์ ์๋๋ก ์ง์์ ํ๋์ง, ๋ค์ํ ๋ฐฉ๋ฒ๋ค์ ๋ํด ๊ฐ๋จํ ์ฝ๋์ ํจ๊ป ์์๋ณด๊ณ
์ค์ ๊ฐ๋ฐ ์ค์ด๋ Thread-UnSafeํ ์ฝ๋๋ฅผ ์์ ํด๋ณผ ๊ฒ์ด๋ค.
1. Thread Safe (์ฐ๋ ๋ ์ธ์ดํ) ๋?
- ๋ฉํฐ ์ฐ๋ ๋ ํ๋ก๊ทธ๋๋ฐ์์, ์ด๋ค ๊ณต์ ์์์ ์ฌ๋ฌ ์ฐ๋ ๋๊ฐ ๋์์ ์ ๊ทผํด๋, ํ๋ก๊ทธ๋จ ์คํ์ ๋ฌธ์ ๊ฐ ์๋ ์ํ๋ฅผ ์๋ฏธํ๋ค.
- Race Condition ์ํฉ์ด ๋ฐ์ํด๋ ์คํ์ ๋ฌธ์ ๊ฐ ์๊ธฐ์ง ์๋ ๊ฒ์ ์๋ฏธํ๋ค.
- Race Condition ์ด๋?
- ๊ฒฝ์ ์ํ๋ก, ๊ณต์ ์์์ ๋ํด ๋ ๊ฐ ์ด์์ ์ค๋ ๋๊ฐ ๋์์ ์ฝ๊ฑฐ๋ ์ฐ๋ ์ํฉ์ ์๋ฏธํ๋ค.
Thread Safe๋ฅผ ์งํค๋ ๋ฐฉ๋ฒ์ผ๋ก ํฌ๊ฒ 4๊ฐ์ง๊ฐ ์๋ค.
1. Re-entrancy(์ฌ์ง์ ์ฑ)
- ์ ์ (์ ์ญ) ๋ณ์๋ฅผ ์ฌ์ฉํ๋ฉด ์ ๋๋ค.
- ์ ์ (์ ์ญ) ๋ณ์์ ์ฃผ์๋ฅผ ๋ฐํํ๋ฉด ์ ๋๋ค.
- ํธ์ถ์๊ฐ ํธ์ถ ์ ์ ๊ณตํ ๋งค๊ฐ ๋ณ์๋ง์ผ๋ก ๋์ํด์ผ ํ๋ค.
- ์ฑ๊ธํค ๊ฐ์ฒด์ ์ ๊ธ์ ์์กดํ๋ฉด ์ ๋๋ค.
- ๋ค๋ฅธ ๋น-์ฌ์ง์ ํจ์๋ฅผ ํธ์ถํ๋ฉด ์ ๋๋ค.
2. Thread-local Storage
- ๊ณต์ ์์์ ์ฌ์ฉ์ ์ต๋ํ ์ค์ด๊ณ ๊ฐ๊ฐ์ Thread์์๋ง ์ ๊ทผ ๊ฐ๋ฅํ ์ ์ฅ์๋ฅผ ์ฌ์ฉํ์ฌ ๋์ ์ ๊ทผ์ ๋ง๋ ๊ฒ
3. Mutual Exclusion
- Lock์ ํตํด ๊ณต์ ์์์ ์ ์ดํ๋ ๊ฒ
- ๋ฎคํ ์ค, ์ธ๋งํฌ์ด ๋ฑ์ ์ด์ฉํ๋ค.
4. Atomic Operation Atomic
- ์ฐ์ฐ์ ์ฌ์ฉํ๋ ๊ฒ
- ๋์ด์ ์ชผ๊ฐค ์ ์๋ ๊ฐ์ฅ ์์ ๋จ์์ ๋ช ๋ น์ด๋ฅผ ์คํํ๋ ๊ฒ
- ๋์ค์ ์ธํฐ๋ฝํธ๊ฐ ๋ฐ์ํ ์ ์๋ค.
2. Thread-Safe๊ฐ ์ง์ผ์ง์ง ์์ ๋
์ฝ๋๋ฅผ Thread-Safeํ๊ฒ ์์ฑํ์ง ์์ผ๋ฉด ์ด๋ป๊ฒ ๋๋์ง ์ดํด๋ณด๊ธฐ ์ํด ์๋์ ๊ฐ์ ๊ฐ๋จํ ์ฝ๋๋ฅผ ์์ฑํ์๋ค.
์๋ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ๋ค.
- ์ต์ 100๋ช ์ ์์ฝ์ด ์ฐจ์ผ์ง ๋ฒ์ค๋ฅผ ์ดํํ๋ค.
- 100๋ช ์ดํ์ผ ๊ฒฝ์ฐ, ๊ฒฝ๊ณ ๋ฉ์ธ์ง์ ํจ๊ป ํ์ฌ ์์ฝ ์ธ์์๋ฅผ ๋ณด์ฌ์ค๋ค.
@Getter
public class Bus {
private int minOccupancy = 100;
private int reservation = 0;
public void getBusTicket() {
try {
Thread.sleep(100);
reservation++;
if (reservation <= minOccupancy) {
Thread.sleep(100);
System.out.println("์ต์ ์ธ์์ธ 100๋ช
์ ๋์ด์ผ ํฉ๋๋ค. ํ์ฌ ์์ฝ ์ธ์: " + reservation);
}
} catch (InterruptedException e) {
System.out.println("ERROR!");
}
}
}
๋ง์ฝ 500๋ช ์ด ๋์์ ๋ฒ์ค๋ฅผ ์๋งคํ๋ค๊ณ ์๊ฐํด๋ณด์.
100๋ช ์ดํ์ผ ๋๋ "์ต์ ์ธ์์ธ 100๋ช ์ ๋์ด์ผ ํฉ๋๋ค. ํ์ฌ ์์ฝ ์ธ์: x" ๋ผ๋ ๋ฌธ๊ตฌ๊ฐ ์ถ๋ ฅ๋์ผ ํ๊ณ ,
์ต์ข ์์ฝ ์ธ์์ 500๋ช ์ด์ด์ผ ํ๋ค.
class BusTest {
private final ExecutorService THREAD_POOL = Executors.newFixedThreadPool(100);
@Test
void ๋ค์์_์ฌ๋๋ค์ด_๋์์_๋ฒ์คํฐ์ผ์_๊ตฌ๋งคํ๋ค() throws InterruptedException {
int N = 500;
CountDownLatch latch = new CountDownLatch(N);
Bus bus = new Bus();
for (int i = 0; i < N; i++) {
THREAD_POOL.execute(() -> {
bus.getBusTicket();
latch.countDown();
});
}
latch.await();
int busReservation = bus.getReservation();
System.out.println("======= Total reservation: " + busReservation + " =======");
assertThat(busReservation).isNotEqualTo(N);
}
}
@Test
void ๋ค์์_์ฌ๋๋ค์ด_๋์์_๋ฒ์คํฐ์ผ์_๊ตฌ๋งคํ๋ค() throws InterruptedException {
int N = 500;
CountDownLatch latch = new CountDownLatch(N);
Bus bus = new Bus();
for (int i = 0; i < N; i++) {
THREAD_POOL.execute(() -> {
bus.getBusTicket();
latch.countDown();
});
}
latch.await();
int busReservation = bus.getReservation();
System.out.println("======= Total reservation: " + busReservation + " =======");
assertThat(busReservation).isEqualTo(N);
}
ํ์ง๋ง ๊ฒฐ๊ณผ๋ ์๋์ ๊ฐ์ด ๋์๋ค.
1. 100๋ช ์ด์์ผ ๋๋ ๋ฌธ๊ตฌ๊ฐ ์ถ๋ ฅ๋ ์ด์
1๋ฒ ์ฐ๋ ๋๊ฐ if๋ฌธ์ ํต๊ณผํ ์์ ์ 2๋ฒ ์ฐ๋ ๋๊ฐ reservation ์์ฝ ํ๋์ ์ ๊ทผํด ++ ์ ์ํํ์ ์๋ ์๊ธฐ ๋๋ฌธ์ด๋ค.
2. ์ต์ข ๊ตฌ๋งค ์ธ์์ด 486๋ช
ํ์ฌ reservation ๊ฐ์ด n ์ด๋ผ ๊ฐ์ ํ๋ฉด ์ฐ๋ ๋ 1, 2 ๊ฐ ++์ ํ๊ธฐ ์ํด ๊ฐ์ ๊ฐ์ ธ์ค๊ฒ ๋๋ค.
์ฐ์ฐ์ ์ํํ๊ณ ๋๋ฉด
์ฐ๋ ๋1: n+1
์ฐ๋ ๋2: n+1
์ ๊ฐ์ผ๋ก ์
๋ฐ์ดํธํ๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก n+2๊ฐ ์๋ n+1์ด ๋๋ ๊ฒ์ด๋ค.
๊ฒฐ๊ตญ ์ด ๋ ๊ฐ์ง ์ํฉ ๋ชจ๋ Race Condition(๊ฒฝ์ ์ํ)๊ฐ ๋ฐ์ํ ๊ฒ์ผ๋ก ๋ณผ ์ ์๋ค. Race Condition์ ๋ ๊ฐ ์ด์์ ์ค๋ ๋๊ฐ ๋์์ ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๊ณต์ ์์์ ์ ๊ทผํ๊ณ ์ด๋ฅผ ๋ณ๊ฒฝํ๋ ค๊ณ ํ ๋ ๋ฐ์ํ๋ค.
์ค๋ ๋ ์ค์ผ์ฅด๋ง ์๊ณ ๋ฆฌ์ฆ์ ์ํด ์ค๋ ๋๋ ์ธ์ ๋ swap ๋ ์ ์๊ธฐ ๋๋ฌธ์ ์ด๋ ์์ ์ ์ด๋ค ์์๋ก ์ค๋ ๋๊ฐ ๊ณต์ ์์์ ์ ๊ทผํ ์ง ์ ์ ์๋ค.
๋ฐ๋ผ์, ์ค๋ ๋ ์ค์ผ์ฅด๋ง ์๊ณ ๋ฆฌ์ฆ์ด ๊ฒฐ์ ํ๋ ์ค๋ ๋์ ์ ๊ทผ ์์ ๊ณผ ์์์ ๋ฐ๋ผ ์คํ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๊ฒ ๋๋ค.
๊ทธ๋ ๋ค๋ฉด Java์์๋ ์ด๋ป๊ฒ Thread-Safeํ ์ฝ๋๋ฅผ ์์ฑํ ์ ์์๊น?
3. ํด๊ฒฐ ๋ฐฉ์
- Synchronized
์๋ฐ์ ๋ชจ๋ ๊ฐ์ฒด๋ ํ๋์ Monitor๋ฅผ ๊ฐ๊ณ ์๋ค. Monitor๋ ์ํธ ๋ฐฐ์ (mutual exclusion)์ ํ๋ ฅ(cooperation) ๋ ๊ฐ์ง ์ข ๋ฅ์ ์ค๋ ๋ ๋๊ธฐํ๋ฅผ ์ง์ํ๋ค.
์ํธ ๋ฐฐ์ ๋ Lock์ ํตํด ๋ค์์ ์ค๋ ๋๊ฐ ๊ณต์ ์์์ ๋ํด ๋ ๋ฆฝ์ ์ผ๋ก ์ฐ์ฐ์ ์ํํ ์ ์๋๋ก ํ๋ ๊ฒ์ด๋ฉฐ,
ํ๋ ฅ์ wait์ notify ๋ฉ์๋๋ฅผ ํตํด ์ค๋ ๋๊ฐ ์ํธ ํ๋ ฅํ๋๋ก ํ๋ ๊ฒ์ด๋ค.
์๋ฅผ ๋ค์ด ๋ฒํผ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด์ค๋ ์ค๋ ๋์ ๋ฒํผ์ ๋ฐ์ดํฐ๋ฅผ ์ฐ๋ ์ค๋ ๋๊ฐ ์๋ค๊ณ ํ ๋, ๋ฒํผ๊ฐ ๋น์ด ์๋ค๋ฉด ๋ฒํผ์ ๋ฐ์ดํฐ๋ฅผ ์ฐ๋ ์ค๋ ๋๊ฐ ๋ฐ์ดํฐ๋ฅผ ์ฑ์ธ ๋๊น์ง waitํ๊ณ , ํด๋น ์์ ์ด ์ข ๋ฃ๋๋ฉด ๋ค์ ์ฝ๊ธฐ๋ฅผ ์์ํ๋ ๊ฒ์ด๋ค.
์๋ฐ์ synchrnoized ํค์๋๋ ์ค๋ ๋ ๊ฐ ๋๊ธฐํ๋ฅผ ์ํด ์ฌ์ฉ๋๋ ๋ํ์ ์ธ ๊ธฐ๋ฒ์ด๋ค.
synchronized ํค์๋๋ ๊ฐ์ฒด ์์ ์กด์ฌํ๋ Monitor๋ฅผ ์ด์ฉํด ๋๊ธฐํ๋ฅผ ์ํํ๋ค. ํ๋์ ์ค๋ ๋๊ฐ synchronized๋ก ์ง์ ํ ์๊ณ ์์ญ์ ๋ค์ด๊ฐ ์์ ๋ Lock์ด ๊ฑธ๋ฆฌ๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์๊ณ ์์ญ์ ์ ๊ทผํ ์ ์๋ค. ์ดํ ํด๋น ์ค๋ ๋๊ฐ ์๊ณ ์์ญ์ ์ฝ๋๋ฅผ ๋ชจ๋ ์คํํ ๋ค ๋ฒ์ด๋๋ฉด unlock ์ํ๊ฐ ๋์ด ๋๊ธฐํ๊ณ ์๋ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์ด ์๊ณ์์ญ์ ์ ๊ทผํด ๋ค์ Lock์ ๊ฑธ์ด ์ฌ์ฉํ ์ ์๋ค.
์์ ์งํํ๋ ํ ์คํธ๋ฅผ synchronized๋ฅผ ์ฌ์ฉํด์ ๋ค์ ์งํํด๋ณด์๋ค.
@Getter
public class Bus {
private int minOccupancy = 100;
private int reservation = 0;
public synchronized void getBusTicket() {
try {
Thread.sleep(100);
reservation++;
if (reservation <= minOccupancy) {
Thread.sleep(100);
System.out.println("์ต์ ์ธ์์ธ 100๋ช
์ ๋์ด์ผ ํฉ๋๋ค. ํ์ฌ ์์ฝ ์ธ์: " + reservation);
}
} catch (InterruptedException e) {
System.out.println("ERROR!");
}
}
}
@Test
void ๋ค์์_์ฌ๋๋ค์ด_๋์์_๋ฒ์คํฐ์ผ์_๊ตฌ๋งคํ๋ค() throws InterruptedException {
int N = 500;
CountDownLatch latch = new CountDownLatch(N);
Bus bus = new Bus();
for (int i = 0; i < N; i++) {
THREAD_POOL.execute(() -> {
bus.getBusTicket();
latch.countDown();
});
}
latch.await();
int busReservation = bus.getReservation();
System.out.println("======= Total reservation: " + busReservation + " =======");
assertThat(busReservation).isEqualTo(N);
}
๊ฒฐ๊ณผ๋ ์๋์ฒ๋ผ ๋์จ๋ค.
synchronized๋ฅผ ์ฌ์ฉํ๋ฉด ์์ฝ๊ฒ Thread-Safeํ ํ๊ฒฝ์ ๋ง๋ค ์ ์์ง๋ง ๋๋ฆฌ๋ค. ์๊ณ์์ญ์ ์ฝ๋๋ ํ๋์ ์ฐ๋ ๋์ฉ๋ง ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
- Atomic Objects
์๋ฐ๋ AtomicInteger, AtomicLong, AtomicBoolean ๋ฑ์ atomic ํด๋์ค๋ฅผ ์ ๊ณตํ๋ค.
atomic ํด๋์ค๋ ๋ฉํฐ ์ค๋ ๋ ํ๊ฒฝ์์ ์์์ฑ์ ๋ณด์ฅํด์ค๋ค. ๋ํ, ์์์ ์ดํด๋ณธ synchronized์๋ ๋ค๋ฅด๊ฒ blocking์ด ์๋ non-blocking ๋ฐฉ์์ผ๋ก ๋์ํ๋ฉฐ ํต์ฌ ๋์ ์๋ฆฌ๋ CAS(Compare And Swap) ์๊ณ ๋ฆฌ์ฆ์ด๋ค.
@Getter
public class Bus {
private int minOccupancy = 100;
private AtomicInteger reservation = new AtomicInteger();
public synchronized void getBusTicket() {
try {
Thread.sleep(100);
int newReservation = reservation.incrementAndGet();
if (newReservation <= minOccupancy) {
Thread.sleep(100);
System.out.println("์ต์ ์ธ์์ธ 100๋ช
์ ๋์ด์ผ ํฉ๋๋ค. ํ์ฌ ์์ฝ ์ธ์: " + reservation);
}
} catch (InterruptedException e) {
System.out.println("ERROR!");
}
}
}
์์์ ๋ดค๋ synchronized๋ ๋น๊ตํ๊ธฐ ์ํด ํ ์คํธ ์ฝ๋ ์ํ ์๊ฐ์ ๋จ๊ฒผ๋ค.
@Test
void ๋ค์์_์ฌ๋๋ค์ด_๋์์_๋ฒ์คํฐ์ผ์_๊ตฌ๋งคํ๋ค() throws InterruptedException {
long startTime = System.currentTimeMillis();
int N = 500;
CountDownLatch latch = new CountDownLatch(N);
Bus bus = new Bus();
for (int i = 0; i < N; i++) {
THREAD_POOL.execute(() -> {
bus.getBusTicket();
latch.countDown();
});
}
latch.await();
int busReservation = bus.getReservation().get();
System.out.println("======= Total reservation: " + busReservation + " =======");
long endTime = System.currentTimeMillis();
System.out.println("์ด ๊ฑธ๋ฆฐ ์๊ฐ: " + (endTime - startTime) + " milliseconds.");
assertThat(busReservation).isEqualTo(N);
}
์ํ๋ ๊ฒฐ๊ณผ๊ฐ ๋์๋ค.
Atomic์ด ์ด๋ค ์ญํ ์ ํด์คฌ๊ธธ๋ Thread-Safeํ ์ฝ๋๊ฐ ๋์๊น??
์๋ฐ๋ก ์์ฑ๋ ํ ์ค์ ์ฐ์ฐ์ธ reservation++์ ๋ณ์์ ๊ฐ์ ์ฝ๊ณ , ์ฐ์ฐํ ๋ค ์ ์ฅํ๋ ์ธ ์ค์ ๊ธฐ๊ณ์ด๋ก ์ชผ๊ฐ์ง ์ ์๊ณ , ๋ฉํฐ ์ค๋ ๋ ํ๊ฒฝ์์ ๊ธฐ๊ณ์ด ์ฐ์ฐ๋ค์ด ํ ๋ฒ์ ๋ชจ๋ ์คํ๋ ๊ฑฐ๋ผ๋ ๋ณด์ฅ์ด ์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ฐ์ ์ฝ๊ณ ์ฐ์ฐํ๋ ๊ธฐ๊ณ์ด๊น์ง ์คํํ๋๋ฐ ๋ค๋ฅธ ์ค๋ ๋๋ก swap ๋๋ ์ํฉ์ ์๊ฐํ๋ฉด ๋๋ค.
atomic ํด๋์ค๋ ์์์ฑ์ ๋ณด์ฅํ๊ธฐ ๋๋ฌธ์ ๋ฉํฐ ์ค๋ ๋ ํ๊ฒฝ์์ ์์ ํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
๋ฌด์๋ณด๋ค ์ํ ์๊ฐ์ด 667ms๋ก ๊ต์ฅํ ๋นจ๋๋ค.
synchronized ๋ฅผ ์ฌ์ฉํด์ ํ ์คํธ๋ฅผ ์งํํ๋.. ๋ฌด๋ ค 64031ms ๊ฐ ๊ฑธ๋ ธ๋ค.
ํ๋์ ํ๋๋ฅผ ๊ณต์ ์์์ผ๋ก ๊ฐ๋๋ค๋ฉด Atomic ์ ์ฌ์ฉํ๋ ๊ฒ ์ข์๋ณด์๋ค. ํ์ง๋ง ํ๋๊ฐ ์๋ ๋ก์ง์ด Thread-Safeํด์ผ ํ๋ค๋ฉด ๋๋ฆฌ์ง๋ง synchronized๋ฅผ ์ฌ์ฉํด์ผ ํ์ง ์์๊น ์๊ฐ์ด ๋ ๋ค.
'Spring Boot, JAVA ๐ฑ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋์์ฑ ๋ฌธ์ ์ ๋ํ ๊ณ ๋ฏผ๊ณผ ํด๊ฒฐ ๊ณผ์ (0) | 2024.04.23 |
---|---|
Spring Boot ์ ๋ค์ค ์ ์ ์์ฒญ ์ฒ๋ฆฌ ๋ฐฉ๋ฒ (Tomcat) (3) | 2024.04.19 |
DTO ๊ด๋ฆฌ์ ๋ํ ๊ณ ๋ฏผ๊ณผ ๋ฆฌํํ ๋ง (0) | 2024.04.16 |
[Spring Batch] Paging ๊ธฐ๋ฐ์ ItemReader ์ฌ์ฉ ์ ๋ฐ์ํ๋ ๋ฐ์ดํฐ ๊ฑด๋๋ฐ๊ธฐ ํ์ (ํด๊ฒฐ ๋ฐฉ๋ฒ 2๊ฐ์ง) (0) | 2024.03.15 |
[Spring Batch] ItemReader cursor vs paging (0) | 2024.03.11 |