your programing

유용한 휘발성 키워드는 무엇입니까

lovepro 2020. 10. 2. 23:01
반응형

유용한 휘발성 키워드는 무엇입니까


오늘 직장 volatile에서 Java 키워드를 발견했습니다. 익숙하지 않아서 다음 설명을 찾았습니다.

자바 이론과 실습 : 변동성 관리

해당 기사에서 문제의 키워드를 설명하는 세부 정보를 고려할 때 해당 키워드를 사용 했습니까? 아니면이 키워드를 올바른 방식으로 사용할 수있는 경우를 볼 수 있습니까?


volatile메모리 가시성에 대한 의미가 있습니다. 기본적으로 volatile필드 의 값은 쓰기 작업이 완료된 후 모든 판독기 (특히 다른 스레드)에게 표시됩니다. 가 없으면 volatile독자는 업데이트되지 않은 값을 볼 수 있습니다.

귀하의 질문에 답하려면 : 예, volatile일부 코드가 루프를 계속할지 여부를 제어 하기 위해 변수를 사용합니다 . 루프는 volatile값을 테스트하고 값이있는 경우 계속합니다 true. 조건은 false"stop"메소드를 호출하여 설정할 수 있습니다 . 루프는 falsestop 메서드가 실행을 완료 한 후 값을 테스트 할 때보고 종료됩니다.

필자가 강력히 추천하는 " Java Concurrency in Practice "라는 책 은에 대한 좋은 설명을 제공합니다 volatile. 이 책은 질문에서 언급 된 IBM 기사를 작성한 동일한 사람이 작성했습니다 (사실 그는 해당 기사의 맨 아래에 자신의 책을 인용합니다). 내가 사용하는 volatile것은 그의 기사에서 "패턴 1 상태 플래그"라고 부르는 것입니다.

내부에서 volatile작동 하는 방식에 대해 자세히 알아 보려면 Java 메모리 모델을 읽어보십시오 . 그 수준을 넘어서고 싶다면 Hennessy & Patterson같은 좋은 컴퓨터 아키텍처 책을 확인하고 캐시 일관성 및 캐시 일관성에 대해 읽어보십시오.


"… volatile 한정자는 필드를 읽는 모든 스레드가 가장 최근에 기록 된 값을 볼 수 있도록 보장합니다." -Josh Bloch

를 사용할 생각이라면 원자 동작을 다루는 volatile패키지 java.util.concurrent읽어보십시오 . Singleton Pattern

에 대한 Wikipedia 게시물 은 사용중인 휘발성을 보여줍니다.


에 대한 중요 사항 volatile:

  1. 자바의 동기화는 자바 키워드를 사용하여 가능 synchronized하고 volatile및 잠금.
  2. Java에서는 synchronized변수를 가질 수 없습니다 . 사용 synchronized변수와 키워드 불법 컴파일 오류가 발생합니다. synchronizedJava 에서 변수 를 사용하는 대신 Java 변수를 사용하면 volatileJVM 스레드가 volatile주 메모리에서 변수 값을 읽고 로컬로 캐시하지 않도록 지시 할 수 있습니다.
  3. 변수가 여러 스레드간에 공유되지 않으면 volatile키워드 를 사용할 필요가 없습니다 .

출처

사용 예 volatile:

public class Singleton {
    private static volatile Singleton _instance; // volatile variable
    public static Singleton getInstance() {
        if (_instance == null) {
            synchronized (Singleton.class) {
                if (_instance == null)
                    _instance = new Singleton();
            }
        }
        return _instance;
    }
}

우리는 첫 번째 요청이 올 때 느리게 인스턴스를 생성하고 있습니다.

우리가하지 않으면 _instance변수 volatile의 인스턴스를 생성하는 스레드 다음을 Singleton다른 스레드와 통신 할 수 없습니다. 따라서 스레드 A가 Singleton 인스턴스를 생성하고 생성 직후 CPU가 손상되면 다른 모든 스레드는 값을 _instancenull 이 아닌 것으로 볼 수 없으며 여전히 null이 할당되었다고 믿습니다.

