programing

Java에서 휘발성 키워드의 가장 간단하고 이해하기 쉬운 예

nasanasas 2020. 11. 8. 10:09
반응형

Java에서 휘발성 키워드의 가장 간단하고 이해하기 쉬운 예


Java의 휘발성 키워드에 대해 읽고 있으며 이론 부분을 완전히 이해합니다.

하지만 제가 찾고있는 것은 변수가 휘발성 이 아닌 경우 어떤 일이 발생하는지 보여주는 좋은 사례 입니다.

아래 코드 스 니펫이 예상대로 작동하지 않습니다 ( 여기 에서 가져옴 ).

class Test extends Thread {

    boolean keepRunning = true;

    public void run() {
        while (keepRunning) {
        }

        System.out.println("Thread terminated.");
    }

    public static void main(String[] args) throws InterruptedException {
        Test t = new Test();
        t.start();
        Thread.sleep(1000);
        t.keepRunning = false;
        System.out.println("keepRunning set to false.");
    }
}

이상적으로 volatilekeepRunning 이 아니라면 스레드는 무기한으로 계속 실행되어야합니다. 그러나 몇 초 후에 중지됩니다.

두 가지 기본적인 질문이 있습니다.

  • 누구든지 예를 들어 휘발성을 설명 할 수 있습니까? JLS의 이론이 아닙니다.
  • 휘발성이 동기화를 대체합니까? 원 자성을 달성합니까?

휘발성-> 원 자성이 아닌 가시성을 보장합니다.

동기화 (잠금)-> 가시성과 원 자성 보장 (올바르게 수행 된 경우)

휘발성은 동기화를 대체하지 않습니다.

참조를 업데이트하고 다른 작업을 수행하지 않는 경우에만 volatile을 사용하십시오.

예:

volatile int i = 0;

public void incrementI(){
   i++;
}

증가는 복합 연산이므로 동기화 또는 AtomicInteger를 사용하지 않으면 스레드로부터 안전하지 않습니다.

프로그램이 무기한 실행되지 않는 이유는 무엇입니까?

다양한 상황에 따라 다릅니다. 대부분의 경우 JVM은 내용을 플러시 할만큼 똑똑합니다.

휘발성의 올바른 사용은 휘발성의 다양한 가능한 사용에 대해 설명합니다. volatile을 올바르게 사용하는 것은 까다 롭습니다. "의심스러운 경우에는 그대로 두십시오"라고 말하고 대신 동기화 된 블록을 사용합니다.

또한:

volatile 대신 동기화 된 블록을 사용할 수 있지만 그 반대는 사실이 아닙니다 .


특정 예의 경우 : volatile로 선언되지 않은 경우 서버 JVM은 keepRunning변수가 루프 에서 수정되지 않았으므로 (무한 루프로 전환) 루프 에서 변수를 끌어 올릴 수 있지만 클라이언트 JVM은 그렇지 않습니다. 그것이 다른 결과를 보는 이유입니다.

휘발성 변수에 대한 일반적인 설명은 다음과 같습니다.

필드가 선언 volatile되면 컴파일러와 런타임은이 변수가 공유되고 그에 대한 작업이 다른 메모리 작업과 함께 재정렬되어서는 안된다는 알림을받습니다. 휘발성 변수는 레지스터 또는 다른 프로세서에서 숨겨진 캐시에 캐시되지 않으므로 휘발성 변수를 읽으면 항상 스레드에 의한 가장 최근 쓰기가 반환됩니다 .

휘발성 변수의 가시성 효과는 휘발성 변수 자체의 값을 넘어 확장됩니다. 스레드 A가 휘발성 변수에 쓰고이어서 스레드 B가 동일한 변수를 읽으면 휘발성 변수에 쓰기 전에 A에게 표시되었던 모든 변수의 값이 휘발성 변수를 읽은 후 B에게 표시됩니다.

휘발성 변수의 가장 일반적인 용도는 완료, 중단 또는 상태 플래그입니다.

  volatile boolean flag;
  while (!flag)  {
     // do something untill flag is true
  }

