item 78 incheol - JAVA-JIKIMI/EFFECTIVE-JAVA3 GitHub Wiki

Effective Java 3e μ•„μ΄ν…œ 68λ₯Ό μš”μ•½ν•œ λ‚΄μš© μž…λ‹ˆλ‹€.

synchronized ν‚€μ›Œλ“œλŠ” ν•΄λ‹Ή λ©”μ„œλ“œλ‚˜ 블둝을 ν•œλ²ˆμ— ν•œ μŠ€λ ˆλ“œμ”© μˆ˜ν–‰ν•˜λ„λ‘ 보μž₯ν•œλ‹€. 즉 ν•œ μŠ€λ ˆλ“œκ°€ λ³€κ²½ν•˜λŠ” μ€‘μ΄λΌμ„œ μƒνƒœκ°€ μΌκ΄€λ˜μ§€ μ•Šμ€ μˆœκ°„μ˜ 객체λ₯Ό λ‹€λ₯Έ μŠ€λ ˆλ“œκ°€ 보지 λͺ»ν•˜κ²Œ λ§‰λŠ” μš©λ„λ‘œλ§Œ μƒκ°ν•œλ‹€.

동기화λ₯Ό μ œλŒ€λ‘œ μ‚¬μš©ν•˜λ©΄ μ–΄λ–€ λ©”μ„œλ“œλ„ 이 객체의 μƒνƒœκ°€ μΌκ΄€λ˜μ§€ μ•Šμ€ μˆœκ°„μ„ λ³Ό 수 μ—†κ³  μŠ€λ ˆλ“œκ°€ λ§Œλ“  λ³€ν™”λ₯Ό λ‹€λ₯Έ μŠ€λ ˆλ“œμ—μ„œ ν™•μΈν•˜μ§€ λͺ»ν•  수 μžˆλ‹€.

longκ³Ό double μ™Έμ˜ λ³€μˆ˜λ₯Ό 읽고 μ“°λŠ” λ™μž‘μ€ μ›μžμ (atomic)이닀. 이 말을 λ“£κ³  **"μ„±λŠ₯을 높이렀면 μ›μžμ  데이터λ₯Ό 읽고 μ“Έ λ•ŒλŠ” λ™κΈ°ν™”ν•˜μ§€ 말아야겠닀"**κ³  μƒκ°ν•˜κΈ° μ‰¬μš΄λ°, μ•„μ£Ό μœ„ν—˜ν•œ λ°œμƒμ΄λ‹€.

λ™κΈ°ν™”λŠ” 배타적 싀행뿐 μ•„λ‹ˆλΌ μŠ€λ ˆλ“œ μ‚¬μ΄μ˜ μ•ˆμ •μ μΈ 톡신에 κΌ­ ν•„μš”ν•˜λ‹€.

곡유 쀑인 κ°€λ³€ 데이터λ₯Ό 비둝 μ›μžμ μœΌλ‘œ 읽고 μ“Έ 수 μžˆμ„μ§€λΌλ„ 동기화에 μ‹€νŒ¨ν•˜λ©΄ μ²˜μ°Έν•œ 결과둜 μ΄μ–΄μ§ˆ 수 μžˆλ‹€. λ‹€μŒ μŠ€λ ˆλ“œλ₯Ό μ€‘μ§€ν•˜λŠ” μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄μž

public class StopThread {
	private static boolean stopRequested;

	public static void main(String[] args) throws InterruptedException {
		Thread backgroundThread = new Thread(() -> {
			int i = 0;
			while (!stopRequested) 
				i ++;
		});
		backgroundThread.start();

		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}
}

μœ„μ˜ μ½”λ“œλ₯Ό 보면 1초 ν›„ stopRequestedλ₯Ό true둜 μ„€μ •ν•˜λ©΄ backgroundTreadλŠ” λ°˜λ³΅λ¬Έμ„ λΉ μ Έλ‚˜μ˜¬ κ²ƒμ²˜λŸΌ 보일 것이닀. ν•˜μ§€λ§Œ λ‚΄ μ»΄ν“¨ν„°μ—μ„œλŠ” 도톡 끝날 쀄 λͺ¨λ₯΄κ³  μ˜μ›νžˆ μˆ˜ν–‰λ˜μ—ˆλ‹€. 원인은 λ™κΈ°ν™”ν•˜μ§€ μ•ŠμœΌλ©΄ 메인 μŠ€λ ˆλ“œκ°€ μˆ˜μ •ν•œ 값을 λ°±κ·ΈλΌμš΄λ“œ μŠ€λ ˆλ“œκ°€ μ–Έμ œμ―€μ—λ‚˜ 보게 될지 보증할 수 μ—†λ‹€. 동기화가 λΉ μ§€λ©΄ 가상 머신이 λ‹€μŒκ³Ό 같은 μ΅œμ ν™”λ₯Ό μˆ˜ν–‰ν•  μˆ˜λ„ μžˆλŠ” 것이닀.

