밤빵's 개발일지
[TIL]20241122 synchronized 본문
생각없이 보던 화면에 코드가 보여서 살펴보다가, 멀티스레드 환경에서 동기화 문제를 해결하기 위한 synchronized 키워드를 사용해 본 적은 없었기 때문에, 이 기회에 학습 겸 개발일지를 작성하기로 했다. 코드 예시를 통해 synchronized 키워드가 실제로 어떻게 사용되는지, 어떤 문제를 해결할 수 있는지, 그리고 이 키워드를 사용할 때 주의해야 할 점 까지 정리를 해보기로했다.
▶ synchronized 키워드
멀티스레드 환경에서 여러 스레드가 하나의 공유 리소스에 동시에 접근하려 할 때, Race Condition과 같은 문제가 발생할 수 있다. synchronized 키워드는 이러한 문제를 방지하기 위해 사용되고, 공유 리소스에 대한 접근을 하나의 스레드로 제한한다.
특징
- synchronized를 사용하면 동시에 하나의 스레드만 메서드나 코드 블록에 접근할 수 있다.
- 데이터 무결성을 보장한다.
- 객체 또는 클래스 수준에서 동기화를 적용할 수 있다.
▶ 동기화가 필요한 상황: Race Condition
Race Condition은 여러 스레드가 공유 리소스를 동시에 수정하려 할 때 발생한다. 예를 들어, 두 스레드가 파일에 데이터를 쓰는 작업을 수행한다면, 데이터가 손실되거나 파일이 손상될 가능성이 있다. 이러한 상황에서 synchronized를 사용하면 문제를 방지할 수 있다.
▶코드 예시
synchronized 키워드가 적용된 코드로, 영상에서 나온 코드는 메서드가 비어있어서 대충 흐름에 맞게 채워 넣었다. 이 코드는 파일에 사용자 리포트를 추가하거나, 파일에서 사용자 리포트 리스트를 읽어오는 기능을 제공한다.
synchronized public void addReportEntry(UserReport urp) {
File idxFile = new File(userroot, idxFile);
FileWriter fw = null;
try {
fw = new FileWriter(idxFile, true); // 파일에 append 모드로 열기
fw.write(urp.encode() + "\n"); // UserReport를 문자열로 변환하여 파일에 쓰기
fw.flush(); // 파일 내용을 즉시 저장
} catch (IOException e) {
logger.log(Level.SEVERE, "Error writing to file", e);
} finally {
if (fw != null) {
try {
fw.close(); // 파일 닫기
} catch (IOException e) {
logger.log(Level.WARNING, "Error closing file", e);
}
}
}
}
→ addReportEntry를 호출한 여러 스레드가 동일한 파일에 접근하는 상황에서, 한 번에 한 스레드만 파일을 수정하도록 보장한다.
synchronized public List<UserReport> list() {
List<UserReport> replist = new java.util.ArrayList<UserReport>();
File idxFile = new File(userroot, idxFile);
if (idxFile.exists()) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader(idxFile));
String line = null;
while ((line = br.readLine()) != null) {
UserReport rep = UserReport.decodeLine(line);
if (rep != null) {
replist.add(rep);
if (rep.getId() > this.maxId) maxId = rep.getId();
}
}
} catch (Exception ex) {
logger.log(Level.SEVERE, "Exception while reading file", ex);
} finally {
if (br != null) {
try {
br.close(); // BufferedReader 닫기
} catch (IOException e) {
logger.log(Level.WARNING, "Error closing BufferedReader", e);
}
}
}
}
return replist;
}
→ 파일에서 사용자 데이터를 읽어 리스트로 반환한다. 파일이 존재하지 않을 경우 작업을 건너뛰고, synchronized를 통해 여러 스레드가 동시에 파일을 읽는 상황에서도 안정성을 보장한다.
▶ synchronized의 작동 방식
메서드 수준 동기화: 위 코드는 synchronized를 메서드 수준에 적용했다. 해당 객체(this)를 락으로 사용한다.
락(Lock) 개념: 한 스레드가 synchronized 메서드에 접근하면 다른 스레드는 락이 해제될 때까지 대기한다.
객체 수준 동기화: 메서드의 synchronized는 해당 객체의 모든 synchronized 메서드에 적용된다.
▶ synchronized의 장단점
장점
- 구현이 간단하다. 한 키워드만으로 동기화 문제를 해결할 수 있다.
- 데이터 무결성을 보장한다. 여러 스레드가 동시에 공유 리소스에 접근하여 발생할 수 있는 문제를 방지한다.
단점
- 병목 현상: 동기화를 잘못 사용하면 성능 저하가 발생할 수 있다. 특히 대량의 스레드가 경쟁하는 상황에서는 효율성이 떨어질 수 있다.
- 유연성 부족: 세밀한 제어가 필요한 경우 synchronized는 한계가 있다.
▶ ReentrantLock
ReentrantLock은 synchronized보다 더 세밀하게 동기화를 제어할 수 있는 고급 메커니즘이다.
ReentrantLock의 장점
- 락의 획득과 해제를 명시적으로 제어할 수 있다.
- 타임아웃을 설정해 스레드가 무한 대기하지 않도록 할 수 있다.
- 락 공정성(Fairness) 설정이 가능하다.
private final ReentrantLock lock = new ReentrantLock();
public void addReportEntry(UserReport urp) {
lock.lock();
try {
// 파일 쓰기 로직
} finally {
lock.unlock();
}
}
멀티스레드 환경에서 공유 리소스의 동기화는 매우 중요한 부분이고, synchronized 키워드는 간단한 동기화 문제를 해결하는 데 유용하다. 그러나 성능 이슈나 더 세밀한 제어가 필요한 경우에는 ReentrantLock과 같은 대안을 고려해야 한다.
+추가) 개발일지 소재가 된 코드 🤭

'개발Article' 카테고리의 다른 글
[TIL]20241124 유틸리티클래스는 악이다? (1) | 2024.11.24 |
---|---|
[TIL]20241123 PUBG 프로젝트 roster 응답 구조 재작업 (1) | 2024.11.23 |
[TIL]20241121 Jackson @JsonInclude (1) | 2024.11.21 |
[TIL]20241120 FACADE 패턴 (14) | 2024.11.20 |
[TIL]20241118 Spring Security에서 권한별 URL 접근 제어에 대한 고민 (1) | 2024.11.19 |