명령 재배치는 단일 스레드 환경에서 프로그램의 실행 효율성을 향상시키는 데 도움이 되며 다중 스레드 환경에서는 프로그램에 부정적인 영향을 미치지 않습니다. 명령 재정렬은 프로그램에 예상치 못한 오류를 가져옵니다. .
다음은 명령어 재배열을 100% 복구할 수 있는 예입니다.
public class D { static Integer a; static Boolean flag; public static void writer() { a = 1; flag = true; } public static void reader() { if (flag != null && flag) { System.out.println(a); a = 0; flag = false; } } }
reader
메서드는 flag
변수가 true인 경우에만 a
변수의 값을 콘솔에 출력합니다. . reader
方法仅在flag
变量为true时向控制台打印变量a
的值。
writer
方法先执行变量a
的赋值操作,后执行变量flag
的赋值操作。
如果按照上述分析逻辑,那么控制台打印的结果一定全为1。
假如代码未发生指令重排,那么当flag
变量为true时,变量a
一定为1。
上述代码中关于变量a
和变量flag
在两个方法类均存在指令重排的情况。
public static void writer() { a = 1; flag = true; }
通过观察日志输出,发现有大量的0输出。
当writer
方法内部发生指令重排时,flag
变量先完成赋值,此时假如当前线程发生中断,其它线程在调用reader
方法,检测到flag
变量为true,那么便打印变量a
的值。此时控制台存在超出期望值的结果。
使用关键字new创建对象时,因其非原子操作,故存在指令重排,指令重排在多线程环境下会带来负面影响。
public class Singleton { private static UserModel instance; public static UserModel getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new UserModel(2, "B"); } } } return instance; } } @Data @AllArgsConstructor class UserModel { private Integer userId; private String userName; }
使用关键字new创建一个对象,大致分为一下过程:
在栈空间创建引用地址
以类文件为模版在堆空间对象分配内存
成员变量初始化
使用构造函数初始化
将引用值赋值给左侧存储变量
针对上述示例,假设第一个线程进入synchronized代码块,并开始创建对象,由于重排序存在,正常的创建对象过程被打乱,可能会出现在栈空间创建引用地址后,将引用值赋值给左侧存储变量,随后因CPU调度时间片耗尽而产生中断的情况。
后续线程在检测到instance
writer
메소드는 먼저 a
변수의 할당 작업을 수행한 다음 flag
변수의 할당 작업을 수행합니다. 위의 분석 로직을 따르면 콘솔에서 출력되는 결과는 모두 1이어야 합니다. 2. 명령어 재배열코드에 명령어 재배열이 없으면 flag
변수가 true일 때 변수 a
는 1이어야 합니다. 위 코드에서는 a
변수와 flag
변수와 관련하여 두 메소드 클래스 모두에 명령 재배열이 있습니다. @Data @NoArgsConstructor @AllArgsConstructor public class ValueModel { private Integer value; private Boolean flag; }로그인 후 복사로그 출력을 관찰한 결과 0개의 출력이 많이 있는 것을 발견했습니다.
writer
메서드 내에서 명령을 재배치하면 flag
변수가 먼저 할당됩니다. 이때 현재 스레드가 중단되면 다른 스레드가 를 호출합니다. reader
메소드는 flag
변수가 true인지 감지한 다음 변수 a
의 값을 인쇄합니다. 이때 콘솔에서는 기대치를 초과하는 결과가 나오고 있습니다. (2) New는 객체를 생성합니다 new 키워드를 사용하여 객체를 생성하는 경우 비원자적 연산으로 인해 명령어 재배치가 발생하며 멀티 스레드 환경에서는 부정적인 영향을 미칩니다. public class E { private static final AtomicReference<ValueModel> ar = new AtomicReference<>(new ValueModel()); public static void writer() { ar.set(new ValueModel(1, true)); } public static void reader() { ValueModel valueModel = ar.get(); if (valueModel.getFlag() != null && valueModel.getFlag()) { System.out.println(valueModel.getValue()); ar.set(new ValueModel(0, false)); } } }
🎜🎜2. 🎜🎜위의 예에서는 첫 번째 스레드가 동기화된 코드 블록을 입력하고 객체 생성을 시작한다고 가정합니다. 재정렬의 존재로 인해 스택 공간에 참조 주소가 생성된 후 정상적인 객체 생성 프로세스가 중단될 수 있습니다. , 왼쪽 저장 변수에 기준값을 할당한 후 CPU 스케줄링 시간으로 인해 칩 소모로 인해 인터럽트가 발생합니다. 🎜🎜후속 스레드에서왼쪽 저장소 변수에 참조 값 할당
인스턴스
변수가 비어 있지 않음을 감지하면 해당 변수가 직접 사용됩니다. 싱글톤 개체는 인스턴스화되지 않기 때문에 직접 사용하면 예상치 못한 결과가 발생합니다. 🎜🎜3. 명령어 재배열 대처🎜🎜 (1) AtomicReference 원자 클래스🎜🎜Atomic 클래스를 사용하여 관련 변수 집합을 객체로 캡슐화하고 원자 연산의 특성을 활용하여 명령어 재배열 문제를 효과적으로 방지합니다. 🎜public class Singleton { private volatile static UserModel instance; public static UserModel getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new UserModel(2, "B"); } } } return instance; } } @Data @AllArgsConstructor class UserModel { private Integer userId; private String userName; }
위 내용은 멀티 스레드 환경에서 Java 명령 재정렬을 해결하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!