Inor

[JAVA] Synchronized(동기화) 본문

Computer Engineering/Java

[JAVA] Synchronized(동기화)

Inor 2017. 7. 20. 17:54

Synchronized


 자바의 Thread Class의 사용 방법에대해 학습 하면서 간단하게 synchronized 키워드를 알아봤습니다. 동기화가 필요한 경우에 사용하는 키워드로서 synchronized block에서 다른 스레드가 작업을 수행중일 경우에 다른 스레드가 이 영역의 작업을 수행하지 못하도록 하는 키워드 였습니다. 이 글에서는 synchronized을 사용할 경우 주의해야되는 상황에대해서 알아보도록 하겠습니다. 먼저 아래 코드의 코드를 확인 해보겠습니다.


package threadTest;

class TestObj{
	public void methodA(int id){
		for(int i=0;i<10;i++){
			System.out.println(id + " : A Method");
		}
	}
	
	public void methodB(int id){
		for(int i=0;i<10;i++){
			System.out.println(id + " : B Method");
		}
	}
}

class ATestThread extends Thread{
	TestObj testObj;
	int id;
	
	public ATestThread(TestObj testObj, int id) {
		this.testObj = testObj;
		this.id = id;
	}
	
	@Override
	public void run(){
		testObj.methodA(id);
	}
}

class BTestThread extends Thread{
	TestObj testObj;
	int id;
	
	public BTestThread(TestObj testObj, int id) {
		this.testObj = testObj;
		this.id = id;
	}
	
	@Override
	public void run(){
		testObj.methodB(id);
	}
}

public class Synchronized {
	public static void main(String[] args) {
		ATestThread aTestThread;
		BTestThread bTestThread;
		TestObj testObj = new TestObj();
		for(int i=0;i<10;i++){
			aTestThread = new ATestThread(testObj, i);
			aTestThread.start();
			bTestThread = new BTestThread(testObj, i);
			bTestThread.start();
		}	
	}
}


 위의 코드를 간단하게 살펴보면 methodA와 methodB 메서드를 갖고 있는 TestObj라는 클래스를 확인할 수 있습니다. 이 메서드들은 각각 스레드에서 호출 될 메서드들 입니다. 그리고 methodA와 methodB를 각각 호출하는 ATestThread와 BTestThread 클래스가 있습니다. ATestThread와 BTestThread는 Thread 클래스를 상속 받는 스레드 클래스 입니다. ATestThread와 BTestThread는 생성자에서 TestObj의 객체 하나를 참조 합니다. 메인 메서드에서 생성된 TestObj의 객체를 참조하고 있습니다. 그리고 스레드들의 run 메서드를 확인해보면 각각 methodA와 methodB 메서드에게 클래스의 id를 매개변수로 넘기면서 호출 하고 있습니다. 아래는 결과 화면 입니다. 결과 화면이 너무 길어서 유의미한 부분만을 캡쳐 했습니다.



 결과 화면을 확인 해보겠습니다. 앞에 있는 숫자와 A, B 알파벳을 통해서 몇번째(숫자) 생성된 어떤 클래스(A or B)가 어떤 메서드를 호출 했는지 확인 할 수 있습니다. 역시나 synchronized를 하지 않았기 때문에 아무런 제약 없이 뒤죽박죽 호출된 것을 확인할 수 있습니다. 그렇다면 methodA만 synchronized 해보도록 하겠습니다. 아래는 해당 코드와 결과 입니다. 


class TestObj{
	public synchronized void methodA(int id){
		for(int i=0;i<10;i++){
			System.out.println(id + " : A Method");
		}
	}
	
	public void methodB(int id){
		for(int i=0;i<10;i++){
			System.out.println(id + " : B Method");
		}
	}
}




 결과 화면은 너무 길어서 유의미한 부분만을 캡쳐 했습니다. methodA에만 synchronized를 해줬을 경우에 해당 메서드에서만 규칙을 확인할 수 있었습니다. methodB에서는 이전에 봤었던 결과와 마찬가지고 methodA를 수행하는 중간에 껴있기도 하고 id가 다른  methodB끼리 섞여있는 경우도 있었습니다. 하지만 methodA의 경우에는 스레드가 methodA 수행 중에 다른 스레드는 접근하지 못하는 것을 확인할 수 있었습니다. 여기서 다음 예제를 확인하기 전에 주목 해야되는 점은 methodB는 methodA가 수행되는 중간에 수행이 됐다는 것 입니다. 그렇다면 다음 예제와 결과를 확인 하도록 하겠습니다.


class TestObj{
	public synchronized void methodA(int id){
		for(int i=0;i<10;i++){
			System.out.println(id + " : A Method");
		}
	}
	
	public synchronized void methodB(int id){
		for(int i=0;i<10;i++){
			System.out.println(id + " : B Method");
		}
	}
}



 위의 코드에서는 methodA뿐만 아니라 methodB도 synchronized 키워드를 붙여줬습니다. 결과 화면을 확인하면 methodA와 methodB도 synchronized block에 막혀서 특정 스레드가 이 block을 실행할 때 다른 스레드가 접근하지 못하는 것을 확인할 수 있습니다. 그런데 약간 이상한 점이 하나 있습니다. 제 생각에는 methodA와 methodB는 별개의 메서드이기 때문에 methodA가 실행되는 중간에 methodB가 실행되는 경우도 있을 것 이라고 생각 했습니다. 그런데 마치 methodA와 methodB가 하나의 메서드처럼 둘 중 하나가 종료 돼야 다른 스레드가 메서드에 접근이 가능한 것 처럼 결과가 나왔습니다. 제가 예상한 결과도 아니고 몇 번을 반복해서 실행해도 동일한 상황이 발생해서 이유를 찾아봤습니다. 그 이유는 위의 코드는 사실 아래의 코드와 같이 동작하기 때문이었습니다.


class TestObj{
	public void methodA(int id){
		synchronized(this){
			for(int i=0;i<10;i++){
				System.out.println(id + " : A Method");
			}	
		}
	}
	
	public synchronized void methodB(int id){
		synchronized(this){
			for(int i=0;i<10;i++){
				System.out.println(id + " : B Method");
			}
		}
	}
}


 사실 synchronized는 따로 블럭을 설정해서 사용할 수 있습니다. 여기서 우리가 주목해야되는 부분은 this 입니다. 이 this는 모니터 객체라고 불리며 모니터 객체를 기준으로 동기화 합니다. 동기화된 객체의 메서드는 자기 자신을 모니터 객체로 사용 합니다. 객체를 기준으로 동기화 하기 때문에 해당 객체에서 동기화된 다른 메서드가 있다면 이 메서드도 객체를 기준으로 동기화 됩니다. 즉, 같은 모니터 객체를 기준으로 동기화된 블럭의 코드들은 같은 시점에 하나의 스레드만이 접근 가능 합니다. 그렇기 떄문에 위의 코드에서 methodA와 methodB가 동시에 실행된 경우가 없었던 것 입니다. 객체를 기준으로 동기화를 하기 때문에 우리는 synchronized를 정말 신중하게 사용해야 합니다. 성능에 엄청난 영향을 미칠 수 있기 때문 입니다. 만약 synchronized block의 모니터 객체가 다르다면 동시에 실행될 수 있습니다. 

 그렇다면 static method에 synchronized 키워드를 선언하면 어떻게 될까요? 이 경우에는 모니터 객체가 SomeClass.class가 됩니다. 즉, static 메서드가 synchronized(SomeClass.class){} 블락으로 묶이게 되는 것 입니다. 앞의 경우에는 특정 객체가 모니터 객체가 됐기 때문에 다른 객체가 모니터 객체가 된다면 동시에 접근이 가능 했습니다. 그러나 static의 경우에는 class 자체가 모니터 하기 어떠한 경우에도 오직 하나의 스레드만이 해당 메서드에 접근할 수 있을 것 입니다.


 synchronized에대해 조금 더 깊이 알아보고 싶은 분들은 바로 아래의 링크를 찾아가서 본문과 댓글들을 읽어볼 것을 추천 합니다. 모니터 객체로 어떤 객체를 추천 하는지, this를 모니터 객체로 사용할 때 문제점 등에 대해서 간략하게 알아보고 앞으로의 학습 방향을 확인 할 수 있습니다.


https://slipp.net/questions/179




참고


Comments