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 범위의 동기화 범위를 가집니다.

위 코드에서 모든 메서드는 같은 monitor 객체 (this)를 가지기 때문에 Thread 1 이 setObject를 호출하는 순간 Thread 2는 getObject를 호출 할 수 없습니다. (동기화 블럭으로 선언된 xxxObj 메서드 들도 마찬가지)

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 메서드의 경우 클래스 객체를 모니터 객체로 활용한다.

전체 쓰레드에서 한 쓰레드만이 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

- 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

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

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