[JAVA] synchronized와 static synchronized의 차이점(block, method)

synchronized method

해당 메서드를 호출하는 객체에 락을 사용하여 동기화됩니다. 이렇게 하면 해당 객체에 대해 하나의 스레드만 메서드를 실행할 수 있습니다.

public class SynchronizedClass {
	public synchronized void start(String threadName) {
		System.out.println(threadName + " lock");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(threadName + " unlock");
	}
}

인스턴스가 1개인 경우

public class Main {
	public static void main(String[] args) {
		SynchronizedClass sc = new SynchronizedClass();
				
		Thread threadA = new Thread(() -> {
			sc.start("threadA");
		});
		
		Thread threadB = new Thread(() -> {
			sc.start("threadB");
		});
		
		threadA.start();
		threadB.start();
	}
}

결과 : 이럴 경우 synchronized method는 인스턴스 단위로 동기화가 되기 때문에 위에 경우에는 밑에 처럼 나옵니다.

threadA lock
threadA unlock
threadB lock
threadB unlock

인스턴스가 2개인 경우

public class Main {
	public static void main(String[] args) {
		SynchronizedClass sc = new SynchronizedClass();
        SynchronizedClass sc2 = new SynchronizedClass();
				
		Thread threadA = new Thread(() -> {
			sc.start("threadA");
		});
		
		Thread threadB = new Thread(() -> {
			sc2.start("threadB");
		});
		
		threadA.start();
		threadB.start();
	}
}

결과: thread A,B 호출은 실행때마다 달라집니다.

threadB lock
threadA lock
threadA unlock
threadB unlock

static synchronized method

해당 메서드를 호출하는 클래스에 락을 사용하여 동기화됩니다. 따라서 이 클래스의 모든 인스턴스에 대해 하나의 스레드만 메서드를 실행할 수 있습니다.

public class StaticSynchronizedClass {
	public static synchronized void start(String threadName) {
		System.out.println(threadName + " lock");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(threadName + " unlock");
	}
}
public class Main {
	public static void main(String[] args) {
		StaticSynchronizedClass sc = new StaticSynchronizedClass();
		StaticSynchronizedClass sc2 = new StaticSynchronizedClass();
				
		Thread threadA = new Thread(() -> {
			sc.start("threadA");
		});
		
		Thread threadB = new Thread(() -> {
			sc2.start("threadB");
		});
		
		threadA.start();
		threadB.start();
	}
}

2개의 인스턴스를 만들어서 Thread를 돌려도 static synchronized는 클래스 단위로 동기화가 되기 때문에 아래와 같은 결과가 나옵니다.

threadA lock
threadA unlock
threadB lock
threadB unlock

synchronized block

특정 코드 블록에 동기화를 적용합니다. 이 경우, 해당 객체 단위의 락을 사용합니다.

public class SynchronizedBlockClass {
	public void start(String threadName) {
		System.out.println(threadName + " start");
		synchronized (this) {
			System.out.println(threadName + " lock");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(threadName + " unlock");
		}
		System.out.println(threadName + " end");
	}
}

this를 이용하여 synchronized block에 인스턴스 단위로 사용되게끔 만듭니다.

public class Main {
	public static void main(String[] args) {
		SynchronizedBlockClass sc = new SynchronizedBlockClass();
				
		Thread threadA = new Thread(() -> {
			sc.start("threadA");
		});
		
		Thread threadB = new Thread(() -> {
			sc.start("threadB");
		});
		
		threadA.start();
		threadB.start();
	}
}

같은 인스턴스를 사용하는 Thread 2개는 synchronized block을 만나기 전까지 각자 활동하다가 block에 들어서는 순간 동기화가 발생합니다. 1개의 스레드가 synchronized block을 빠져나오면 다른 스레드 1개가 다시 synchronized block으로 들어갑니다.

threadA start
threadB start
threadA lock
threadA unlock
threadA end
threadB lock
threadB unlock
threadB end

static synchronized block

static 블록 안에 synchronized block를 사용하는 경우 클래스에 락을 설정하여 동기화를 적용합니다.

static으로 블록을 설정하기 때문에 this를 인자로 사용할 수 없고 클래스를 인자로 사용합니다.

public class StaticSynchronizedBlockClass {
	public static void start(String threadName) {
		System.out.println(threadName + " start");
		synchronized (SynchronizedBlockClass.class) {
			System.out.println(threadName + " lock");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(threadName + " unlock");
		}
		System.out.println(threadName + " end");
	}
}

이 경우 static method이기 때문에 따로 인스턴스를 만들 필요없이 클래스 method로 바로 호출합니다.

public class Main {
	public static void main(String[] args) {				
		Thread threadA = new Thread(() -> {
			StaticSynchronizedBlockClass.start("threadA");
		});
		
		Thread threadB = new Thread(() -> {
			StaticSynchronizedBlockClass.start("threadB");
		});
		
		threadA.start();
		threadB.start();
	}
}

static synchronized block은 클래스 단위로 동기화 되기때문에 인스턴스 1개 생성한 synchronized block와 동일한 결과가 나옵니다.

threadB start
threadA start
threadB lock
threadB unlock
threadB end
threadA lock
threadA unlock
threadA end