HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
📝
남득윤 학습 저장소
/
🧵
멀티쓰레드, 동시성 프로그래밍
/
🧵
Java Concurrency and Multithreding
/
Java Synchronized - The synchronized keyword in Java and Java synchronized blocks and methods

Java Synchronized - The synchronized keyword in Java and Java synchronized blocks and methods

 

Java synchronized keyword

synchronized block은 synchronized 키워드로 표시합니다.
 

Synchronized instance methods

synchronized 키워드는 메서드에도 적용할 수 있습니다.
 
public class SynchronizedExchanger { protected Object object = null; public synchronized Object getObject() { return object; } public synchronized void setObject(Object object) { this.object = object; } public Object getObj(){ synchronized (this){ return object; } } public void setObj(Object o) { synchronized (this) { this.object = o; } } }
 
인스턴스 메서드를 synchronized로 선언하는 것은 한 시점에 하나의 쓰레드만 해당 메서드를 실행 할 수 있음을 의미합니다.
 

Synchronized blocks inside instance methods

전체 메서드를 synchronized 로 선언하고 싶지 않다면 synchronized block을 활용할 수 있습니다. synchronized block 역시 한 시점에 하나의 쓰레드에 의해서만 실행 될 수 있습니다.
 
동기화 블럭을 선언할때 괄호 안에 작성한 this는 monitor object 입니다. 모니터 객체는 동기화의 범위를 나타냅니다.
 
동기화 메서드는 자동적으로 this 범위의 동기화 범위를 가집니다.
 
Thread 1과 Thread 2가 동일한 SynchronizedExchanger 객체를 공유하는 경우
Thread 1과 Thread 2가 동일한 SynchronizedExchanger 객체를 공유하는 경우
위 코드에서 모든 메서드는 같은 monitor 객체 (this)를 가지기 때문에 Thread 1 이 setObject를 호출하는 순간 Thread 2는 getObject를 호출 할 수 없습니다. (동기화 블럭으로 선언된 xxxObj 메서드 들도 마찬가지)
Thread 1과 Thread 2가 각자의 SynchronizedExchanger 객체를 가지는 경우
Thread 1과 Thread 2가 각자의 SynchronizedExchanger 객체를 가지는 경우

Example of threads calling synchronized instance methods on a shared object

public class SynchronizedExchangerMain { public static void main(String[] args) { SynchronizedExchanger exchanger = new SynchronizedExchanger(); Thread thread1 = new Thread( new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { exchanger.setObj(""+i); } } } ); Thread thread2 = new Thread( new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println(exchanger.getObj()); } } } ); thread1.start(); thread2.start(); } }
실행 결과
null null null null null .... null 999 999 999 ... 999
thread 2가 thread 1 이 set하기도 전에 접근해서 getObj할때 null을 반환한다.
 

Synchronized static methods

public class StaticSynchronizedExchanger { private static Objectobject= null; public static synchronized Object getObject() { return object; } public static synchronized void setObject(Object o) { object= o; } public static Object getObj(){ synchronized (StaticSynchronizedExchanger.class){ return object; } } public static void setObj(Object o) { synchronized (StaticSynchronizedExchanger.class) { object= o; } } }
static 메서드의 경우 클래스 객체를 모니터 객체로 활용한다.
notion image
 
전체 쓰레드에서 한 쓰레드만이 synchronized static 메서드를 호출 할 수 있다.
 

Using both synchronized static and instance methods

public class MixedSynchronizedExchanger { private static Object staticObject= null; public static synchronized Object getStaticObject() { return staticObject; } public static void setStaticObj(Object o) { synchronized (MixedSynchronizedExchanger.class) { staticObject= o; } } protected Object object = null; public synchronized void setObject(Object object) { this.object = object; } public Object getObj(){ synchronized (this){ return object; } } }
한 클래스에 선언된 synchronized 메서드 일지라도 서로 다른 monitor 객체를 가질 수 있다. static method의 경우 클래스 객체에 instance method의 경우 자신의 참조 (this)를 monitor 객체로 가진다.
 

Using different monitor objects for synchronized blocks within same class

