일전 공유객체를 사용하여, 여러개의 스레드가 공유객체의 메소드를 활용하는 상황이 일어난다고 가정해보자.
public class MusicBox{
public void PlayMusicA(){
System.out.println("played music A");
}
public void PlayMusicB(){
System.out.println("played music B");
}
}
public class MusicBoxTester{
public void main(String[] args){
MusicBox box = new MusicBox();
//공유객체를 실행하는 스레드를 type(생성자)에 따라 여러개 생성
MusicBoxListener person1 = new MusicBoxListener(1, box);
MusicBoxListener person2 = new MusicBoxListener(2, box);
//MusicBoxListener 객체에서 run 메소드 실행
//생성자를 통해 box 객체가 전달되면서, 해당 type에 맞게 switch문 실행
//전달된 객체 모두 동일한 공유 객체를 가지며, 해당 객체에서 type과 관련한 메소드를 실행
person1.run();
person2.run();
}
}
위 MusicBoxTester의 main thread를 실행하면 playMusicA메소드와 playMusicB메소드가 순서 상관없이 뒤죽박죽 호출 및 실행된다.
이를 다시 말하면, block 내부에서는 person1을 통한 MusicA 메소드 → person2을 통한 MusicB 메소드 순서로 호출되었지만 실제 실행되는 순서는 작성된 순서를 따르지 않는다는 것과 같다.
이러한 순서 상관없이 뒤죽박죽 실행이 되는 것을 방지하는 방안 중 하나로, 한 메소드가 실행될 때는 메소드가 완전히 실행종료될때까지 다른 메소드의 실행을 lock(실행보류 및 대기)상태로 두는 것을 의미한다.
공유객체의 method에 synchronized 키워드를 붙인다.
public class MusicBox{
public synchronized void PlayMusicA(){
System.out.println("played music A");
}
public synchronized void PlayMusicB(){
System.out.println("played music B");
}
}
위 공유객체의 메소드가 실행되면 메소드A가 실행이 모두 완료될때까지 B가 실행되지 않고 대기하며, A 메소드 실행이 모두 완료가 되었을때 B 메소드가 실행된다.
참고로 monitoring lock이 걸린 상태에서는, 메소드가 완전히 실행종료되거나 특정 키워드 및 명령어를 만나기 전까지 풀리지 않고 실행보류 상태가 지속된다.
만약 두개의 메소드에 대해 synchronized를 설정하고, 나머지 1개의 메소드에 대해서는 설정하지 않는다면 1개의 메소드는 다른 메소드의 실행여부에 상관없이 동시 실행될 수 있다(=monitoring lock 대상이 아님).
위와 같이 메소드 전체에 대한 monitoring lock을 설정할 경우 한 메소드의 실행길이 및 시간이 길어지면 다른 메소드들이 그만큼 비정상적으로 lock 상태를 유지해야 한다는 단점이 있다.
이럴 경우, 메소드 일부에 대한 monitoring lock을 설정하여 부분적인 synchronized 처리를 해줄 수 있는데, 이러한 과정을 synchronized block 처리리라 일컫는다.
public void playMusicA(){
synchronized (this){
//이 실행부분에 한해서만 locking 처리한다.
System.out.println("MusicA");
}
}
위처럼 메소드 전체가 아니라, 메소드 실행 block 중 일부에 대해서만 blocking 처리를 해준다면, 말 그대로 해당 실행부분만 실행하고 바로 다음 메소드가 실행된다.
실행부분을 객체로 넣는다는 점에 유의한다(this).