동기화란?
- 특정 객체를 동시에 접근하여 변경하기를 막는 기법.
- 멀티 쓰레드 환경에서 쓰레드A가 자원을 이용하는데 동시에 그 자원을 사용하면 안될 때 동기화가 필요.
- 자바에서 동기화 기법을 적용하는 방법은
synchronized 예약어를 사용하는 방법,synchronized block2가지 방법이 있다.
동기화가 왜 필요 할까?
헬스장에 런닝 머신은 1대가 있고 런닝머신을 이용하기를 희망하는 사람은 10명이 있다고 가정해보겠습니다.
일반적인 상황이라면 가장 먼저온 1명이 런닝머신 이용이 끝난 후 다음 사람이 이용하겠죠?
암묵적으로 헬스장의 기구(자원)을 이용하려는 사람(스레드)은 동기화를 실천하는 중인 셈이죠.
하지만 그렇지 않은 상황이라면 어떻게 될까요? 런닝머신에 10명이 동시에 올라가 속도를 멋대로 바꾸고 갑자기 종료를 해버린다면 누구 하나는 골로 가겠죠?그리고 그 골로 가는 코드를 짠 사람이 바로 저입니다.^^
즉, 모두(스레드)가 안정적으로 런닝머신(자원)을 사용해야 하기 때문에 동기화가 필요합니다.
synchronized 사용하여 동기화
우선 동기화를 걸지 않은 코드를 작성해보겠습니다.
public class RunningMachine {
public void start(String name){
System.out.println(name+"이 런닝머신을 시작합니다.");
}
public void modifySpeed(String name){
System.out.println(name+"이 런닝머신의 속도를 조절합니다.");
}
public void stop(String name){
System.out.println(name+"이 런닝머신을 종료합니다..");
}
public void useRunningMachine(String name){
start(name);
modifySpeed(name);
stop(name);
}
}
public class MyThread extends Thread{
private String name;
private RunningMachine runningMachine;
public MyThread(String name,RunningMachine runningMachine){
this.name=name;
this.runningMachine = runningMachine;
}
public void run(){
runningMachine.useRunningMachine(name);
}
}
public class ThreadTest {
public static void main(String[] args) {
RunningMachine runningMachine=new RunningMachine(); //이 객체를 쓰레드 3개에서 사용
MyThread thread1=new MyThread("철수",runningMachine);
MyThread thread2=new MyThread("영희",runningMachine);
MyThread thread3=new MyThread("영철",runningMachine);
thread1.start();
thread2.start();
thread3.start();
}
}
철수,영희,영철 모두가 런닝머신에 올라갔다 속도를 멋대로 바꾸고 런닝머신을 종료 해버리는 모두가 골로가는 코드를 작성하였습니다.
// 실제 결과
영희이 런닝머신을 시작합니다.
영철이 런닝머신을 시작합니다.
철수이 런닝머신을 시작합니다.
영희이 런닝머신의 속도를 조절합니다.
영철이 런닝머신의 속도를 조절합니다.
영희이 런닝머신을 종료합니다..
철수이 런닝머신의 속도를 조절합니다.
영철이 런닝머신을 종료합니다..
철수이 런닝머신을 종료합니다..
모두가 안전하게 런닝머신을 사용할 수 있도록 동기화를 적용해보겠습니다.가장 간단한 방법은 동기화할 메소드에 synchronized 키워드만 추가하면 됩니다.
useRunningMachine 메서드에 synchronized 키워드를 추가해줍니다.
public synchronized void useRunningMachine(String name){
start(name);
modifySpeed(name);
stop(name);
}
그리고 다시 코드를 실행시키면 아래의 결과를 확인 할 수 있습니다.
// 실행 결과
철수이 런닝머신을 시작합니다.
철수이 런닝머신의 속도를 조절합니다.
철수이 런닝머신을 종료합니다..
영철이 런닝머신을 시작합니다.
영철이 런닝머신의 속도를 조절합니다.
영철이 런닝머신을 종료합니다..
영희이 런닝머신을 시작합니다.
영희이 런닝머신의 속도를 조절합니다.
영희이 런닝머신을 종료합니다..모두가 차례를 지키면서 안전하게 런닝머신을 이용 할 수 있게 됐습니다.
Synchronized Block으로 원하는 코드만 동기화
메서드 전체에 동기화를 거는건 방법은 단순하지만 메서드 전체를 동기화 하기 때문에 불필요한 곳까지 동시에 실행을 할 수 없습니다.
헬스장에서 런닝머신을 이용하는건 차례를 기다려야 하지만 누구든 물은 마실수 있잖아요?
public class RunningMachine {
public void start(String name){
System.out.println(name+"이 런닝머신을 시작합니다.");
}
public void modifySpeed(String name){
System.out.println(name+"이 런닝머신의 속도를 조절합니다.");
}
// 추가된 코드
public void drinkWater(String name){
System.out.println(name+"이 물을 마십니다.");
}
public void stop(String name){
System.out.println(name+"이 런닝머신을 종료합니다..");
}
public synchronized void useRunningMachine(String name){
start(name);
modifySpeed(name);
drinkWater(name);
stop(name);
}
}하지만 위의 코드대로라면 물 마시는것도 차례대로 마셔야 하는 상황이 됩니다.
런닝머신을 이용하는 행위에만 동기화를 하려고 할 때 block을 이용해야 합니다.
public void useRunningMachine(String name){
synchronized (this){
start(name);
modifySpeed(name);
stop(name);
}
drinkWater(name);
}
물을 마시는 행위에 대해서는 더이상 순서를 보장하지 않게 됐습니다. 모두가 자유롭게 물을 마실수 있게 됐어요!!
// 실행 결과
영희이 물을 마십니다.
철수이 물을 마십니다.
영철이 물을 마십니다.
영희이 런닝머신을 시작합니다.
영희이 런닝머신의 속도를 조절합니다.
영희이 런닝머신을 종료합니다..
영철이 런닝머신을 시작합니다.
영철이 런닝머신의 속도를 조절합니다.
영철이 런닝머신을 종료합니다..
철수이 런닝머신을 시작합니다.
철수이 런닝머신의 속도를 조절합니다.
철수이 런닝머신을 종료합니다..synchronized block의 인자는 사실 잠글(lock 시킬) 객체를 말합니다. this이니까 바로 RunningMachine 객체 자신이 잠글 객체의 타겟이 되겠죠?
마치며
이번 포스팅에서는 자바에서 동기화 하는 법을 알아봤습니다.
이번 글의 주제 또한 회사에서 발생한 이슈였는데요..^^ 회사는 정말 배울 점이 많은 곳인것 같습니다.
멀티 스레드를 적용하면서 속도 개선에만 신경쓰고 데이터 동기화는 고려하지 않았던 지난날을 반성하며 글을 마치도록 하겠습니다.
끝까지 읽어주셔서 감사합니다 😆
'Dev > Java' 카테고리의 다른 글
| [Java] Garbage Collection(가비지 컬렉션) 톺아보기 (1) | 2022.10.02 |
|---|---|
| [JAVA] 메모리 누수(Memory Leak) (0) | 2022.09.18 |
| Optional의 안티 패턴을 피하는 방법 😎 (0) | 2022.07.22 |
| Optional 제대로 알고 쓰는건가요? (0) | 2022.07.10 |
| 동작 파라미터화 코드 전달하기 (마지막) (0) | 2022.05.29 |