- synchronized는 자동 동기화. 즉, 공유자원을 사용하면 synchronized 블록에서는 자동으로 동기화가 보장되는 것.
편리하지만 미세한 동기화를 제어하기에는 약간의 한계가 있음.
- 비디오 가게를 예로, 비디오 가게에 비디오 테이프가 5개 있고, 그리고 손님들이 비디오 테이프를 빌려간다는 가정하에 동기화가 보장되어야함. 두 사람이 동시에 하나의 테이프를 빌려가면 안 됨. 물론 synchronized를 사용하면 동기화는 보장 할수 있지만, 비디오를 빌린 후 비디오 테이프를 보는 동안에 동기화를 걸면, 한 사람이 비디오를 보는(시청하는) 동안 어떠한 사람도 비디오 테이프를 빌릴 수 없음.
잘못된 동기화의 예
============================================================================================================
import java.util.Vector;
public class VideoShop {
private Vector buffer = new Vector();
public VideoShop(){
buffer.addElement("은하철도 999-0");
buffer.addElement("은하철도 999-1");
buffer.addElement("은하철도 999-2");
buffer.addElement("은하철도 999-3");
}
public String lendVideo(){
String v = (String)this.buffer.remove(buffer.size()-1);
return v;
}
public void returnVideo(String video){
this.buffer.addElement(video);
}
}
public class Person extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (VideoShopMain.vShop) {
// 5초동안 VideoShopMain.vShop은 락(Lock)에 걸리게 됨.
try{
String v = VideoShopMain.vShop.lendVideo();
System.out.println(this.getName() + " : " + v + " 대여");
System.out.println(this.getName() + " : " + v + " 보는 중");
this.sleep(5000);
System.out.println(this.getName() + " : " + v + " 반납");
VideoShopMain.vShop.returnVideo(v);
}catch (Exception e) {
// TODO: handle exception
}
}
}
}
public class VideoShopMain {
public static VideoShop vShop = new VideoShop();
public static void main(String[] args) {
System.out.println("프로그램 시작");
Person p1 = new Person();
Person p2 = new Person();
Person p3= new Person();
Person p4 = new Person();
p1.start();
p2.start();
p3.start();
p4.start();
System.out.println("프로그램 종료");
}
}
============================================================================================================
- 위의 예에서 4개의 Person 쓰레드를 실행시키고 있음.
-> Person p1 = new Person();
-> Person p2 = new Person();
-> Person p3 = new Person();
-> Person p4 = new Person();
-> p1.start(); p2.start(); p3.start(); p4.start();
-> 일반적인 경우라면 4개의 쓰레드가 동시에 작업을 진행할 것이라고 생각하지만 비디오를 빌린 후 반납할 때까지 동기화를 걸어두었다면, 비디오 테이프를 빌린 첫번째 사람이 비디오를 보기 전까지는 어떠한 쓰레드도 VideoShop에 접근할 수 없음.
- synchronized 블럭
-> synchronized(VideoShopMain.vShop){
String v = VideoShopMain.vShop.lendVideo(); // 비디오를 빌림.
try{
this.sleep(5000); // 비디오를 보는 시간 5초
catch(InterruptedException e){e.printStackTrace();}
VideoShopMain.vShop.returnVideo(v); // 비디오를 반납.
}
-> 비디오를 보는 5초동안 VideoShop vShop은 synchronized로 인해 락(Lock)이 걸린 상태이기 때문에 어떠한 쓰레드도 접근할 수 없음. 이렇게 될 경우 비디오 가게에서는 단 하나의 테이프만을 운영하는 것이 됨.
-> synchronized를 사용할 때 비디오 테이프를 빌려주는 곳과 반환하는 곳에 synchronized를 거는 것이 더 정확한 사용 방법.
- synchronized를 걸어야 하는 곳
-> public synchronized String lendVideo(){...}
-> public synchronized void returnVideo(String video){...}
- 데이터를 집어넣고 추출할 때에만 동기화를 보장
============================================================================================================
import java.util.Vector;
public class VideoShop {
private Vector buffer = new Vector();
public VideoShop(){
buffer.addElement("은하철도 999-0");
buffer.addElement("은하철도 999-1");
buffer.addElement("은하철도 999-2");
buffer.addElement("은하철도 999-3");
}
public synchronized String lendVideo(){
String v = (String)this.buffer.remove(buffer.size() - 1);
return v;
}
public synchronized void returnVideo(String video){
this.buffer.addElement(video);
}
}
public class Person extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
try{
String v = VideoShopMain2.vShop.lendVideo();
System.out.println(this.getName() + " : " + v + " 대여");
System.out.println(this.getName() + " : " + v + " 보는 중");
this.sleep(5000);
System.out.println(this.getName() + " : " + v + " 반납");
VideoShopMain2.vShop.returnVideo(v);
}catch (Exception e) {
// TODO: handle exception
}
}
}
public class VideoShopMain2 {
public static VideoShop vShop = new VideoShop();
public static void main(String[] args) {
System.out.println("프로그램 시작");
Person p1 = new Person();
Person p2 = new Person();
Person p3 = new Person();
Person p4 = new Person();
p1.start();
p2.start();
p3.start();
p4.start();
System.out.println("프로그램 종료");
}
}
============================================================================================================
- synchronized를 lendVideo()와 returnVideo()에 걸면 4개의 비디오를 동시에 대여할 수 있음. 비디오를 보는동안 synchronized 처리하는 것보다는 훨씬 유연하게 동작하는 것을 확인.
하지만 이 경우에는 또 다른 문제가 제기. 즉 비디오 테이프를 보는 곳에 동기화를 걸지 않았기 때문에 5번째 비디오 테이프를 빌리면 심각한 문제가 발생.
-> 문제발생
=> Person p5 = new Person();
=> p5.start();
-> 비디오를 보는 곳에 동기화를 걸 수 없으며, 테이프는 4개밖에 없기 때문에 에러가 발생. 물론 비디오가 없으면 빌려주지 않으면 됨. 즉 Person p5가 구동되었을 때 비디오 테이프가 없으면 바로 p5를 종료하면 될 것. 즉 여유분이 있을 때만 비디오 테이프를 빌려주면 됨.
============================================================================================================
import java.util.Vector;
public class VideoShop {
private Vector buffer = new Vector();
public VideoShop(){
buffer.addElement("은하철도 999-0");
buffer.addElement("은하철도 999-1");
buffer.addElement("은하철도 999-2");
buffer.addElement("은하철도 999-3");
}
public synchronized String lendVideo(){
if(buffer.size() > 0 ) {
String v = (String)this.buffer.remove(buffer.size()-1);
return v;
}else{
return null;
}
}
public synchronized void returnVideo(String video){
this.buffer.addElement(video);
}
}
public class Person extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
String v = VideoShopMain3.vShop.lendVideo();
if(v == null){
System.out.println(this.getName() + "비디오가 없군요. 안 봅니다.");
return;
}
try{
System.out.println(this.getName() + " : " + v + " 대여");
System.out.println(this.getName() + " : " + v + " 보는 중\n");
this.sleep(5000);
System.out.println(this.getName() + " : " + v + " 반납");
VideoShopMain3.vShop.returnVideo(v);
}catch (Exception e) {
// TODO: handle exception
}
}
}
public class VideoShopMain3 {
public static VideoShop vShop = new VideoShop();
public static void main(String[] args) {
System.out.println("프로그램 시작");
Person p1 = new Person();
Person p2 = new Person();
Person p3 = new Person();
Person p4 = new Person();
Person p5 = new Person();
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
System.out.println("프로그램 종료");
}
}
============================================================================================================
- 위의 예에서는 비디오 테이프가 없을 때 비디오 테이프를 빌리고자 한다면 null을 리턴하게 되어 있음.
-> 비디오 테이프가 없을 때 null을 리턴
=> if(buffer.size() > 0 ){
String v = (String)this.buffer.remove(buffer.size() - 1);
return v;
}else{
return null;
}
-> 그리고 Person에서는 비디오 테이프가 없다면 다음과 같이 바로 쓰레드를 종료하게 됨.
=> if(v == null){
System.out.println(this.getName() + "비디오가 없군요. 안 봅니다.");
return null; // 비디오 테이프를 빌리는 것 자체를 포기.
}
- 이렇게 한다면 에러 발생은 막을 수 있음. 하지만 자원을 요청한 사람은 대기하는 것이 아니라 아예 포기하는 것이 됨. 즉, 이 비디오 가게는 장사를 잘 못하는 것. 만약 제대로 장사를 한다면 잠깐 기다리라고 한 뒤 비디오 테이프가 들어오면 바로 빌려줌ㄴ 됨.
현재 동기화 기법으로 이것을 구현하는 것은 거의 불가능. 물론 구현할 수 없는 것은 아니지만 약간 까다로움. 보다 효율적인 쓰레드 제어의 기법을 구현하기 위해서는 즉 비디오 가게에서 손님의 동기화를 보장하기 위해서는 wait()와 notify()를 사용하면 됨. 비디오 테이프가 없다면 wait() 시켰다가 비디오 테이프가 반납되면 notify()해서 빌겨가게 한다면 보다 효율적으로 비디오 가게를 운영할 수 있을 것임.