public class MultipleMonitorObjects { private Object monitor1 = new Object(); private Object monitor2 = new Object(); private int counter1 = 0; private int counter2 = 0; public void incCounter1(){ synchronized (this.monitor1){ this.counter1++; } } public void incCounter2(){ synchronized (this.monitor2){ this.counter2++; } } }
이경우 intCounterN 메서드는 두개의 카운터 필드는 서로 다른 모니터 객체에 의해 동기화 되므로 서로다른 쓰레드에 의해 동시에 실행 될 수 있습니다.
 

Sharing monitor objects across different class instances (objects)

public class SharedMonitorObject { private Object monitor = null; private int counter = 0; public SharedMonitorObject(Object monitor) { if(monitor == null){ throw new IllegalArgumentException( "Monitor object cannot be null." ); } this.monitor = monitor; } public void incCounter(){ synchronized (this.monitor){ this.counter++; } } }
 

Monitor objects cannot be null

  • 모니터 객체는 null이 될 수 없다.
  • 모니터 객체가 null일 경우 동기화 메서드/ 블락 진입시 null pointer exception이 발생한다.
 

Example of sharing monitor objects across objects

public class SharedMonitorObjectMain { public static void main(String[] args) { Object monitor1 = new Object(); SharedMonitorObject smo1 = new SharedMonitorObject(monitor1); SharedMonitorObject smo2 = new SharedMonitorObject(monitor1); Object monitor2 = new Object(); SharedMonitorObject smo3 = new SharedMonitorObject(monitor2); } }
 
⚠️
매우 멋지고 Advanced 한 기술이고 잘 쓰면 Fancy한 동기화 기술을 사용할 수 있지만 정말 잘 동기화 하고 있는가에 대한 검증은 매우매우 어려워진다.

Don't use String constant objects as monitor objects

자바 컴파일러가 마구마구 최적화 해버린다. 같은 instance 임을 보장 할 수 없음. 마찬가지로 Wrapper 클래스도 사용하면 안된다.
 

Java synchronized blocks inside Java Lambda Expressions

public class SynchronizedLambda { private static Objectobject= null; public static synchronized void setObject(Object o){ object= o; } public static void consumeObject(Consumer consumer){ consumer.accept(object); } public static void main(String[] args){ consumeObject( obj -> { synchronized (SynchronizedLambda.class){ System.out.println(obj); } }); consumeObject( obj -> { synchronized (String.class){ System.out.println(obj); } }); } }
java 람다 표현식에서는 this라는 참조를 사용할 수 없으므로 monitor 객체로 this를 전달 할 수 없다.

Java synchronized block reentrance rules

public class Reentrance { private int count = 0; public synchronized void inc(){ this.count++; } public synchronized int incAndGet(){ inc(); return this.count; } }
incAndGet 메서드와 inc 메서드 모두 같은 모니터 객체인 this를 가지기 때문에 incAndGet 메서드에서도 inc를 진입할 수 있다.
 

Java synchronized block data change visibility guarantee

notion image
  • all fields that are visible to Thread 1
    • main memory → cache → registers of cpu 1
 
  • 동기화 블럭을 제거하면 Thread 들 간에 count 필드를 언제 가져갔고 main memory에 최신상태로 다시 write 해 놓았는지에 대한 보장이 안됨
 

Java synchronized block visibility example

public class SyncCounter { private long count = 0; public synchronized void incCount() { this.count++; } public synchronized long getCount(){ return this.count; } }
public class SyncCounterMain { public static void main(String[] args) { SyncCounter counter = new SyncCounter(); Thread thread1 = new Thread( () -> { for (int i = 0; i < 1_000_000; i++) { counter.incCount(); } System.out.println(counter.getCount()); }); Thread thread2 = new Thread( () -> { for (int i = 0; i < 1_000_000; i++) { counter.incCount(); } System.out.println(counter.getCount()); }); thread1.start(); thread2.start(); } }

Java synchronized block happens before guarantee

notion image
 

Java syncronized block limitations

Limitations of Java synchronized blocks:
  • Only one thread can enter a synchronized block at a time.
  • There is no guarantee about the sequence in which waiting threads gets access to the synchronized block.
    • no fairness → maybe starvation!
 

Java synchronized block performance overhead

notion image
 

Java syncrhronized blocks in clustered setups

Java는 같은 JVM 상의 쓰레드 끼리의 동기화만 제공한다.