휘발성 변수는 다른 종류의 상태 정보에 사용할 수 있지만이를 시도 할 때는 더 많은주의가 필요합니다. 예를 들어, count++변수가 단일 스레드에서만 작성된다는 것을 보장 할 수없는 경우 volatile의 의미는 증가 연산 ( )을 원자 적으로 만들만큼 강력하지 않습니다 .

잠금은 가시성과 원 자성을 모두 보장 할 수 있습니다. 휘발성 변수는 가시성을 보장 할 수만 있습니다.

다음 기준이 모두 충족되는 경우에만 휘발성 변수를 사용할 수 있습니다.

  • 변수에 대한 쓰기는 현재 값에 의존하지 않거나 단일 스레드 만 값을 업데이트하도록 할 수 있습니다.
  • 변수는 다른 상태 변수와 함께 불변에 참여하지 않습니다.
  • 변수에 액세스하는 동안에는 다른 이유로 잠금이 필요하지 않습니다.

디버깅 팁 : -server개발 및 테스트를 위해 JVM을 호출 할 때 항상 JVM 명령 줄 스위치를 지정해야합니다 . 서버 JVM은 클라이언트 JVM보다 더 많은 최적화를 수행합니다. 예를 들어, 루프에서 수정되지 않은 변수를 루프 밖으로 끌어 올리는 것입니다. 개발 환경 (클라이언트 JVM)에서 작동하는 것처럼 보일 수있는 코드는 배포 환경 (서버 JVM)에서 손상 될 수 있습니다.

이것은 이 주제에 대해 찾을 수있는 최고의 책인 "Java Concurrency in Practice"에서 발췌 한 것입니다 .


귀하의 예를 약간 수정했습니다. 이제 keepRunning을 휘발성 및 비 휘발성 멤버로 사용하는 예제를 사용하십시오.

class TestVolatile extends Thread{
    //volatile
    boolean keepRunning = true;

    public void run() {
        long count=0;
        while (keepRunning) {
            count++;
        }

        System.out.println("Thread terminated." + count);
    }

    public static void main(String[] args) throws InterruptedException {
        TestVolatile t = new TestVolatile();
        t.start();
        Thread.sleep(1000);
        System.out.println("after sleeping in main");
        t.keepRunning = false;
        t.join();
        System.out.println("keepRunning set to " + t.keepRunning);
    }
}

휘발성 키워드는 무엇입니까?

volatile 키워드는 caching of variables.

먼저 volatile 키워드가 없는 코드를 고려하십시오.

class MyThread extends Thread {
    private boolean running = true;   //non-volatile keyword

    public void run() {
        while (running) {
            System.out.println("hello");
        }
    }

    public void shutdown() {
        running = false;
    }
}

public class Main {

    public static void main(String[] args) {
        MyThread obj = new MyThread();
        obj.start();

        Scanner input = new Scanner(System.in);
        input.nextLine(); 
        obj.shutdown();   
    }    
}

이상적 으로이 프로그램 은를 누를 print hello때까지 해야합니다 RETURN key. 그러나에 some machines그것을 그 변수 발생할 수 있습니다 실행이 있습니다 cached당신은에 어떤 결과를 종료 () 메소드에서 값을 변경할 수 없습니다 infinite안녕하세요 텍스트의 인쇄.

따라서 휘발성 키워드를 사용하여, 그 것이다 guaranteed당신의 변수 즉, 것이다 캐시되지 않습니다 run fineall machines.

private volatile boolean running = true;  //volatile keyword

따라서 volatile 키워드를 사용하는 것은 goodsafer programming practice입니다.


Variable Volatile: Volatile Keyword는 변수에 적용 할 수 있습니다. Java의 volatile 키워드는 volatile 변수의 값을 항상 스레드의 로컬 캐시가 아닌 주 메모리에서 읽도록 보장합니다.

Access_Modifier volatile DataType Variable_Name;

Volatile Field : 여러 스레드가 동시에 필드 값에 액세스 / 업데이트하려고 할 수 있음을 VM에 표시합니다. Modified 값을 가진 모든 스레드간에 공유되어야하는 특별한 종류의 인스턴스 변수에. Static (Class) 변수와 유사하게 휘발성 값의 복사본이 하나만 주 메모리에 캐시되므로 ALU 작업을 수행하기 전에 각 스레드는 ALU 작업 후 주 메모리에서 업데이트 된 값을 읽어 주 메모리 디렉터리에 써야합니다. (휘발성 변수 v에 대한 쓰기는 모든 스레드에 의한 v의 모든 후속 읽기와 동기화 됨) 이는 휘발성 변수의 변경 사항이 항상 다른 스레드에 표시됨을 의미합니다.

여기에 이미지 설명 입력

여기서 nonvoltaile variable스레드 t1이 t1의 캐시에서 값을 변경하면 스레드 t2는 t1이 쓸 때까지 변경된 값에 액세스 할 수없고, t2는 가장 최근에 수정 된 값에 대해 메인 메모리에서 읽혀질 수 있습니다 Data-Inconsistancy.

volatile은 캐시 할 수 없음 - 어셈블러

    +--------------+--------+-------------------------------------+
    |  Flag Name   |  Value | Interpretation                      |
    +--------------+--------+-------------------------------------+
    | ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached.|
    +--------------+--------+-------------------------------------+
    |ACC_TRANSIENT | 0x0080 | Declared transient; not written or  |
    |              |        | read by a persistent object manager.|
    +--------------+--------+-------------------------------------+

Shared Variables: 스레드간에 공유 할 수있는 메모리를 공유 메모리 또는 힙 메모리라고합니다. 모든 인스턴스 필드, 정적 필드 및 배열 요소는 힙 메모리에 저장됩니다.

동기화 : 동기화는 메소드, 블록에 적용 가능합니다. 객체에서 한 번에 하나의 스레드 만 실행할 수 있습니다. t1이 제어권을 가지면 나머지 스레드는 제어를 해제 할 때까지 기다려야합니다.

예:

public class VolatileTest implements Runnable {

    private static final int MegaBytes = 10241024;

    private static final Object counterLock = new Object();
    private static int counter = 0;
    private static volatile int counter1 = 0;

