Synchronize(동기화)와 Deadlock
Synchronized 키워드와 동기화
synchronized 란?
멀티쓰레딩 환경에서 공유 자원(Shared Resource) 에 대한 동기화(Synchronization)를 제공 하는 키워드임.
여러 쓰레드가 동시에 공유 데이터에 접근하면 경쟁 조건(Race Condition) 이 발생할 수 있음.
이를 방지하기 위해 임계 구역(Critical Section) 을 설정하여 한 번에 하나의 쓰레드만 해당 블록을 실행할 수 있도록 함.
synchronizeed 사용방법
Java 에서
synchronized
는 메서드 단위 또는 블록 단위 로 사용할 수 있음.
synchronized
메서드
class SharedResource {
private int count = 0;
public synchronized void increament() {
count++;
}
public synchronized int getCount() {
return count;
}
}
위와 같이
synchronized
키워드를 메서드에 사용하면 해당 메서드가 실행되는 동안 다른 스레드는 이 메서드를 실행할 수 없음즉,
increment()
메서드가 실행되는 동안 다른 스레드는increment()
나getCount()
를 호출할 수 없음.
synchronized
블록
class SharedResource {
private int count = 0;
private final Object lock = new Object(); // 별도의 Lock 객체 생성
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
특정 객체(
lock
) 에 대해 동기화 블록을 설정하면 해당 객체를 사용하는 다른 synchronized 블록들이 같은 객체에 접근하지 못하게 제한 함.synchronized
블록을 사용하면 동기화 범위를 줄일 수 있어 성능 최적화 가 가능함.
정적 메서드의
synchronized
class SharedResource {
private static int count = 0;
public static synchronized void increment() {
count++;
}
public static synchronized int getCount() {
return count;
}
}
정적 메서드에
synchronized
를 적용하면 클래스 레벨의 동기화 가 수행됨.즉,
SharedResource.class
자체가 락을 가지므로 해당 클래스의 모든 인스턴스가 공유하는 자원에 대한 동기화가 보장 됨.
synchronized 와 Deadlock(교착 상태)
Deadlock(교착 상태) 란?
Deadlock(교착 상태)이란 두 개 이상의 스레드가 서로가 가진 리소스를 기다리면서 영원히 멈춰있는 상태 를 말함.
Deadlock 조건
상호 배제(Mutual Exclusion) : 한 번에 하나의 스레드만 공유 자원을 사용할 수 있어야 한다.
점유 대기(Hold and Wait) : 하나의 쓰레드가 자원을 점유한 상태에서 다른 자원을 기다려야 한다.
비선점(No Preemption) : 점유한 자원을 다른 쓰레드가 강제로 가져갈 수 없어야 한다.
순환 대기(Circular Wait) : 두 개 이상의 스레드가 서로를 기다리는 형태의 순환 구조가 존재해야 한다.
Deadlock 발생 예제
아래 코드에서는 Thread-1
과 Thread-2
가 각각 다른 락을 가지고 있으며, 서로 상대방이 가진 락을 기다리는 상태 가 되어 Deadlock 이 발생할 수 있음.
class Resource {
public void print(String message) {
System.out.println(message);
}
}
class Task implements Runnable {
private final Resource resource1;
private final Resource resource2;
public Task(Resource resource1, Resource resource2) {
this.resource1 = resource1;
this.resource2 = resource2;
}
@Override
public void run() {
synchronized (resource1) {
resource1.print(Thread.currentThread().getName() + " locked resource1");
try{
Thread.sleep(100);
}catch(InterruptedException e){}
synchronized(resource2) {
resource2.print(Thread.currentThread().getName() + " locked resource2";
}
}
}
}
public class DeadlockExample {
public static void main(String[] args) {
Resource resourceA = new Resource();
Resource resourceB = new Resource();
Thread thread1 = new Thread(new Task(resourceA, resourceB), "Thread-1");
Thread thread2 = new Thread(new Task(resourceB, resourceB), "Thread-2");
thread1.start();
thread2.start();
}
}
설명
Thread-1
은resourceA
를 먼저 획득한 후resourceB
를 기다림.Thread-2
는resourceB
를 먼저 획득한 후resourceA
를 기다림.결과적으로 두 쓰레드는 서로 상대방이 가진 자원을 기다리며 Deadlock 이 발생하게됨.
Deadlock 해결 방법
락을 항상 일정한 순서로 획득하기
synchronized (resourceA) {
synchronized (resourceB) {
// 작업 수행
}
}
모든 쓰레드가
resourceA
->resourceB
순서로 락을 요청하면 Deadlock 이 발생하지 않음.
tryLock()
을 사용하여 시간 초과 설정하기 (ReentrantLock 활용)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class SafeTask implements Runnable {
private final Lock lock1;
private final Lock lock2;
public SafeTask(Lock lock1, Lock lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
while(true) {
try{
if(lock1.tryLock() && lock2.tryLock()){
try{
System.out.println(Thread.currentThread().getName() + " acquired both locks";
break;
} finally {
lock2.unlock();
lock1.unlock();
}
}
} finally {
if(lock1.isHeldByCurrentThread()) lock1.unlock();
if(lock2.isHeldByCurrentThread()) lock2.unlcok();
}
}
}
}
public class DeadlockPrevention {
public static void main(String[] args) {
Lock lockA = new ReentrantLock();
Lock lockB = new ReentrantLock();
Thread thread1 = new Thread(new SafeTask(lockA, lockB), "Thread-1");
Thread thread2 = new Thread(new SafeTask(lockB, lockA), "Thread-2");
thread1.start();
thread2.start();
}
}
tryLock()
을 사용하면 특정 시간 동안만 락을 대기하고, 시간이 지나면 락을 포기함.
3.Deadlock 감지 및 해결 알고리즘 사용
탐욕적 알고리즘 : 점유한 락을 주기적으로 확인하여 일정 시간 이상 유지되면 해제하는 방법.
Wait-for Graph 알고리즘 : 시스템의 락 대기 그래프를 분석하여 사이클이 발생하면 Deadlock 을 감지하는 방법
Last updated
Was this helpful?