Table of Contents

1 동기

volatile의 필요성을 동료분들에게 공유하기 위함

2 설명

Oracle의 설명을 그대로 첨부.

Thread.sleep causes the currently executing thread to sleep (temporarily cease execution) for the specified duration, subject to the precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors, and resumption of execution will depend on scheduling and the availability of processors on which to execute the thread.

It is important to note that neither Thread.sleep nor Thread.yield have any synchronization semantics. In particular, the compiler does not have to flush writes cached in registers out to shared memory before a call to Thread.sleep or Thread.yield, nor does the compiler have to reload values cached in registers after a call to Thread.sleep or Thread.yield.

For example, in the following (broken) code fragment, assume that this.done is a non-volatile boolean field:

while (!this.done)
    Thread.sleep(1000);

The compiler is free to read the field this.done just once, and reuse the cached value in each execution of the loop. This would mean that the loop would never terminate, even if another thread changed the value of this.done.

즉, Thread.sleep, Thread.yeild 는 스레드를 일시적으로 절전모드로 만드는데 절전이후에 데이터 동기화는 하지않는다. 특히 컴파일러는 Thread.sleep, Thread.yield 를 호출하기전에 레지스터에 캐시된 쓰기를 공유 메모리로 flush할 필요가 없으므로 Thread.sleep, Thread.yield 호출 이후 레지스터에 캐시된값을 다시 로드할 필요가 없다.

하여 위 코드는 평생 멈추지 않고 loop를 수행할 수 있다.

3 코드

아래 코드는 테스트상에서 volatile 이 있는 경우와 없는 경우를 테스트한 것이다. 기본적으로 flag값이 volatile이거나 아닌경우를 갖고 Executor에서 수행되고 있는 loop가 멈추는지를 테스트하는 것이다. nonVolatileflag 를 쓰는 경우, 10초를 기다려도 멈추지 않는 것을 알 수 있다.

import mu.KotlinLogging
import org.junit.jupiter.api.Test
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit

class VolatileTest {

    private var nonVolatileFlag = false
    @Volatile
    private var volatileFlag = false

    var logger = KotlinLogging.logger {}
    @Test
    fun testWithoutVolatile() {
	val executorService = Executors.newFixedThreadPool(2)

	executorService.submit {
	    Thread.sleep(100) // 일부 지연
	    nonVolatileFlag = true
	    logger.info("done")
	}

	executorService.submit {
	    var counter = 0
	    while (!nonVolatileFlag) {
		counter++
	    }
	    logger.info("Without volatile, iterations: $counter")
	}

	executorService.shutdown()
	// 10초를 기다려도 끝나지 않는다.
	executorService.awaitTermination(10, TimeUnit.SECONDS)
    }

    @Test
    fun testWithVolatile() {
	val executorService = Executors.newFixedThreadPool(2)

	executorService.submit {
	    Thread.sleep(100) // 일부 지연
	    volatileFlag = true
	    logger.info("done")

	}

	executorService.submit {
	    var counter = 0
	    while (!volatileFlag) {
		counter++
		// volatileFlag가 true가 될 때까지 대기
	    }
	    logger.info("With volatile, iterations: $counter")
	}

	executorService.shutdown()
	executorService.awaitTermination(1, TimeUnit.SECONDS)
    }
}

Author: Younghwan Nam

Created: 2024-05-02 Thu 03:16

Emacs 27.2 (Org mode 9.4.4)

Validate