    private volatile int counter2 = 0;
    private int counter3 = 0;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            concurrentMethodWrong();
        }

    }

    void addInstanceVolatile() {
        synchronized (counterLock) {
            counter2 = counter2 + 1;
            System.out.println( Thread.currentThread().getName() +"\t\t « InstanceVolatile :: "+ counter2);
        }
    }

    public void concurrentMethodWrong() {
        counter = counter + 1;
        System.out.println( Thread.currentThread().getName() +" « Static :: "+ counter);
        sleepThread( 1/4 );

        counter1 = counter1 + 1;
        System.out.println( Thread.currentThread().getName() +"\t « StaticVolatile :: "+ counter1);
        sleepThread( 1/4 );

        addInstanceVolatile();
        sleepThread( 1/4 );

        counter3 = counter3 + 1;
        sleepThread( 1/4 );
        System.out.println( Thread.currentThread().getName() +"\t\t\t\t\t « Instance :: "+ counter3);
    }
    public static void main(String[] args) throws InterruptedException {
        Runtime runtime = Runtime.getRuntime();

        int availableProcessors = runtime.availableProcessors();
        System.out.println("availableProcessors :: "+availableProcessors);
        System.out.println("MAX JVM will attempt to use : "+ runtime.maxMemory() / MegaBytes );
        System.out.println("JVM totalMemory also equals to initial heap size of JVM : "+ runtime.totalMemory() / MegaBytes );
        System.out.println("Returns the amount of free memory in the JVM : "+ untime.freeMemory() / MegaBytes );
        System.out.println(" ===== ----- ===== ");

        VolatileTest volatileTest = new VolatileTest();
        Thread t1 = new Thread( volatileTest );
        t1.start();

        Thread t2 = new Thread( volatileTest );
        t2.start();

        Thread t3 = new Thread( volatileTest );
        t3.start();

        Thread t4 = new Thread( volatileTest );
        t4.start();

        Thread.sleep( 10 );;

        Thread optimizeation = new Thread() {
            @Override public void run() {
                System.out.println("Thread Start.");

                Integer appendingVal = volatileTest.counter2 + volatileTest.counter2 + volatileTest.counter2;

                System.out.println("End of Thread." + appendingVal);
            }
        };
        optimizeation.start();
    }

    public void sleepThread( long sec ) {
        try {
            Thread.sleep( sec * 1000 );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Static [ Class Field] vs Volatile [ Instance Field]-둘 다 스레드에 의해 캐시되지 않습니다.

  • 정적 필드는 모든 스레드에 공통이며 메소드 영역에 저장됩니다. 휘발성이있는 정전기는 사용하지 않습니다. 정적 필드는 직렬화 할 수 없습니다.

  • 휘발성은 주로 힙 영역에 저장되는 인스턴스 변수와 함께 사용됩니다. volatile의 주요 용도는 모든 스레드에서 업데이트 된 값을 유지하는 것입니다. 인스턴스 휘발성 필드는 직렬화 될 수 있습니다 .

@보다


이상적으로 keepRunning이 휘발성이 아니라면 스레드는 무기한으로 계속 실행되어야합니다. 그러나 몇 초 후에 중지됩니다.

단일 프로세서에서 실행 중이거나 시스템이 매우 바쁜 경우 OS가 스레드를 교체하여 일정 수준의 캐시 무효화를 유발할 수 있습니다. 다른 언급 한 것처럼,이 있지 않는 volatile그 메모리를 의미하지 않습니다 하지 공유 할 수 있지만,이 성능상의 이유로 메모리가 업데이트되지 않을 수 있습니다 수있는 경우 JVM은하지 동기화 메모리에 노력하고있다.

주목해야 할 또 다른 사항은 System.out.println(...)기본 PrintStream이 동기화를 수행하여 겹치는 출력을 중지 하기 때문에 동기화 된다는 것 입니다 . 그래서 당신은 메인 스레드에서 "무료로"메모리 동기화를 얻고 있습니다. 이것은 여전히 ​​읽기 루프가 업데이트를 보는 이유를 설명하지 않습니다.

println(...)라인이 들어 가든 나 가든 상관없이 Intel i7이 설치된 MacBook Pro의 Java6에서 프로그램이 저를 위해 회전합니다.

누구든지 예를 들어 휘발성을 설명 할 수 있습니까? JLS의 이론이 아닙니다.

나는 당신의 모범이 좋다고 생각합니다. 모든 System.out.println(...)문이 제거 된 상태에서 작동하지 않는 이유를 모르겠습니다 . 그것은 나를 위해 작동합니다.

휘발성이 동기화를 대체합니까? 원 자성을 달성합니까?

메모리 동기화 측면 에서 장벽이 양방향 대 양방향 이라는 점을 제외하고 volatilesynchronized블록 과 동일한 메모리 장벽을 던집니다 volatile. volatile읽기는로드 장벽을 던지고 쓰기는 저장 장벽을 던집니다. synchronized블록은 양방향 장벽이다.

그러나의 관점 atomicity에서 대답은 "상황에 따라 다름"입니다. 필드에서 값을 읽거나 쓰는 경우 volatile적절한 원 자성 제공합니다. 그러나 volatile필드를 증가시키는 ++것은 실제로 읽기, 증가, 쓰기의 3 가지 작업 이라는 제한을받습니다 . 이 경우 또는 더 복잡한 뮤텍스의 경우 전체 synchronized블록이 필요할 수 있습니다.


변수가 volatile인 경우 캐시되지 않고 다른 스레드에서 업데이트 된 값을 볼 수 있음을 보장합니다. 그러나 표시 volatile하지 않는다고해서 그 반대가 보장되는 것은 아닙니다. volatile오랫동안 JVM에서 깨졌지만 여전히 잘 이해되지 않은 것들 중 하나였습니다.


volatileJVM과 컴파일러에 따라 반드시 큰 변화를 만들지는 않습니다. 그러나 많은 (에지) 케이스의 경우 변수의 변경 사항이 올바르게 작성되는 것과 대조적으로 눈에 띄지 않게하는 최적화 간의 차이 일 수 있습니다.

기본적으로 옵티마이 저는 레지스터 나 스택에 비 휘발성 변수를 배치하도록 선택할 수 있습니다. 다른 스레드가 힙 또는 클래스의 기본 요소에서이를 변경하면 다른 스레드가 스택에서 계속 검색하여 부실 상태가됩니다.

volatile 이러한 최적화가 발생하지 않고 모든 읽기 및 쓰기가 힙 또는 모든 스레드가 볼 수있는 다른 위치에 직접 이루어 지도록합니다.


아래에서 해결책을 찾으십시오.

이 변수의 값은 스레드 로컬에서 캐시되지 않습니다. 모든 읽기 및 쓰기는 "주 메모리"로 바로 이동합니다. 휘발성은 스레드가 매번 원래 변수를 업데이트하도록 강제합니다.

public class VolatileDemo {

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {

        ChangeMaker changeMaker = new ChangeMaker();
        changeMaker.start();

        ChangeListener changeListener = new ChangeListener();
        changeListener.start();

    }

    static class ChangeMaker extends Thread {

        @Override
        public void run() {
            while (MY_INT < 5){
                System.out.println("Incrementing MY_INT "+ ++MY_INT);
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException exception) {
                    exception.printStackTrace();
                }
            }
        }
    }

    static class ChangeListener extends Thread {

        int local_value = MY_INT;

        @Override
        public void run() {
            while ( MY_INT < 5){
                if( local_value!= MY_INT){
                    System.out.println("Got Change for MY_INT "+ MY_INT);
                    local_value = MY_INT;
                }
            }
        }
    }

}

더 명확하게하려면 이 링크 http://java.dzone.com/articles/java-volatile-keyword-0참조하십시오 .


volatile 키워드는 다른 스레드에 의해 수정 될 수 있음을 JVM에 알립니다. 각 스레드에는 자체 스택이 있으므로 액세스 할 수있는 자체 변수 사본이 있습니다. 스레드가 생성되면 자체 메모리에있는 모든 액세스 가능한 변수의 값을 복사합니다.

public class VolatileTest {
    private static final Logger LOGGER = MyLoggerFactory.getSimplestLogger();

    private static volatile int MY_INT = 0;

    public static void main(String[] args) {
        new ChangeListener().start();
        new ChangeMaker().start();
    }

    static class ChangeListener extends Thread {
        @Override
        public void run() {
            int local_value = MY_INT;
            while ( local_value < 5){
                if( local_value!= MY_INT){
                    LOGGER.log(Level.INFO,"Got Change for MY_INT : {0}", MY_INT);
                     local_value= MY_INT;
                }
            }
        }
    }

    static class ChangeMaker extends Thread{
        @Override
        public void run() {

            int local_value = MY_INT;
            while (MY_INT <5){
                LOGGER.log(Level.INFO, "Incrementing MY_INT to {0}", local_value+1);
                MY_INT = ++local_value;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) { e.printStackTrace(); }
            }
        }
    }
}

이 예제를 휘발성 유무에 관계없이 사용해보십시오.


Objects that are declared as volatile are usually used to communicate state information among threads,To ensure CPU caches are updated, that is, kept in sync, in the presence of volatile fields, a CPU instruction, a memory barrier, often called a membar or fence, is emitted to update CPU caches with a change in a volatile field’s value.

The volatile modifier tells the compiler that the variable modified by volatile can be changed unexpectedly by other parts of your program.

The volatile variable must be used in Thread Context only. see the example here


Lot's of great examples, but I just want to add that there are a number of scenarios where volatile is required so there is no one concrete example to rule them a.

  1. volatile모든 스레드가 주 메모리에서 변수의 최신 값을 가져 오도록 강제 할 수 있습니다 .
  2. synchronization중요한 데이터를 보호하는 데 사용할 수 있습니다.
  3. LockAPI 를 사용할 수 있습니다.
  4. Atomic변수 를 사용할 수 있습니다.

더 많은 Java 휘발성 예제 를 확인하십시오 .

참고 URL : https://stackoverflow.com/questions/17748078/simplest-and-understandable-example-of-volatile-keyword-in-java

반응형