if (!stopRequested)
	while (true)
		i++;

μ΄λŠ” stopRequested ν•„λ“œλ₯Ό 동기화해 μ ‘κ·Όν•˜λ©΄ 이 문제λ₯Ό ν•΄κ²°ν•  수 μžˆλ‹€.

public class StopThread {
	private static boolean stopRequested;

	private static synchronized void requestStop() {
		stopRequested = true;
	}
	
	private static synchronized boolean stopRequested() {
		return stopRequested;
	}

	public static void main(String[] args) throws InterruptedException {
		Thread backgroundThread = new Thread(() -> {
			int i = 0;
			while (!stopRequested()) 
				i ++;
		});
		backgroundThread.start();

		TimeUnit.SECONDS.sleep(1);
		requestStop();
	}
}

λ°˜λ³΅λ¬Έμ—μ„œ 맀번 λ™κΈ°ν™”ν•˜λŠ” λΉ„μš©μ΄ 크진 μ•Šμ§€λ§Œ 속도가 더 λΉ λ₯Έ λŒ€μ•ˆμ΄ μžˆλ‹€. stopRequested ν•„λ“œλ₯Ό volatile으둜 μ„ μ–Έν•˜λ©΄ 동기화λ₯Ό 생각해도 λœλ‹€. volatile ν•œμ •μžλŠ” 배타적 μˆ˜ν–‰κ³ΌλŠ” μƒκ΄€μ—†μ§€λ§Œ 항상 κ°€μž₯ μ΅œκ·Όμ— 기둝된 값을 읽게 됨을 보μž₯ν•œλ‹€.

public class StopThread {
	private static volatile boolean stopRequested;

	public static void main(String[] args) throws InterruptedException {
		Thread backgroudThread = new Tread(() -> {
			int i = 0;
			while (!stopRequested) 
				i ++;
		});
		backgroundThread.start();

		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}
}

ν•˜μ§€λ§Œ volatile은 μ£Όμ˜ν•΄μ„œ μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.

private static volatile int nextSerialNumber = 0;

public static int generateSerialNumber() {
	return nextSerialNumber++;
}

이 λ©”μ„œλ“œλŠ” 맀번 κ³ μœ ν•œ 값을 λ°˜ν™˜ν•  μ˜λ„λ‘œ λ§Œλ“€μ–΄μ‘Œλ‹€. λ¬Έμ œλŠ” 증가 μ—°μ‚°μž(++)λ‹€. 이 μ—°μ‚°μžλŠ” μ½”λ“œμƒμœΌλ‘œλŠ” ν•˜λ‚˜μ§€λ§Œ μ‹€μ œλ‘œλŠ” nextSerialNumber ν•„λ“œμ— 두 번 μ ‘κ·Όν•œλ‹€. λ§Œμ•½ 두 번째 μŠ€λ ˆλ“œκ°€ 이 두 μ ‘κ·Ό 사이λ₯Ό λΉ„μ§‘κ³  듀어와 값을 읽어 κ°€λ©΄ 첫 번째 μŠ€λ ˆλ“œμ™€ λ˜‘κ°™μ€ 값을 λŒλ €λ°›κ²Œ λœλ‹€. ν”„λ‘œκ·Έλž¨μ΄ 잘λͺ»λœ κ²°κ³Όλ₯Ό κ³„μ‚°ν•΄λ‚΄λŠ” 이런 였λ₯˜λ₯Ό μ•ˆμ „ μ‹€νŒ¨ν•˜κ³  ν•œλ‹€. μ΄λŠ” synchronized ν•œμ •μžλ₯Ό 뢙이면 ν•΄κ²°λœλ‹€. 이 λ©”μ„œλ“œλ₯Ό 더 κ²¬κ³ ν•˜κ²Œ ν•˜λ €λ©΄ int λŒ€μ‹ μ— long을 μ‚¬μš©ν•˜κ±°λ‚˜ nextSerialNumberκ°€ μ΅œλŒ“κ°’μ— λ„λ‹¬ν•˜λ©΄ μ˜ˆμ™Έλ₯Ό λ˜μ§€κ²Œ ν•˜μž.

λ˜λŠ” AtomicLong을 μ‚¬μš©ν•˜λ©΄ 락 없이도 μŠ€λ ˆλ“œ μ•ˆμ „ν•œ ν”„λ‘œκ·Έλž˜λ°μ„ μ§€μ›ν•œλ‹€. volatile은 λ™κΈ°ν™”μ˜ 두 효과 쀑 톡신 μͺ½λ§Œ μ§€μ›ν•˜μ§€λ§Œ 이 νŒ¨ν‚€μ§€λŠ” μ›μžμ„±κΉŒμ§€ μ§€μ›ν•œλ‹€. 그리고 μ„±λŠ₯도 동기화 버전보닀 μš°μˆ˜ν•˜λ‹€.

