Thread와 공유 객체를 간단하게 예를 들어보자면 다음과 같다.
놀이터에 그네가 하나가 있고 아이가 3명 있다고 하자.
어린이들을 Thread라고 생각하면 그네는 공유 객체라고 할 수 있다.
하나의 객체를 여러 개의 Thread가 함께 가지고 있다는 것이다.
// 하나의 객체(공유 객체)
public class MusicBox {
// 신나는 음악!!!이란 메시지가 1초이하로 쉬면서 10번 반복출력
public void playMusicA(){
for(int i=0; i<10; i++){
System.out.println("신나는 음악!!!");
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//슬픈 음악!!!이란 메시지가 1초이하로 쉬면서 10번 반복출력
public void playMusicB(){
for(int i=0; i<10; i++){
System.out.println("슬픈 음악!!!");
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//카페 음악!!! 이란 메시지가 1초이하로 쉬면서 10번 반복출력
public void playMusicC(){
for(int i=0; i<10; i++){
System.out.println("카페 음악!!!");
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// MusicBox를 사용하는 사용자들(스레드)
public class MusicPlayer extends Thread{
int type;
MusicBox musicBox;
// 생성자로 부터 musicBox와 정수를 하나 받아들여서 필드를 초기화
public MusicPlayer(int type, MusicBox musicBox){
this.type = type;
this.musicBox = musicBox;
}
// 실행 : type이 무엇이냐에 따라서 musicBox가 가지고 있는 메소드가 다르게 호출
@Override
public void run(){
switch(type){
case 1 : musicBox.playMusicA(); break;
case 2 : musicBox.playMusicB(); break;
case 3 : musicBox.playMusicC(); break;
}
}
}
public class MusicBoxExam1 {
public static void main(String[] args) {
// MusicBox 인스턴스
MusicBox box = new MusicBox();
MusicPlayer kim = new MusicPlayer(1, box);
MusicPlayer lee = new MusicPlayer(2, box);
MusicPlayer kang = new MusicPlayer(3, box);
// MusicPlayer 쓰레드를 실행
kim.start();
lee.start();
kang.start();
}
}
앞에서 프로그램을 실행했을 때 결과가 순서가 정해지지 않은 상태로 메소드들이 불려지고 있다.
이는 문제가 발생할 수 있는데 이때 해결할 수 있는 방법이 synchronized이다.
메소드 앞에 synchronized를 붙히면 공유 객체가 가진 메소드를 동시에 호출되지 않도록 할 수 있다.
public synchronized void playMusicA(){
// 코드 생략
}
여러개의 Thread들이 공유 객체의 메소드를 사용할 때 메소드에 synchronized가 붙어 있을 경우
먼저 호출한 메소드가 객체의 사용권(Monitoring Lock)을 얻는다. 따라서 OO 음악!!! 하나가
10번 다 출력이 되면 다른 타입의 음악이 10번 출력되고 또 다른 음악이 10번 출력된다.
해당 모니터링 락은 메소드 실행이 종료되거나, wait()과 같은 메소드를 만나기 전까지 유지되고
다른 쓰레드들은 모니터링 락을 놓을때까지 대기한다. synchronized를 붙히지 않은 메소드는
다른 Thread들이 synchronized 메소드를 실행하면서 모니터링 락을 획득했다 하더라도
그것과 상관없이 실행된다.
synchronized를 메소드에 붙혀서 사용 할 경우
메소드의 코드가 길어지면 마지막에 대기하는 쓰레드가 너무 오래 기다리는것을 막기위해서
메소드에 synchronized를 붙이지 않고 문제가 있을것 같은 부분만 synchronized 블록을 사용한다.
public void playMusicA(){
for(int i = 0; i < 10; i ++){
synchronized(this){
System.out.println("신나는 음악!!!");
}
try {
Thread.sleep((int)(Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
★ 실행 순서와 모니터락에 대한 부가적인 설명 ★
https://school.programmers.co.kr/questions/1140
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr