문제를 설명하기 전에 짧은 이야기를 살펴보겠습니다.
북송 왕조의 진종 황후가 죽은 후, 그의 두 당시 사랑받았던 후궁 유후궁과 리후궁은 모두 임신을 한 상태였다. 누구든 아들을 낳으면 정궁이 될 수 있음은 자명하다. 유후궁(劉孫)은 이후후가 아들을 낳고 왕비가 될까봐 오랫동안 질투하여 궁궐 관리인 두당(郡堂) 곽회(郭淮)와 조산사 이씨 유시(吉師)의 협력을 받아 계획을 세웠다. 출산 중 혼수상태로 사망했는데, 뜻밖에도 사향고양이의 털이 벗겨져 피가 흐르고 윤기가 나며 새로 태어난 왕자를 빼앗아갔다. 유후궁은 궁녀 구주에게 태자를 목졸라 죽이라고 명령했다. 구주는 참지 못하고 몰래 태자를 내시 진린에게 넘겨주었다. 진림은 태자를 여행가방에 담아 팔현왕에게 보냈다. 그를 키우기 위해. 게다가 진종은 가죽이 벗겨진 사향고양이를 보고 리후궁이 괴물을 낳았다고 생각하여 그녀를 차가운 궁궐로 강등시켰다. 곧 유후궁이 진통을 당해 아들을 낳았고, 유후후도 왕비로 임명되었습니다. 뜻밖에도 6년 후, 유왕후의 아들이 병으로 사망했습니다. Zhenzong은 더 이상 상속자가 없었기 때문에 형 Baxianwang (실제로는 그해에 교체 된 왕자)의 아들을 양자로 입양하여 왕세자로 세웠습니다.
이 이야기를 보면 왕자가 태어났을 때 사향고양이로 바뀌었다가, 이상한 상황의 조합으로 인해 마침내 왕자가 되기 위해 돌아왔다는 것을 알 수 있습니다. 결과는 같아도 그 과정은 우여곡절이 많고, 왕자는 정말 불행한 운명을 맞이하게 된다.
이 이야기를 하는 이유는 무엇인가요? 사실 오늘 소개할 문제와도 많은 관련이 있습니다. 같은 결과로 중간에 몇번의 연산이 일어났는지 모르니 변하지 않았다고 볼 수 있을까요? 다양한 비즈니스 시나리오에서 우리는 이 문제를 신중하게 고려해야 합니다.
멀티 스레드 시나리오CAS
会出现ABA
문제에서 ABA 문제에 대한 간단한 과학은 다음과 같습니다. 예를 들어 동일한 값에 대해 CAS 작업을 수행하는 두 개의 스레드가 있습니다. A) 동시에 이 세 가지 스레드는 다음과 같습니다.
스레드 1이 CPU 타임 슬라이스를 가져옵니다. 먼저 스레드 2가 다른 이유로 CPU 타임 슬라이스를 먼저 가져오는 동안 스레드 1의 값이 A의 예상 값과 비교되어 동일한 것으로 확인된 다음 값이 B로 업데이트됩니다. 이때, 스레드 3이 나타나고, 예상값은 B, 업데이트할 값은 A이며, 스레드 3의 값은 예상값과 동일합니다. B값을 비교하여 같다면 업데이트합니다. 이때 스레드 2는 블로킹에서 복구되어 CPU 타임 슬라이스를 획득합니다. 이때 스레드 2의 값을 예상 값 A와 비교하여 동일한 것으로 확인되면 값을 업데이트합니다. B.에게 스레드 2도 작업을 완료했지만 스레드 2는 값이 A->B->A의 변경 과정을 거쳤다는 사실을 알지 못합니다.
샤오밍이 현금인출기에서 50위안을 인출했는데 현금인출기 문제로 실이 2개 있었는데 동시에 잔액이 100에서 50으로 바뀌었습니다. 시간:
100(100-50+50)
,但是实际上变为了50(100-50+50-50)
AtomicStampedReference는 내부적으로 객체 값과 버전 번호를 유지 관리합니다. 초기값을 전달하고 초기 버전 번호
AtomicStampedReference가 개체 값을 설정할 때 쓰기가 성공하려면 개체 값과 상태 스탬프가 모두 예상 값을 충족해야 합니다.
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100,1); public static void main(String[] args) { //第一个线程 new Thread(() -> { System.out.println("t1拿到的初始版本号:" + atomicStampedReference.getStamp()); //睡眠1秒,是为了让t2线程也拿到同样的初始版本号 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100, 101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); atomicStampedReference.compareAndSet(101, 100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); },"t1").start(); // 第二个线程 new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println("t2拿到的初始版本号:" + stamp); //睡眠3秒,是为了让t1线程完成ABA操作 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("最新版本号:" + atomicStampedReference.getStamp()); System.out.println(atomicStampedReference.compareAndSet(100, 2019,stamp,atomicStampedReference.getStamp() + 1) + "\t当前值:" + atomicStampedReference.getReference()); },"t2").start(); }
1. 초기 값 100, 초기 버전 번호 1
2. 스레드 t1과 t2는 동일한 초기 버전 번호를 얻습니다
3. 스레드 t1은 ABA 작업을 완료하고 버전 번호는 3으로 증가합니다. .Thread t2 CAS 작업이 완료되었습니다. 이전에 스레드 t2에서 얻은 버전 번호 1과 동일하지 않습니다.
실행 결과:
t1拿到的初始版本号:1 t2拿到的初始版本号:1 最新版本号:3 false 当前值:100
private static AtomicMarkableReference<Integer> atomicMarkableReference = new AtomicMarkableReference<Integer>(100,false); public static void main(String[] args) { // 第一个线程 new Thread(() -> { System.out.println("t1版本号是否被更改:" + atomicMarkableReference.isMarked()); //睡眠1秒,是为了让t2线程也拿到同样的初始版本号 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicMarkableReference.compareAndSet(100, 101,atomicMarkableReference.isMarked(),true); atomicMarkableReference.compareAndSet(101, 100,atomicMarkableReference.isMarked(),true); },"t1").start(); // 第二个线程 new Thread(() -> { boolean isMarked = atomicMarkableReference.isMarked(); System.out.println("t2版本号是否被更改:" + isMarked); //睡眠3秒,是为了让t1线程完成ABA操作 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("是否更改过:" + atomicMarkableReference.isMarked()); System.out.println(atomicMarkableReference.compareAndSet(100, 2019,isMarked,true) + "\t当前值:" + atomicMarkableReference.getReference()); },"t2").start(); }
AtomicStampedReference
可以给引用加上版本号,追踪引用的整个变化过程,如:A -> B -> C -> D -> A,通过AtomicStampedReference,我们可以知道,引用变量中途被更改了3次。但是,有时候,我们并不关心引用变量更改了几次,只是单纯的关心是否更改过,所以就有了AtomicMarkableReference
1. 초기 값은 100이며 초기 버전 번호는 수정되지 않았습니다. false 2. 스레드 t1과 t2는 동일한 초기 버전 번호를 가지며 수정되지 않았습니다. false실행 결과. :3. , 버전 번호가 수정되었습니다.
4. 스레드 t2가 CAS 작업을 완료했습니다. 버전 번호는 이전에 스레드 t2에서 얻은 버전 번호 false와 동일하지 않습니다.
以上是本期关于CAS领域的一个经典ABA问题的解析,不知道你在实际的工作中有没有遇到过,但是在面试中这块是并发知识考查的重点。如果你还没接触过此类的问题,我的建议是你自己将上面的代码运行一下,结合理论去理解一下ABA问题所带来的问题以及如何解决他,这对你日后的开发工作也是有莫大的帮助的!t1版本号是否被更改:false
t2版本号是否被更改:false
是否更改过:true
false 当前值:100
多说几句
위 내용은 면접관이 질문합니다. ABA 문제가 무엇인지 아시나요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!