synchronized는 코드의 중요한 부분에 대해 스레드 안전한 접근을 수행할 수 있게 해주는 키워드입니다. 이는 메서드(예: public synchronized void foo())나 코드 블록(synchronized(obj) { ... })에 사용할 수 있습니다. 스레드가 synchronized 블록에 들어가면, 객체의 모니터(락)를 얻게 됩니다. 모니터가 사용 중인 동안, 다른 스레드는 동일한 객체를 모니터로 사용하는 다른 synchronized 블록에 들어올 수 없습니다.
특징:
this, 정적 객체(예: 클래스) 또는 임의의 객체에 대해 동기화할 수 있습니다.public class Counter { private int count; private final Object lock = new Object(); // 프라이빗 객체 public void increment() { synchronized(lock) { count++; } } }
프라이빗 락을 사용하는 것이 더 나은 이유: 퍼블릭 객체(예: this 또는 공개 문자열)에 대해 동기화하면 외부 코드가 모니터를 획득할 수 있어 데드락이나 잘못된 동작을 초래할 수 있습니다.
질문: 고정된 값을 가진 String 객체에 대해 동기화하면 어떻게 될까요?
답변: Java의 문자열은 내부적으로 관리됩니다(같은 리터럴에 대해 동일한 객체 사용). synchronized("lock") 형태의 문자열에 대해 동기화하면, 같은 리터럴에 대해 동기화하는 다른 코드와 우연히 충돌할 수 있어 프로그램의 완전히 다른 부분 간에 예기치 않은 블록킹이 발생할 수 있습니다.
synchronized("LOCK") { ... }
이야기
멀티스레드 거래 시스템에서 동기화를 위해 퍼블릭 객체를 사용했으며, 외부 코드가 락을 획득하여 서로 다른 모듈의 스레드 간에 임시 데드락이 발생하고 거래소에서의 대기가 발생했습니다.
이야기
젊은 개발자가 문자열 리터럴에 대해 컬렉션 접근을 동기화했습니다. 코드의 다른 부분도 같은 값의 문자열에 대해 동기화되어 이러한 스레드가 서로 대기하게 되어 비즈니스 로직이 급격히 느려졌습니다.
이야기
매번 새로운 객체를 선택하여 동기화했습니다:
synchronized(new Object()) { ... }. 그 결과, 동기화가 전혀 작동하지 않았고, 데이터에 여러 스레드가 동시에 접근할 수 있었습니다. 이는 부하 테스트에서만 발견되었습니다.