휘발성은 Java에서 상대적으로 중요한 키워드로, 주로 다른 스레드에서 액세스하고 수정할 변수를 수정하는 데 사용됩니다.
그리고 이 변수는 단 두 가지 특성만을 보장할 수 있습니다. 하나는 질서를 보장하는 것이고, 다른 하나는 가시성을 보장하는 것입니다.
그렇다면 질서란 무엇이고 가시성은 무엇입니까?
질서란 무엇입니까?
실제로 프로그램 실행 순서는 코드 순서에 따라 결정되며 명령어 재정렬은 금지되어 있습니다.
자연스러워 보이지만 그렇지 않습니다. 명령어 재정렬은 단일 스레드 프로그램의 실행 결과에 영향을 주지 않으면서 명령어를 최적화하고, 프로그램 실행 효율성을 향상시키며, 병렬성을 최대한 높이기 위한 것입니다.
그러나 多线程
환경에서는 일부 코드의 순서가 바뀌어 논리적 오류가 발생할 수 있습니다.
그리고 휘발성은 이 기능 때문에 잘 알려져 있습니다.
휘발성은 어떻게 질서를 보장하나요?
많은 친구들이 인터넷에서 말하는 바에 따르면 휘발성은 명령 재정렬을 금지하여 코드 프로그램이 코드 순서대로 엄격하게 실행되도록 보장할 수 있다고 합니다. 이는 질서를 보장합니다. 휘발성으로 수정된 변수에 대한 연산은 엄격하게 코드 순서대로 실행됩니다. 즉, 휘발성으로 수정된 변수에 대해 코드가 실행되면 그 앞의 코드가 실행되고 뒤의 코드가 실행되어야 합니다. 처형되지 않습니다.
이때 면접관이 계속해서 더 깊이 파고들지 않는다면 축하합니다. 이 질문에 대한 답이 나왔을지도 모르지만, 면접관이 계속해서 더 깊이 파고들면 명령 재배열이 왜 금지되나요?指 소스 코드부터 명령어까지의 명령어 실행은 일반적으로 그림과 같이 세 가지 재배열 유형으로 구분됩니다.
Volatile이 명령어 재배열을 어떻게 금지하는지 살펴봐야 합니다.
우리는 코드를 직접 사용하여 다음을 확인합니다.
public class ReSortDemo { int a = 0; boolean flag = false; public void mehtod1(){ a = 1; flag = true; } public void method2(){ if(flag){ a = a +1; System.out.println("最后的值: "+a); } } }누군가 이 코드를 본다면 분명히 '이 코드의 결과는 무엇일까?'라고 말할 것입니다. 어떤 사람들은 2라고 합니다. 네, 단일 스레드에서만 호출하면 결과가 2가 되지만, 여러 스레드에서 호출하면 최종 출력 결과가 우리가 상상한 2가 아닐 수도 있습니다. 이제 변경해야 합니다. 두 변수 모두 휘발성으로 설정되어 있습니다. 싱글턴 모드에 대해 더 많이 알고 계시다면 이 휘발성에 주목하셨을 것입니다.
다음 코드를 살펴보겠습니다.
class Singleton { // 不是一个原子性操作 //private static Singleton instance; //改进,Volatile 可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生! private static volatile Singleton instance; // 构造器私有化 private Singleton() { } // 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题,同时保证了效率, 推荐使用 public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }위의 싱글톤 패턴에 익숙하십니까?
예, 이것은 **이중 확인(DCL 게으른 스타일)입니다**
어떤 사람들은 명령어 재정렬이 있기 때문에 이중 종료 검색 메커니즘이 반드시 스레드로부터 안전하지는 않다고 말할 것입니다. 그렇습니다. 동기화된 키워드는 스레드로부터 안전하도록 만드는 데 사용됩니다. Visibility사실 가시성은 공유 변수에 대한 수정 사항이 멀티 스레드 환경의 다른 스레드에 즉시 표시되는지 여부의 문제입니다. 그럼 그의 가시성은 일반적으로 어디에 나타납니까? 어디에 사용되나요? 사실 이 변수는 일반적으로 가시성을 보장하기 위해 사용됩니다. 예를 들어 전역 변수가 정의되고 이 변수의 값을 판단하는 루프가 있습니다. 중지되고 다음 실행으로 이동합니다.휘발성 수정을 사용하지 않고 코드 구현을 살펴보겠습니다.
public class Test { private static boolean flag = false; public static void main(String[] args) throws Exception{ new Thread(new Runnable() { @Override public void run() { System.out.println("线程A开始执行:"); for (;;){ if (flag){ System.out.println("跳出循环"); break; } } } }).start(); Thread.sleep(100); new Thread(new Runnable() { @Override public void run() { System.out.println("线程B开始执行"); flag = true; System.out.println("标识已经变更"); } }).start(); } }결과는 상상할 수 있는 대로입니다.
실행 결과는 다음과 같아야 합니다.
스레드 A가 실행을 시작합니다.
스레드 B가 시작됩니다. 실행중로고가 바뀌었습니다
그렇죠.
휘발성을 사용하면 이 코드의 실행 결과가 달라지나요?
해보자:
public class Test { private static volatile boolean flag = false; public static void main(String[] args) throws Exception{ new Thread(new Runnable() { @Override public void run() { System.out.println("线程A开始执行:"); for (;;){ if (flag){ System.out.println("跳出循环"); break; } } } }).start(); Thread.sleep(100); new Thread(new Runnable() { @Override public void run() { System.out.println("线程B开始执行"); flag = true; System.out.println("标识已经变更"); } }).start(); }이렇게 하면 또 다른 실행 결과를 볼 수 있고 루프의 출력 문을 실행할 수 있습니다.
즉, 스레드 B에서는 이 수정된 변수를 수정하고, 마지막으로 스레드 A에서는 우리의 데이터 정보를 원활하게 읽을 수 있습니다.
원자성이 보장될 수 있나요? 아니, 휘발성으로 수정되는 일부 코드, 변수를 살펴보겠습니다.public class Test { // volatile不保证原子性 // 原子性:保证数据一致性、完整性 volatile int number = 0; public void addPlusPlus() { number++; } public static void main(String[] args) { Test volatileAtomDemo = new Test(); for (int j = 0; j < 20; j++) { new Thread(() -> { for (int i = 0; i < 1000; i++) { volatileAtomDemo.addPlusPlus(); } }, String.valueOf(j)).start(); }// 后台默认两个线程:一个是main线程,一个是gc线程 while (Thread.activeCount() > 2) { Thread.yield(); } // 如果volatile保证原子性的话,最终的结果应该是20000 // 但是每次程序执行结果都不等于20000 System.out.println(Thread.currentThread().getName() + " final number result = " + volatileAtomDemo.number); } }
메인 최종 숫자 결과 = 17114메인 최종 숫자 결과 = 20000메인 최종 숫자 결과 = 19317
3번의 실행, 모두 다른 결과,
왜 이런 일이 발생하나요? 이것은 숫자++와 관련이 있습니다number++는 3개의 명령어로 나뉩니다
GETFIELD를 실행하여 주 메모리의 원래 값 번호를 가져옵니다.
IADD를 실행하여 1개의 연산을 추가합니다.
PUTFIELD를 실행하여 작업 메모리의 값을 다시 메인 메모리中
여러 스레드가 PUTFIELD 명령어를 동시에 실행하면 후기입 메인 메모리 덮어쓰기 문제가 발생하므로 최종 결과는 20000이 아니므로 휘발성은 원자성을 보장할 수 없습니다.
위 내용은 Java의 Volatile 키워드는 스레드 안전성을 보장합니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!