왜 이런 일이 발생합니까? 판독기 스레드가 잠금을 수행하지 않고 작성기 스레드가 동기화 된 블록에서 나올 때까지 메모리가 동기화되지 않고의 값이 _instance주 메모리에서 업데이트되지 않습니다. Java의 Volatile 키워드를 사용하면 Java 자체에서 처리되며 이러한 업데이트는 모든 판독기 스레드에서 볼 수 있습니다.

결론 : volatile키워드는 스레드간에 메모리 내용을 전달하는 데에도 사용됩니다.

휘발성없이 사용 예 :

public class Singleton{    
    private static Singleton _instance;   //without volatile variable
    public static Singleton getInstance(){   
          if(_instance == null){  
              synchronized(Singleton.class){  
               if(_instance == null) _instance = new Singleton(); 
      } 
     }   
    return _instance;  
    }

위의 코드는 스레드로부터 안전하지 않습니다. 동기화 된 블록 내에서 인스턴스 값을 다시 확인하지만 (성능상의 이유로) JIT 컴파일러는 생성자가 실행을 완료하기 전에 인스턴스에 대한 참조가 설정되는 방식으로 바이트 코드를 재 배열 할 수 있습니다. 이는 getInstance () 메소드가 완전히 초기화되지 않았을 수있는 객체를 반환 함을 의미합니다. 스레드로부터 안전한 코드를 만들기 위해 인스턴스 변수에 대해 Java 5부터 키워드 volatile을 사용할 수 있습니다. 휘발성으로 표시된 변수는 객체의 생성자가 실행을 완전히 완료 한 후에 만 ​​다른 스레드에 표시됩니다.
출처

여기에 이미지 설명 입력

volatileJava에서의 사용법 :

Fail-Fast 반복기는 일반적으로volatile 목록 개체 카운터를 사용하여 구현됩니다 .

  • 목록이 업데이트되면 카운터가 증가합니다.
  • Iterator가 생성 되면 카운터의 현재 값이 Iterator개체에 포함됩니다 .
  • Iterator작업이 수행 될 때 메서드는 두 카운터 값을 비교하고 ConcurrentModificationException다른 경우 a를 발생시킵니다.

오류 방지 반복기의 구현은 일반적으로 가볍습니다. 일반적으로 특정 목록 구현의 데이터 구조 속성에 의존합니다. 일반적인 패턴은 없습니다.


휘발성은 스레드를 중지하는 데 매우 유용합니다.

자신의 스레드를 작성해야한다는 것이 아니라 Java 1.6에는 멋진 스레드 풀이 많이 있습니다. 그러나 스레드가 필요하다고 확신하는 경우 스레드를 중지하는 방법을 알아야합니다.

스레드에 사용하는 패턴은 다음과 같습니다.

public class Foo extends Thread {
  private volatile boolean close = false;
  public void run() {
    while(!close) {
      // do work
    }
  }
  public void close() {
    close = true;
    // interrupt here if needed
  }
}

동기화가 필요하지 않은지 확인하십시오.


일반적인 사용 예 는 스레드를 종료하기위한 플래그로 변수 volatile를 사용하는 것 volatile boolean입니다. 스레드를 시작했고 다른 스레드에서 안전하게 인터럽트 할 수 있도록하려면 스레드가 주기적으로 플래그를 확인하도록 할 수 있습니다. 중지하려면 플래그를 true로 설정하십시오. 플래그 volatile를 만들면이를 확인하는 스레드가 synchronized블록을 사용하지 않고도 다음에 확인할 때 설정되었음을 확인할 수 있습니다 .


volatile키워드로 선언 된 변수는이를 특별하게 만드는 두 가지 주요 특성이 있습니다.

  1. 휘발성 변수가 있으면 어떤 스레드도 컴퓨터 (마이크로 프로세서) 캐시 메모리에 캐시 할 수 없습니다. 액세스는 항상 주 메모리에서 발생했습니다.

  2. 휘발성 변수에 대한 쓰기 작업 이 있고 갑자기 읽기 작업 이 요청되면 읽기 작업 전에 쓰기 작업이 완료됩니다 .

위의 두 가지 특성은

  • 휘발성 변수를 읽는 모든 스레드는 확실히 최신 값을 읽습니다. 캐시 된 값이이를 오염시킬 수 없기 때문입니다. 또한 읽기 요청은 현재 쓰기 작업이 완료된 후에 만 ​​허용됩니다.

반면에

  • 내가 언급 한 # 2더 조사 해보면, volatile키워드가 'n'개의 리더 스레드와 하나의 라이터 스레드 만 있는 공유 변수를 유지하는 이상적인 방법 이라는 것을 알 수 있습니다. volatile키워드를 추가하면 완료됩니다. 스레드 안전성에 대한 다른 오버 헤드가 없습니다.

반대로

우리는 할 수없는 사용하기 volatile가 공유 변수 만족 전적으로 키워드 를 액세스하는 하나 개 이상의 작가 스레드 .


long 및 double 변수 유형에 대한 읽기 및 쓰기 작업의 처리에 대해 언급 한 사람은 없습니다. 읽기 및 쓰기는 원자 적 작업이 되려면 volatile 키워드를 사용해야하는 long 및 double 변수 유형을 제외하고 참조 변수 및 대부분의 기본 변수에 대한 원자 적 작업입니다. @링크


예, 여러 스레드에서 변경 가능한 변수에 액세스하기를 원할 때마다 volatile을 사용해야합니다. 일반적으로 하나 이상의 원자 연산 (예 : 변수 상태를 수정하기 전에 확인)을 수행해야하기 때문에 일반적으로 사용되지 않습니다.이 경우 대신 동기화 된 블록을 사용합니다.


제 생각에는 volatile 키워드가 사용되는 스레드 중지 이외의 두 가지 중요한 시나리오는 다음과 같습니다.

  1. 재확인 된 잠금 메커니즘 . 싱글 톤 디자인 패턴에서 자주 사용됩니다. 여기에서 싱글 톤 객체는 volatile로 선언되어야 합니다.
  2. 스퓨리어스 웨이크 업 . 알림 호출이 실행되지 않은 경우에도 스레드가 대기 호출에서 깨어날 수 있습니다. 이 동작을 스퓨리어스 웨이크 업이라고합니다. 이것은 조건부 변수 (부울 플래그)를 사용하여 대응할 수 있습니다. 플래그가 참인 동안 wait () 호출을 while 루프에 넣으십시오. 따라서 스레드가 Notify / NotifyAll 이외의 이유로 인해 대기 호출에서 깨어 나면 플래그가 여전히 true이므로 호출이 다시 대기합니다. 알림을 호출하기 전에이 플래그를 true로 설정하십시오. 이 경우 부울 플래그는 volatile로 선언됩니다 .

다중 스레드 응용 프로그램을 개발하는 경우 'volatile'키워드 또는 'synchronized'및 기타 동시성 제어 도구 및 기술을 사용해야합니다. 이러한 애플리케이션의 예로는 데스크톱 앱이 있습니다.

애플리케이션 서버 (Tomcat, JBoss AS, Glassfish 등)에 배포 될 애플리케이션을 개발하는 경우 애플리케이션 서버에서 이미 처리 한 동시성 제어를 직접 처리 할 필요가 없습니다. 사실 Java EE 표준을 올바르게 기억했다면 서블릿과 EJB에서 동시성 제어를 금지하는 것은 '인프라'계층의 일부이므로 처리 할 수 ​​없습니다. 싱글 톤 객체를 구현하는 경우에만 이러한 앱에서 동시성 제어를 수행합니다. 이것은 Spring과 같은 프레임 워크를 사용하여 컴포넌트를 짜는 경우에도 이미 해결되었습니다.

따라서 애플리케이션이 웹 애플리케이션이고 Spring 또는 EJB와 같은 IoC 프레임 워크를 사용하는 Java 개발의 대부분의 경우 '휘발성'을 사용할 필요가 없습니다.


volatile모든 스레드 (자체조차도 포함)가 증가하는 것을 보장합니다. 예 : 카운터는 동시에 변수의 동일한면을 봅니다. 동기화되거나 원 자성 또는 기타 항목 대신 사용되지 않으며 읽기가 완전히 동기화됩니다. 다른 자바 키워드와 비교하지 마십시오. 아래의 예에서 볼 수 있듯이 휘발성 변수 작업도 원자 적이며 한 번에 실패하거나 성공합니다.

package io.netty.example.telnet;

import java.util.ArrayList;
import java.util.List;

public class Main {

    public static volatile  int a = 0;
    public static void main(String args[]) throws InterruptedException{

        List<Thread> list = new  ArrayList<Thread>();
        for(int i = 0 ; i<11 ;i++){
            list.add(new Pojo());
        }

        for (Thread thread : list) {
            thread.start();
        }

        Thread.sleep(20000);
        System.out.println(a);
    }
}
class Pojo extends Thread{
    int a = 10001;
    public void run() {
        while(a-->0){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Main.a++;
            System.out.println("a = "+Main.a);
        }
    }
}

휘발성이 있든 없든 결과는 항상 다릅니다. 그러나 아래와 같이 AtomicInteger를 사용하면 결과는 항상 동일합니다. 이것은 동기화와 동일합니다.

    package io.netty.example.telnet;

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.atomic.AtomicInteger;

    public class Main {

        public static volatile  AtomicInteger a = new AtomicInteger(0);
        public static void main(String args[]) throws InterruptedException{

            List<Thread> list = new  ArrayList<Thread>();
            for(int i = 0 ; i<11 ;i++){
                list.add(new Pojo());
            }

            for (Thread thread : list) {
                thread.start();
            }

            Thread.sleep(20000);
            System.out.println(a.get());

        }
    }
    class Pojo extends Thread{
        int a = 10001;
        public void run() {
            while(a-->0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Main.a.incrementAndGet();
                System.out.println("a = "+Main.a);
            }
        }
    }

예, 꽤 많이 사용합니다. 다중 스레드 코드에 매우 유용 할 수 있습니다. 당신이 지적한 기사는 좋은 기사입니다. 명심해야 할 두 가지 중요한 사항이 있습니다.

  1. 휘발성의 기능과 동기화와의 차이점을 완전히 이해 한 경우에만 휘발성을 사용해야합니다. 많은 상황에서 휘발성은 표면적으로 동기화에 대한 더 간단한 대안으로 나타납니다. 종종 휘발성에 대한 더 나은 이해는 동기화가 작동하는 유일한 옵션임을 분명히 할 것입니다.
  2. volatile은 동기화 된 경우에도 실제로 많은 구형 JVM에서 작동하지 않습니다. 여러 JVM에서 다양한 수준의 지원을 참조한 문서를 본 기억이 있지만 불행히도 지금은 찾을 수 없습니다. Java 1.5 이전 버전을 사용 중이거나 프로그램이 실행될 JVM을 제어 할 수없는 경우 확실히 살펴보십시오.

확실히 맞아요. (그리고 Java뿐만 아니라 C #에서도 마찬가지입니다.) 주어진 플랫폼에서 원자 적 연산 (예 : int 또는 boolean)이 보장되는 값을 가져 오거나 설정해야하는 경우가 있습니다. 스레드 잠금의 오버 헤드. volatile 키워드를 사용하면 값을 읽을 때 다른 스레드의 쓰기에 의해 폐기 된 캐시 된 값이 아닌 현재을 얻을 수 있습니다.


휘발성 필드에 액세스하는 모든 스레드는 캐시 된 값을 사용하는 대신 (잠재적으로) 계속하기 전에 현재 값을 읽습니다.

멤버 변수 만 휘발성 또는 일시적 일 수 있습니다.


volatile 키워드에는 두 가지 용도가 있습니다.

  1. JVM이 레지스터 (캐시로 가정)에서 값을 읽지 못하게하고 해당 값을 메모리에서 읽도록합니다.
  2. 메모리 불일치 오류의 위험을 줄입니다.

JVM이 레지스터의 값을 읽지 못하게하고 해당 값을 메모리에서 읽도록합니다.

비지 플래그 장치가 비지하고 플래그가 로크에 의해 보호되지 않은 상태에서 계속 스레드를 방지하기 위해 사용된다 :

while (busy) {
    /* do something else */
}

다른 스레드가 busy 플래그를 끄면 테스트 스레드가 계속됩니다 .

busy = 0;

그러나 busy는 테스트 스레드에서 자주 액세스되기 때문에 JVM은 busy 값을 레지스터에 배치하여 테스트를 최적화 한 다음 매 테스트 전에 busy 값을 읽지 않고 레지스터의 내용을 테스트 할 수 있습니다. 테스트 스레드는 사용중인 변경을 볼 수 없으며 다른 스레드는 메모리에서 사용 중 값만 변경하여 교착 상태가 발생합니다. 사용 중 플래그 를 휘발성으로 선언하면 각 테스트 전에 해당 값을 읽습니다.

메모리 일관성 오류의 위험을 줄입니다.

휘발성 변수를 사용하면 메모리 일관성 오류 의 위험이 줄어 듭니다. 휘발성 변수에 대한 모든 쓰기 는 동일한 변수의 후속 읽기와 "이전 발생" 관계를 설정 하기 때문 입니다. 이는 휘발성 변수의 변경 사항이 항상 다른 스레드에 표시됨을 의미합니다.

메모리 일관성 오류없이 읽고 쓰는 기술을 원자 적 동작 이라고 합니다 .

원자 적 행동은 한 번에 효과적으로 발생하는 행동입니다. 원자 적 행동은 중간에서 멈출 수 없습니다. 그것은 완전히 일어나거나 전혀 일어나지 않습니다. 작업이 완료 될 때까지 원자 적 작업의 부작용이 보이지 않습니다.

다음은 원 자성으로 지정할 수있는 작업입니다.

  • 읽기 및 쓰기는 참조 변수 및 대부분의 기본 변수 (long 및 double을 제외한 모든 유형)에 대해 원자 적입니다.
  • 읽기 및 쓰기는 휘발성으로 선언 된 모든 변수 (long 및 double 변수 포함)에 대해 원자 적입니다 .

건배!


Volatile은 다음을 수행합니다.

1> 다른 스레드에 의한 휘발성 변수의 읽기 및 쓰기는 항상 스레드의 자체 캐시 또는 CPU 레지스터가 아닌 메모리에서 이루어집니다. 따라서 각 스레드는 항상 최신 값을 처리합니다. 2> 2 개의 서로 다른 스레드가 동일한 인스턴스 또는 힙의 정적 변수로 작업 할 때 다른 사람의 작업이 순서가 잘못되었다고 볼 수 있습니다. 이에 대한 Jeremy Manson의 블로그를 참조하십시오. 그러나 여기서 휘발성이 도움이됩니다.

완전히 실행되는 다음 코드는 동기화 된 키워드를 사용하지 않고 미리 정의 된 순서로 여러 스레드를 실행하고 출력을 인쇄하는 방법을 보여줍니다.

thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3
thread 0 prints 0
thread 1 prints 1
thread 2 prints 2
thread 3 prints 3

이를 위해 다음과 같은 완전한 실행 코드를 사용할 수 있습니다.

public class Solution {
    static volatile int counter = 0;
    static int print = 0;
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Thread[] ths = new Thread[4];
        for (int i = 0; i < ths.length; i++) {
            ths[i] = new Thread(new MyRunnable(i, ths.length));
            ths[i].start();
        }
    }
    static class MyRunnable implements Runnable {
        final int thID;
        final int total;
        public MyRunnable(int id, int total) {
            thID = id;
            this.total = total;
        }
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while (true) {
                if (thID == counter) {
                    System.out.println("thread " + thID + " prints " + print);
                    print++;
                    if (print == total)
                        print = 0;
                    counter++;
                    if (counter == total)
                        counter = 0;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        // log it
                    }
                }
            }
        }
    }
}

다음 github 링크에는 적절한 설명을 제공하는 readme가 있습니다. https://github.com/sankar4git/volatile_thread_ordering


오라클 문서 페이지 에서 메모리 일관성 문제를 해결하기 위해 휘발성 변수가 필요합니다.

휘발성 변수를 사용하면 휘발성 변수에 대한 모든 쓰기가 동일한 변수의 후속 읽기와 사전 발생 관계를 설정하기 때문에 메모리 일관성 오류의 위험이 줄어 듭니다.

즉, volatile변수 에 대한 변경 사항 은 항상 다른 스레드에 표시됩니다. 또한 스레드가 휘발성 변수를 읽을 때에 대한 최신 변경 사항 volatile뿐만 아니라 변경을 이끈 코드의 부작용도 볼 수 있습니다.

As explained in Peter Parker answer, in absence of volatile modifier, each thread's stack may have their own copy of variable. By making the variable as volatile, memory consistency issues have been fixed.

Have a look at jenkov tutorial page for better understanding.

Have a look at related SE question for some more details on volatile & use cases to use volatile:

Difference between volatile and synchronized in Java

One practical use case:

You have many threads, which need to print current time in a particular format for example : java.text.SimpleDateFormat("HH-mm-ss"). Yon can have one class, which converts current time into SimpleDateFormat and updated the variable for every one second. All other threads can simply use this volatile variable to print current time in log files.


Volatile Variables are light-weight synchronization. When visibility of latest data among all threads is requirement and atomicity can be compromised , in such situations Volatile Variables must be preferred. Read on volatile variables always return most recent write done by any thread since they are neither cached in registers nor in caches where other processors can not see. Volatile is Lock-Free. I use volatile, when scenario meets criteria as mentioned above.


It is a part of managing hardware memory management. Actually the value of a property can be stored at least on RAM memory or CPU cache memory

In a multithreading envirompment we have a concept as shared resources which is one of issues in this field. Theoretically can run into a situation when you work with out-date resource because different processors work with different values.

Java has a reservation volatile keyword to cope with this situation. It is used to mark a Java variable to read and write to RAM memory directly and atomically.

Java 5 extends volatile responsibility that is called happens-before guarantee[About]. A write to a volatile field happens-before every subsequent read of that same field.

But what about a situation when several threads write different values in the volatile field simultaneously (i.e race condition)? The synchronized keyword is to the rescue[About]

When is volatile Enough?

When only one thread writes and others just read the volatile guarantees that they will see up to date value

Also please take into account that using volatile variable is more expensive task, also it prevents instruction reordering(i.e using happens-before technic) which is a normal performance enhancement technique. Hence you should only use volatile variables when you really need it.

Read Jenkov's explanation


The volatile key when used with a variable, will make sure that threads reading this variable will see the same value . Now if you have multiple threads reading and writing to a variable, making the variable volatile will not be enough and data will be corrupted . Image threads have read the same value but each one has done some chages (say incremented a counter) , when writing back to the memory, data integrity is violated . That is why it is necessary to make the varible synchronized (diffrent ways are possible)

If the changes are done by 1 thread and the others need just to read this value, the volatile will be suitable.


volatile variable is basically used for instant update (flush) in main shared cache line once it updated, so that changes reflected to all worker threads immediately.


다음은 volatile다른 스레드에서 스레드 실행을 제어하는 ​​데 사용되는 for 변수 의 요구 사항을 보여주는 매우 간단한 코드 volatile입니다 (필요한 시나리오 중 하나 입니다).

// Code to prove importance of 'volatile' when state of one thread is being mutated from another thread.
// Try running this class with and without 'volatile' for 'state' property of Task class.
public class VolatileTest {
    public static void main(String[] a) throws Exception {
        Task task = new Task();
        new Thread(task).start();

        Thread.sleep(500);
        long stoppedOn = System.nanoTime();

        task.stop(); // -----> do this to stop the thread

        System.out.println("Stopping on: " + stoppedOn);
    }
}

class Task implements Runnable {
    // Try running with and without 'volatile' here
    private volatile boolean state = true;
    private int i = 0;

    public void stop() {
        state = false;
    } 

    @Override
    public void run() {
        while(state) {
            i++;
        }
        System.out.println(i + "> Stopped on: " + System.nanoTime());
    }
}

volatile사용되지 않는 : 당신은 '볼 수 없을 것입니다 : XXX에 중지 도'후 메시지 ' 에 중지 : XXX ', 프로그램이 계속 실행됩니다.

Stopping on: 1895303906650500

volatile사용 : 당신은 '볼 수 있습니다 : XXX에 정지 즉시'.

Stopping on: 1895285647980000
324565439> Stopped on: 1895285648087300

데모 : https://repl.it/repls/SilverAgonizingObjectcode

참고 URL : https://stackoverflow.com/questions/106591/what-is-the-volatile-keyword-useful-for

반응형