오늘 직장에서 저는 volatile
키워드를 Java로 보았습니다. 그것에 익숙하지 않아서이 설명을 찾았습니다.
해당 기사에서 해당 키워드에 대해 자세히 설명 했으므로 키워드를 사용하거나 올바른 방식으로이 키워드를 사용할 수있는 사례를 볼 수 있습니까?
오늘 직장에서 저는 volatile
키워드를 Java로 보았습니다. 그것에 익숙하지 않아서이 설명을 찾았습니다.
해당 기사에서 해당 키워드에 대해 자세히 설명 했으므로 키워드를 사용하거나 올바른 방식으로이 키워드를 사용할 수있는 사례를 볼 수 있습니까?
답변:
volatile
메모리 가시성에 대한 의미가 있습니다. 기본적으로 volatile
필드 값은 쓰기 작업이 완료된 후 모든 독자 (특히 다른 스레드)에게 표시됩니다. 이 없으면 volatile
독자는 업데이트되지 않은 값을 볼 수 있습니다.
귀하의 질문에 대답하기 위해 : 예, volatile
변수를 사용하여 일부 코드가 루프를 계속하는지 여부를 제어합니다. 루프는 volatile
값을 테스트하고 값이 계속되면 계속합니다 true
. false
"stop"메소드를 호출하여 조건을 설정할 수 있습니다 . 루프는 false
stop 메소드가 실행을 완료 한 후 값을 테스트 할 때보고 종료합니다.
제가 추천하는 " 실제로 Java Concurrency "책에 대한 좋은 설명이 volatile
있습니다. 이 책은 질문에서 언급 된 IBM 기사를 쓴 동일한 사람이 작성했습니다 (사실, 해당 기사의 맨 아래에 그의 책을 인용합니다). 필자가 사용하는 volatile
기사는 "패턴 1 상태 플래그"라고합니다.
volatile
후드 아래에서 작동 하는 방법에 대한 자세한 내용을 보려면 Java 메모리 모델을 읽으십시오 . 이 수준을 넘어서려면 Hennessy & Patterson 과 같은 훌륭한 컴퓨터 아키텍처 책을 확인하고 캐시 일관성 및 캐시 일관성에 대해 읽으십시오.
"… 휘발성 수정자는 필드를 읽는 스레드가 가장 최근에 작성된 값을 볼 수 있도록 보장합니다." -Josh Bloch
사용 volatile
에 대해 생각하고 있다면 java.util.concurrent
원자 행동을 다루는 패키지 를 읽으십시오 . 싱글 톤 패턴
에 대한 Wikipedia 게시물 은 사용 중입니다.
volatile
및 synchronized
키워드는?
void
및 public
키워드".
에 대한 중요한 점 volatile
:
synchronized
하고 volatile
및 잠금.synchronized
변수를 가질 수 없습니다 . 사용 synchronized
변수와 키워드 불법 컴파일 오류가 발생합니다. synchronized
Java 에서 변수 를 사용하는 대신 java volatile
변수를 사용하면 JVM 스레드가 volatile
기본 메모리에서 변수 값을 읽고 로컬로 캐시하지 않도록 지시 할 수 있습니다.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가 손상되면 다른 모든 스레드는 _instance
null이 아닌 값을 볼 수 없으며 여전히 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을 사용할 수 있습니다. 휘발성으로 표시된 변수는 객체 생성자가 실행을 마치면 다른 스레드에서만 볼 수 있습니다.
출처
volatile
Java 사용법 :
페일 패스트 반복기는 일반적으로volatile
목록 객체 의 카운터를 사용하여 구현됩니다 .
Iterator
카운터의 현재 값이 Iterator
개체에 포함됩니다 .Iterator
동작이 수행되고, 상기 방법은 두 개의 카운터 값을 비교하고,이 슬로우 ConcurrentModificationException
가 다르면.페일 세이프 반복기의 구현은 일반적으로 경량입니다. 이들은 일반적으로 특정 목록 구현 데이터 구조의 속성에 의존합니다. 일반적인 패턴은 없습니다.
private static final Singleton _instance;
있습니다.
volatile
스레드를 중지하는 데 매우 유용합니다.
자신의 스레드를 작성하는 것이 아니라 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
}
}
위의 코드 세그먼트 close
에서 while 루프 의 스레드 읽기 는를 호출 하는 스레드 와 다릅니다 close()
. 휘발성이 없으면 루프를 실행하는 스레드가 변경 사항을 닫을 수 없습니다.
동기화가 필요없는 방법에 주목
volatile
키워드가 없는 코드를 사용하고 다른 코드를 사용하는 것을 알고 있기 때문에 궁금 합니다. 항상 잘 작동하는 것 같습니다.
volatile
키워드로 선언 된 변수 에는 두 가지 주요 특성이있어 특별합니다.
휘발성 변수가 있으면 스레드가 컴퓨터의 (마이크로 프로세서) 캐시 메모리에 캐시 할 수 없습니다. 액세스는 항상 주 메모리에서 발생했습니다.
휘발성 변수에 대한 쓰기 작업 이 있고 갑자기 읽기 작업 이 요청되면 읽기 작업 이전에 쓰기 작업이 완료됩니다 .
위의 두 가지 특성으로 인해
반면에
volatile
키워드가 'n'개의 판독기 스레드와 하나의 기록기 스레드 만 있는 공유 변수를 유지하는 이상적인 방법 임을 알 수 있습니다 . volatile
키워드를 추가하면 완료됩니다. 스레드 안전에 대한 다른 오버 헤드가 없습니다.반대로
우리 는volatile
키워드를 단독으로 사용할 수 없으며 , 액세스하는 작성자 스레드 가 둘 이상인 공유 변수를 만족시킬 수 없습니다 .
long 및 double 변수 유형에 대한 읽기 및 쓰기 작업 처리에 대해서는 아무도 언급하지 않았습니다. 읽기 및 쓰기는 volatile 키워드를 원자 연산으로 사용해야하는 long 및 double 변수 유형을 제외하고 참조 변수 및 대부분의 원시 변수에 대한 원자 연산입니다. @링크
내 생각에, volatile 키워드가 사용되는 스레드 중지 이외의 두 가지 중요한 시나리오는 다음과 같습니다.
다중 스레드 응용 프로그램을 개발하는 경우 '휘발성'키워드 또는 '동기화'및 기타 동시성 제어 도구 및 기술을 사용해야합니다. 이러한 응용 프로그램의 예로는 데스크톱 응용 프로그램이 있습니다.
응용 프로그램 서버 (Tomcat, JBoss AS, Glassfish 등)에 배포 할 응용 프로그램을 개발하는 경우 응용 프로그램 서버에서 이미 처리 한대로 동시성 제어를 직접 처리 할 필요는 없습니다. 실제로 Java EE 표준을 올바르게 기억하면 서블릿과 EJB에서 동시성 제어가 금지됩니다. 서블릿과 EJB는 처리가 필요없는 '인프라'계층의 일부이기 때문입니다. 싱글 톤 객체를 구현하는 경우 해당 앱에서만 동시성 제어를 수행합니다. Spring과 같은 프레임 워크를 사용하여 구성 요소를 짜는 경우에도 이미 해결되었습니다.
따라서 애플리케이션이 웹 애플리케이션이고 Spring 또는 EJB와 같은 IoC 프레임 워크를 사용하는 대부분의 Java 개발의 경우 '휘발성'을 사용할 필요가 없습니다.
volatile
모든 스레드가 자체적으로 증가하고 있음을 보장합니다. 예를 들어, 카운터는 변수의 동일한면을 동시에 봅니다. 동기화 또는 원자 또는 기타 항목 대신 사용되지 않으며 읽기를 완전히 동기화합니다. 다른 Java 키워드와 비교하지 마십시오. 아래 예제에서 볼 수 있듯이 휘발성 변수 연산도 원 자성이므로 한 번에 실패하거나 성공합니다.
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);
}
}
}
예, 꽤 많이 사용합니다. 멀티 스레드 코드에 매우 유용 할 수 있습니다. 당신이 지적한 기사는 좋은 것입니다. 명심해야 할 두 가지 중요한 사항이 있지만
휘발성 키워드에는 두 가지 용도가 있습니다.
JVM이 레지스터에서 값을 읽지 못하게하고 메모리에서 값을 읽습니다.
비지 플래그 장치가 비지하고 플래그가 로크에 의해 보호되지 않은 상태에서 계속 스레드를 방지하기 위해 사용된다 :
while (busy) {
/* do something else */
}
다른 스레드가 사용 중 플래그를 끄면 테스트 스레드가 계속됩니다 .
busy = 0;
그러나 busy는 테스트 스레드에서 자주 액세스되므로 JVM은 busy 값을 레지스터에 배치하여 테스트를 최적화 한 다음 모든 테스트 전에 메모리의 busy 값을 읽지 않고 레지스터의 내용을 테스트 할 수 있습니다. 테스트 스레드는 사용중 변경을 볼 수 없으며 다른 스레드는 메모리 사용중의 값만 변경하여 교착 상태가 발생합니다. busy 플래그 를 volatile로 선언하면 각 테스트 전에 값을 읽습니다.
메모리 일관성 오류의 위험을 줄입니다.
휘발성 변수를 사용하면 휘발성 변수에 대한 쓰기 가 동일한 변수의 후속 읽기와 "일어남" 관계를 설정하기 때문에 메모리 일관성 오류 의 위험이 줄어 듭니다 . 이는 휘발성 변수에 대한 변경 사항이 항상 다른 스레드에 표시됨을 의미합니다.
메모리 일관성 오류없이 읽고 쓰는 기술을 원자 동작 이라고 합니다 .
원자 행동은 한 번에 효과적으로 발생하는 행동입니다. 원자 적 행동은 중간에 멈출 수 없다 : 그것은 완전히 일어나거나 전혀 일어나지 않는다. 조치가 완료 될 때까지 원자 조치의 부작용은 표시되지 않습니다.
다음은 원자적인 것으로 지정할 수있는 조치입니다.
건배!
volatile
프로그래머에게는 값이 항상 최신이라고 말합니다. 문제는 다른 유형의 하드웨어 메모리에 값을 저장할 수 있다는 것입니다. 예를 들어 CPU 레지스터, CPU 캐시, RAM이 될 수 있습니다. СPU 레지스터 및 CPU 캐시는 CPU에 속하며 멀티 스레딩 환경에서 복구중인 RAM과 달리 데이터를 공유 할 수 없습니다.
volatile
키워드는 변수가 RAM 메모리에서 직접 RAM 메모리 로 읽히고 쓰여질 것이라고 말합니다 . 계산 공간이 약간 있습니다
Java 5
[정보]volatile
를 지원하여 확장happens-before
휘발성 필드에 대한 쓰기는 해당 필드를 읽을 때마다 발생합니다.
volatile
여러 스레드가 동시에 일부 값을 쓸 수 있는 경우 키워드 가race condition
상황을 치료하지 않습니다 . 대답은 키워드입니다 [정보]synchronized
결과적으로 하나의 스레드가 쓰이고 다른 스레드 가 volatile
값을 읽을 때만 안전 합니다.
휘발성은 다음과 같습니다.
1> 다른 스레드에 의한 휘발성 변수 읽기 및 쓰기는 스레드 자체 캐시 또는 CPU 레지스터가 아닌 항상 메모리에서 가져옵니다. 따라서 각 스레드는 항상 최신 값을 처리합니다. 2> 두 개의 다른 스레드가 동일한 인스턴스 또는 정적 변수를 힙으로 사용하면 다른 작업이 순서대로 잘못 표시 될 수 있습니다. 이에 대한 제레미 맨슨의 블로그를 참조하십시오. 그러나 휘발성이 도움이됩니다.
다음 코드는 실행중인 키워드를 사용하지 않고 여러 스레드가 사전 정의 된 순서로 실행하고 출력을 인쇄하는 방법을 보여줍니다.
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
뿐만 아니라 변경 의 원인이 된 코드의 부작용도 확인합니다.
Peter Parker
답변에서 설명한 것처럼 volatile
수정자가 없으면 각 스레드의 스택에는 고유 한 변수 사본이있을 수 있습니다. 변수를로 설정 volatile
하면 메모리 일관성 문제가 해결되었습니다.
이해를 돕기 위해 jenkov 튜토리얼 페이지를 살펴보십시오 .
휘발성을 사용하는 휘발성 및 사용 사례에 대한 자세한 내용은 관련 SE 질문을 살펴보십시오.
실제 사용 사례 :
현재 시간을 특정 형식으로 인쇄해야하는 스레드가 많이 java.text.SimpleDateFormat("HH-mm-ss")
있습니다. Yon은 현재 시간을 변환 SimpleDateFormat
하고 1 초마다 변수를 업데이트하는 하나의 클래스를 가질 수 있습니다 . 다른 모든 스레드는이 휘발성 변수를 사용하여 현재 시간을 로그 파일로 인쇄 할 수 있습니다.
휘발성 변수는 기본적으로 업데이트되면 기본 공유 캐시 라인에서 즉시 업데이트 (플러시)에 사용되므로 변경 사항이 모든 작업자 스레드에 즉시 반영됩니다.
아래는 volatile
다른 스레드에서 스레드 실행을 제어하는 데 사용되는 변수 에 대한 요구 사항을 보여주는 매우 간단한 코드 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
volatile
스레드가volatile
변수를 읽을 때 다른 스레드가 마지막으로 쓴 값뿐만 아니라 다른 변수에 대한 다른 모든 쓰기도 볼 수 있다는 점에서 JSR 133에 정의 된 새로운 Java 메모리 모델과 함께 제공 되는 중요한 특성이 생략 되었습니다.volatile
쓰기 당시 다른 스레드에서 볼 수있었습니다 . 참조 이 답변 하고 이 참조를 .