JAVA 동기화 메커니즘(1)

 

아래 코드는 멀티스레드 환경에서 심각한 데이터 불일치 문제를 일으킬 수 있습니다. 본 포스트에서는 Java의 다양한 동기화 메커니즘을 통해 이러한 문제를 해결하는 방법을 알아보겠습니다.

1
2
3
4
5
6
7
8
9
10
11
public class ThreadUnsafeCounter {
    private int count = 0;
    public void increment() {
        count++// 이 연산은 원자적이지 않습니다!
    }
    public int getCount() {
        return count;
    }
}
cs

 

1. synchronized 키워드

Java에서 가장 기본적인 동기화 메커니즘은 synchronized 키워드입니다.

메소드 동기화

1
2
3
4
5
6
7
8
9
10
11
public class SynchronizedCounter {
    private int count = 0;
    public synchronized void increment() {
        count++;
    }
    public synchronized int getCount() {
        return count;
    }
}
cs

 

블록 동기화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class BlockSynchronizedCounter {
    private int count = 0;
    private final Object lock = new Object(); // 명시적 락 객체
    public void increment() {
        synchronized(lock) {
            count++;
        }
    }
    public int getCount() {
        synchronized(lock) {
            return count;
        }
    }
}
cs

성능 비교 및 분석

synchronized의 주요 특징:

  • 내부적으로 모니터(monitor)를 사용하여 상호 배제를 구현
  • Java 6 이후 적응형 락(adaptive lock) 메커니즘 도입으로 성능 개선
  • 단순하지만 경쟁 상황에서 성능 저하 가능성 존재

 

2. volatile 키워드

volatile은 가시성(visibility) 문제를 해결하지만 원자성(atomicity)은 보장하지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
public class VolatileExample {
    private volatile boolean flag = false;
    public void setFlag() {
        flag = true// 다른 스레드에 즉시 가시적
    }
    public boolean isFlag() {
        return flag;
    }
}
cs

 

3. java.util.concurrent.atomic 패키지

원자적 연산을 위한 특수 클래스들을 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
    private final AtomicInteger count = new AtomicInteger(0);
    public void increment() {
        count.incrementAndGet(); // 원자적 연산
    }
    public int getCount() {
        return count.get();
    }
}
cs

성능 측정 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class SynchronizationBenchmark {
    public static void main(String[] args) throws InterruptedException {
        final int THREADS = 10;
        final int ITERATIONS = 1_000_000;
        // 다양한 카운터 유형 테스트
        ThreadUnsafeCounter unsafeCounter = new ThreadUnsafeCounter();
        SynchronizedCounter syncCounter = new SynchronizedCounter();
        AtomicCounter atomicCounter = new AtomicCounter();
        // Atomic 카운터 벤치마크
        long start = System.nanoTime();
        testCounter(atomicCounter, THREADS, ITERATIONS);
        long end = System.nanoTime();
        System.out.println(“Atomic time: “ + (end  start) / 1_000_000 + “ms”);
        System.out.println(“Atomic final count: “ + atomicCounter.getCount());
        // 더 많은 벤치마크 코드…
    }
    private static void testCounter(Counter counter, int threads, int iterations)
            throws InterruptedException {
        Thread[] threadArray = new Thread[threads];
        for (int i = 0; i < threads; i++) {
            threadArray[i] = new Thread(() > {
                for (int j = 0; j < iterations; j++) {
                    counter.increment();
                }
            });
        }
        for (Thread t : threadArray) t.start();
        for (Thread t : threadArray) t.join();
    }
    interface Counter {
        void increment();
        int getCount();
    }
}
cs

 

4. Lock 인터페이스

java.util.concurrent.locks 패키지의 Lock 인터페이스는 synchronized보다 유연한 락 메커니즘을 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockCounter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();
    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock(); // 반드시 unlock 호출
        }
    }
    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}
cs

 

ReentrantLock vs synchronized

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class LockPerformanceTest {
    private static final int ITERATIONS = 1_000_000;
    public static void main(String[] args) {
        // ReentrantLock 테스트
        ReentrantLock lock = new ReentrantLock();
        long startLock = System.nanoTime();
        for (int i = 0; i < ITERATIONS; i++) {
            lock.lock();
            try {
                // 간단한 연산
            } finally {
                lock.unlock();
            }
        }
        long endLock = System.nanoTime();
        // synchronized 테스트
        Object syncObj = new Object();
        long startSync = System.nanoTime();
        for (int i = 0; i < ITERATIONS; i++) {
            synchronized(syncObj) {
                // 동일한 간단한 연산
            }
        }
        long endSync = System.nanoTime();
        System.out.println(“ReentrantLock time: “ + (endLock  startLock) / 1_000_000 + “ms”);
        System.out.println(“Synchronized time: “ + (endSync  startSync) / 1_000_000 + “ms”);
    }
}
cs

 

5. ReadWriteLock

읽기 작업이 많고 쓰기 작업이 적은 경우에 유용한 동기화 메커니즘입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteCounter {
    private int count = 0;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    public void increment() {
        lock.writeLock().lock(); // 쓰기 락
        try {
            count++;
        } finally {
            lock.writeLock().unlock();
        }
    }
    public int getCount() {
        lock.readLock().lock(); // 읽기 락 (여러 스레드가 동시에 읽기 가능)
        try {
            return count;
        } finally {
            lock.readLock().unlock();
        }
    }
}
cs

 

 

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다