private static final AtomicLong nextSerialNumber = 0;

public static int generateSerialNumber() {
	return nextSerialNumber++;
}

λ‹€μ‹œ 말해 κ°€λ³€ λ°μ΄ν„°λŠ” 단일 μŠ€λ ˆλ“œμ—μ„œλ§Œ 쓰도둝 ν•˜μž.

그리고 이 사싀을 λ¬Έμ„œμ— 남겨 μœ μ§€λ³΄μˆ˜ κ³Όμ •μ—μ„œλ„ 정책이 계속 μ§€μΌœμ§€λ„λ‘ ν•˜λŠ” 게 μ€‘μš”ν•˜λ‹€. λ˜ν•œ, μ‚¬μš©ν•˜λ €λŠ” ν”„λ ˆμž„μ›Œν¬μ™€ 라이브러리λ₯Ό 깊이 μ΄ν•΄ν•˜λŠ” 것도 μ€‘μš”ν•˜λ‹€.

ν•œ μŠ€λ ˆλ“œκ°€ 데이터λ₯Ό λ‹€ μˆ˜μ •ν•œ ν›„ λ‹€λ₯Έ μŠ€λ ˆλ“œμ— κ³΅μœ ν•  λ•ŒλŠ” ν•΄λ‹Ή κ°μ²΄μ—μ„œ κ³΅μœ ν•˜λŠ” λΆ€λΆ„λ§Œ 동기화해도 λœλ‹€. 그러면 κ·Έ 객체λ₯Ό λ‹€μ‹œ μˆ˜μ •ν•  일이 생기기 μ „κΉŒμ§€ λ‹€λ₯Έ μŠ€λ ˆλ“œλ“€μ€ 동기화 없이 자유둭게 값을 μ½μ–΄κ°ˆ 수 μžˆλ‹€.

클래슀 μ΄ˆκΈ°ν™” κ³Όμ •μ—μ„œ 객체λ₯Ό 정적 ν•„λ“œ, volatile ν•„λ“œ, final ν•„λ“œ, ν˜Ήμ€ λ³΄ν†΅μ˜ 락을 톡해 μ ‘κ·Όν•˜λŠ” ν•„λ“œμ— μ €μž₯해도 λœλ‹€.

정리

μ—¬λŸ¬ μŠ€λ ˆλ“œκ°€ κ°€λ³€ 데이터λ₯Ό κ³΅μœ ν•œλ‹€λ©΄ κ·Έ 데이터λ₯Ό 읽고 μ“°λŠ” λ™μž‘μ€ λ°˜λ“œμ‹œ 동기화해야 ν•œλ‹€. λ™κΈ°ν™”ν•˜μ§€ μ•ŠμœΌλ©΄ ν•œ μŠ€λ ˆλ“œκ°€ μˆ˜ν–‰ν•œ 변경을 λ‹€λ₯Έ μŠ€λ ˆλ“œκ°€ 보지 λͺ»ν•  μˆ˜λ„ μžˆλ‹€. κ³΅μœ λ˜λŠ” κ°€λ³€ 데이터λ₯Ό λ™κΈ°ν™”ν•˜λŠ” 데 μ‹€νŒ¨ν•˜λ©΄ 응닡 λΆˆκ°€ μƒνƒœμ— λΉ μ§€κ±°λ‚˜ μ•ˆμ „ μ‹€νŒ¨λ‘œ μ΄μ–΄μ§ˆ 수 μžˆλ‹€. μ΄λŠ” 디버깅 λ‚œμ΄λ„κ°€ κ°€μž₯ 높은 λ¬Έμ œμ— μ†ν•œλ‹€. κ°„ν—μ μ΄κ±°λ‚˜ νŠΉμ • νƒ€μ΄λ°μ—λ§Œ λ°œμƒν•  μˆ˜λ„ 있고, VM에 따라 ν˜„μƒμ΄ 달라지기도 ν•œλ‹€. 배타적 싀행은 ν•„μš” μ—†κ³  μŠ€λ ˆλ“œ 끼리의 ν†΅μ‹ λ§Œ ν•„μš”ν•˜λ‹€λ©΄ volatile ν•œμ •μžλ§ŒμœΌλ‘œ 동기화할 수 μžˆλ‹€. λ‹€λ§Œ μ˜¬λ°”λ‘œ μ‚¬μš©ν•˜κΈ°κ°€ κΉŒλ‹€λ‘­λ‹€.