프로그램 개발 시 비용이 많이 드는 일부 객체 초기화 작업을 연기하고 이러한 객체가 사용될 때만 초기화해야 하는 경우가 있습니다. 이 경우 이중 확인 잠금을 사용하여 객체를 지연시킬 수 있습니다. 초기화 작업. 이중 확인 잠금(Double-check locking)은 동시 시스템에서 경쟁 및 동기화 오버헤드를 줄이기 위해 설계된 소프트웨어 설계 패턴으로, 일반적인 싱글턴 패턴을 기반으로 먼저 개체가 초기화되었는지 확인한 다음 잠글지 여부를 결정합니다. 이중 확인 잠금은 다중 스레드 환경에서 일반 싱글톤 패턴의 오류가 발생하기 쉽고 스레드가 안전하지 않은 문제를 해결하지만 여전히 숨겨진 위험이 있습니다. 다음은 Double-Check Locking 결함에 대한 원인 분석과 복구 방법을 JAVA 언어 소스코드를 예로 들어 설명한다.
이중 확인 잠금은 단일 스레드 환경에서는 아무런 영향을 미치지 않습니다. 스레드가 언제든지 실행을 전환하기 때문입니다. 명령어 재배열로 인해 개체 인스턴스화가 완료되지 않아 프로그램 호출 시 오류가 발생합니다.
샘플은 Java v1.3용 Samate Juliet Test Suite(https://samate.nist.gov/SARD/testsuite.php)에서 가져온 것입니다. 소스 파일 이름: CWE609_Double_Checked_Locking__Servlet_01.java.
위 코드 행은 23-38행입니다. 프로그램은 먼저 stringBad
가 null인지 여부를 결정합니다. 그렇지 않으면 String
객체를 직접 반환하여 synchronized 사용된 리소스를 차단합니다. <code class="prettyprint code-in-text Prettyprinted">stringBad
가 null인 경우 멀티 스레드에서 synchronized
키워드를 사용하세요. 환경 String
객체를 여러 번 생성하지 마세요. 실제로 코드를 실행하면 위의 코드에서 여전히 오류가 발생할 수 있습니다. stringBad
是否为 null,如果不是则直接返回该 String
对象,这样避免了进入 synchronized
块所需要花费的资源。当 stringBad
为 null 时,使用 synchronized
关键字在多线程环境中避免多次创建 String
对象。在代码实际运行时,以上代码仍然可能发生错误。
对于第33行,创建 stringBad
对象和赋值操作是分两步执行的。但 JVM 不保证这两个操作的先后顺序。当指令重排序后,JVM 会先赋值指向了内存地址,然后再初始化 stringBad
对象。如果此时存在两个线程,两个线程同时进入了第27行。线程1首先进入了 synchronized
块,由于 stringBad
为 null,所以它执行了第33行。当 JVM 对指令进行了重排序,JVM 先分配了实例的空白内存,并赋值给 stringBad
,但这时 stringBad
对象还未实例化,然后线程1离开了 synchronized
块。当线程2进入 synchronized
块时,由于 stringBad
此时不是 null ,直接返回了未被实例化的对象(仅有内存地址值,对象实际未初始化)。后续线程2调用程序对 stringBad
stringBad
객체 생성과 할당 작업이 두 단계로 수행됩니다. 그러나 JVM은 이 두 작업의 순서를 보장하지 않습니다. 명령이 재정렬되면 JVM은 먼저 메모리 주소를 가리키는 값을 할당한 다음 stringBad
개체를 초기화합니다. 이때 두 개의 스레드가 있으면 두 스레드가 동시에 27행에 진입합니다. 스레드 1은 먼저 synchronized
블록에 들어갔습니다. stringBad
가 null이기 때문입니다. 33번째 줄을 실행합니다. JVM이 명령어를 재정렬할 때 JVM은 먼저 인스턴스의 빈 메모리를 할당하고 이를 stringBad
에 할당하지만 이때는 stringBad
개체가 인스턴스화되지 않은 후 스레드 1이 동기화
블록을 떠났습니다. 스레드 2가 synchronized
블록에 들어갈 때 stringBad
가 null이 아니기 때문입니다. 이번에는 인스턴스화되지 않은 개체를 직접 반환합니다(메모리 주소 값만, 개체는 실제로 초기화되지 않음). 후속 스레드 2가 stringBad
개체에 대한 작업을 수행하기 위해 프로그램을 호출하면 이때 개체가 초기화되지 않았으므로 오류가 발생합니다. 360 코드 가드를 사용하여 위의 샘플 코드를 감지하면 "이중 확인 잠금" 결함을 감지할 수 있으며 표시 수준은 중간입니다. 그림 1과 같이 코드의 27번째 줄에서 결함을 보고합니다. 휘발성
키워드를 사용하여 stringBad를 수정하세요. 명령 키워드인 <code class="prettyprint code-in-text Prettyprinted">휘발성
은 컴파일러 최적화로 인해 명령이 생략되지 않도록 보장하고 매번 값을 직접 읽어야 합니다. volatile
关键字来对单例变量 stringBad
进行修饰。 volatile
作为指令关键字确保指令不会因编译器的优化而省略,且要求每次直接读值。
volatile
关键字就可以从语义上解决这个问题,值得关注的是 volatile
的禁止指令重排序优化功能在 Java 1.5 后才得以实现,因此1.5 前的版本仍然是不安全的,即使使用了 volatile
휘발성
키워드는 의미론적으로 이 문제를 해결할 수 있습니다. 주의할 점은 휘발성의 금지된 명령 재정렬 최적화 기능은 Java 1.5 이후에 구현되었으므로 1.5 이전 버전은 <code class="prettyprint code-in-text Prettyprinted">휘발성
키워드인 경우에도 여전히 안전하지 않습니다. 360 코드가드를 이용하여 수리된 코드를 검출해 보면 '이중체크락' 불량이 더 이상 존재하지 않는 것을 확인할 수 있습니다. 그림 2에 표시된 대로: (1) 휘발성 키워드를 사용하면 명령 재정렬을 피할 수 있지만 이 솔루션에는 JDK5 이상이 필요합니다. 왜냐하면 JDK5에서 시작하면 휘발성의 의미를 향상시키는 새로운 JSR-133 메모리 모델 사양을 사용하기 때문입니다. (2) 클래스 초기화를 기반으로 한 솔루션입니다.
JVM은 클래스 초기화 단계 동안(즉, 클래스가 로드된 후 스레드에서 사용되기 전) 클래스 초기화를 수행합니다. 실행 클래스를 초기화하는 동안 JVM은 잠금을 획득합니다. 이 잠금은 여러 스레드에 의해 동일한 클래스의 초기화를 동기화할 수 있습니다.
위 내용은 JAVA 언어로 이중 확인 잠금을 분석하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!