자바 동기 화 자물쇠 잠 금

문제.
(1)스스로 자 물 쇠 를 쓰 려 면 어떤 지식 이 필요 합 니까?
(2)스스로 자 물 쇠 를 쓰 는 것 이 얼마나 간단 합 니까?
(3)자신 이 완벽 한 자 물 쇠 를 쓸 수 있 습 니까?
간단 한 소개
이 글 의 목 표 는 첫째,스스로 자 물 쇠 를 쓰 는 것 이다.이 자물쇠 의 기능 은 매우 간단 해서 정상 적 인 자 물 쇠 를 추가 하고 자 물 쇠 를 풀 수 있다.
이 글 의 목 표 는 두 번 째 로 스스로 자 물 쇠 를 써 서 뒷부분 에서 배 울 AQS 와 각종 동기 화가 실현 되 는 원 리 를 잘 이해 하 는 것 이다.
분석 하 다.
자 물 쇠 를 직접 쓰 려 면 무엇 을 준비 해 야 합 니까?
우선,지난 장 에서 synchronized 를 배 울 때 우 리 는 그 실현 원 리 는 대상 의 머리 에 있 는 Markword 를 바 꾸 는 것 이 라 고 말 했 습 니 다.자 물 쇠 를 추가 하거나 자 물 쇠 를 추가 하지 않 은 것 으로 표 시 됩 니 다.
그러나 우리 자신 은 대상 헤드 정 보 를 수정 할 수 없습니다.그러면 우 리 는 하나의 변수 로 대체 할 수 있 습 니까?
예 를 들 어 이 변수의 값 이 1 일 때 자 물 쇠 를 추가 했다 는 것 을 설명 하고 변수 값 이 0 일 때 자 물 쇠 를 추가 하지 않 았 다 는 것 을 설명 합 니 다.저 는 가능 하 다 고 생각 합 니 다.
그 다음 에 우 리 는 여러 개의 스 레 드 가 위 에서 우리 가 정의 한 변수 에 대한 경쟁 용 도 를 제어 할 수 있 도록 해 야 한다.이른바 제어 할 수 있 는 스 레 드 는 한 개의 스 레 드 만 이 그 값 을 1 로 수정 할 수 있 고 그 값 이 1 일 때 다른 스 레 드 는 그 값 을 다시 수정 할 수 없다.이런 것 이 전형 적 인 CAS 작업 이 아니 냐 는 것 이다.그래서 우 리 는 Unsafe 라 는 종 류 를 사용 하여 CAS 작업 을 해 야 한다.
그 다음 에 우 리 는 다 중 스 레 드 환경 에서 여러 스 레 드 가 같은 자물쇠 에 대한 경쟁 은 반드시 하나 만 성공 할 수 있다 는 것 을 알 고 있다.그러면 다른 스 레 드 는 줄 을 서 야 하기 때문에 우 리 는 하나의 대열 이 필요 하 다.
마지막 으로 이 스 레 드 들 은 줄 을 설 때 무엇 을 합 니까?그들 은 더 이상 자신의 프로그램 을 실행 할 수 없습니다.그러면 막 을 수 밖 에 없습니다.이 스 레 드 가 돌 아 왔 을 때 깨 워 야 합 니 다.그래서 우 리 는 Unsfae 와 같은 종류의 스 레 드 를 막 고 깨 워 야 합 니 다.
상기 네 가 지 를 바탕 으로 우리 가 필요 로 하 는 신 기 는 대체적으로 하나의 변수,하나의 대기 열,CAS/park/unpark 를 실행 하 는 Unsafe 류 가 있 습 니 다.
대략적인 흐름 도 는 다음 그림 과 같다.

Unsafe 류 에 대한 설명 은 앞서 보 낸 글 을 참고 하 시기 바 랍 니 다.
자바 Unsafe 상세 분석
해결 하 다.
변수
이 변 수 는 하나의 스 레 드 만 지원 하기 때문에 다른 스 레 드 를 볼 수 있 도록 수정 되 었 습 니 다.따라서 이 변 수 는 volatile 로 수정 해 야 합 니 다.

private volatile int state;
CAS
이 변 수 는 원자 조작 이 어야 하기 때문에 CAS 업데이트 가 필요 합 니 다.여기 서 는 Unsafe 를 사용 하여 int 형식의 state 를 직접 CAS 로 업데이트 합 니 다.
물론 이 변 수 는 AtomicInteger 를 직접 사용 해도 됩 니 다.하지만 더 밑바닥 의 Unsafe 류 를 배 웠 으 니(파도)를 사용 해 야 합 니 다.

private boolean compareAndSetState(int expect, int update) {
 return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
한 대열
대열 의 실현 은 매우 많 고 배열,링크 가 모두 가능 하 다.우 리 는 여기 서 링크 를 사용한다.왜냐하면 링크 는 대열 이 상대 적 으로 간단 하기 때문에 확대 등 문 제 를 고려 할 필요 가 없다.
이 대기 열의 조작 은 매우 특징 이 있다.
원 소 를 넣 을 때 는 모두 꼬리 부분 에 놓 고 여러 스 레 드 를 함께 놓 을 수 있 으 므 로 꼬리 부분 에 대한 작업 은 CAS 로 업데이트 해 야 합 니 다.
하나의 요 소 를 깨 울 때 는 머리 부터 시작 하지만 동시에 하나의 스 레 드 만 작 동 합 니 다.즉,잠 긴 스 레 드 를 얻 었 기 때문에 머리 에 대한 작업 은 CAS 로 업데이트 할 필요 가 없습니다.

private static class Node {
 //         
 Thread thread;
 //      (    ,        )
 Node prev;
 //      
 Node next;

 public Node() {
 }

 public Node(Thread thread, Node prev) {
 this.thread = thread;
 this.prev = prev;
 }
}
//    
private volatile Node head;
//    
private volatile Node tail;
//     tail  
private boolean compareAndSetTail(Node expect, Node update) {
 return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
이 대기 열 은 매우 간단 합 니 다.저 장 된 요 소 는 스 레 드 입 니 다.다음 깨 워 야 할 노드 가 있어 야 합 니 다.앞의 노드 는 있어 도 되 고 없어 도 되 지만 실현 하기 가 어렵 습 니 다.이 글 을 다 배 우 는 것 을 믿 지 않 으 면 해 보 세 요.
자 물 쇠 를 채우다

public void lock() {
 //     state  ,          
 if (compareAndSetState(0, 1)) {
 return;
 }
 //         
 Node node = enqueue();
 Node prev = node.prev;
 //        ,            head,       
 while (node.prev != head || !compareAndSetState(0, 1)) {
 //      ,  
 unsafe.park(false, 0L);
 }
 //          ,               
 //             head
 // head    
 head = node;
 //          ,  GC
 node.thread = null;
 //             ,  GC
 node.prev = null;
 prev.next = null;
}
//   
private Node enqueue() {
 while (true) {
 //      
 Node t = tail;
 //      
 Node node = new Node(Thread.currentThread(), t);
 //            
 if (compareAndSetTail(t, node)) {
 //         ,      next        
 t.next = node;
 return node;
 }
 }
}
(1)자 물 쇠 를 가 져 오 려 고 시도 하고 성공 하면 바로 돌아 갑 니 다.
(2)자 물 쇠 를 가 져 오지 않 고 대기 열 에 들 어가 줄 을 서 십시오.
(3)입대 후 다시 자 물 쇠 를 가 져 오기;
(4)성공 하지 못 하면 막힌다.
(5)성공 하면 머리 노드 를 한 자리 뒤로 옮 기 고 현재 노드 의 내용 을 비우 고 이전 노드 와 관 계 를 끊 는 다.
(6)잠 금 추가 종료;
자 물 쇠 를 풀다

//   
public void unlock() {
 //  state   0,         ,               
 state = 0;
 //          
 Node next = head.next;
 //         ,    
 if (next != null) {
 unsafe.unpark(next.thread);
 }
}
(1)state 를 0 으로 바 꾸 었 습 니 다.여 기 는 CAS 업데이트 가 필요 없습니다.아직 잠 금 을 추가 하고 있 기 때문에 하나의 스 레 드 만 업데이트 하고 이 말 후에 자 물 쇠 를 풀 었 습 니 다.
(2)다음 노드 가 있 으 면 깨 워 라.
(3)깨 우 면 위 에 있 는 lock()방법의 while 순환 을 통 해 자 물 쇠 를 가 져 옵 니 다.
(4)깨 운 스 레 드 는 100%자 물 쇠 를 가 져 올 수 있 는 것 이 아 닙 니 다.state 가 0 으로 업데이트 되 었 을 때 자 물 쇠 를 풀 었 기 때문에 나중에 스 레 드 가 자 물 쇠 를 추가 하려 고 시도 할 수 있 습 니 다.
테스트
위의 완전한 자물쇠 의 실현 은 끝 이 야.간단 하지 않 아?하지만 정말 믿 을 만 한 거 아니 야?감히 해 볼 수 없 지?!
직접 테스트 코드 올 리 기:

private static int count = 0;

public static void main(String[] args) throws InterruptedException {
 MyLock lock = new MyLock();

 CountDownLatch countDownLatch = new CountDownLatch(1000);

 IntStream.range(0, 1000).forEach(i -> new Thread(() -> {
 lock.lock();

 try {
 IntStream.range(0, 10000).forEach(j -> {
 count++;
 });
 } finally {
 lock.unlock();
 }
// System.out.println(Thread.currentThread().getName());
 countDownLatch.countDown();
 }, "tt-" + i).start());

 countDownLatch.await();

 System.out.println(count);
}
이 코드 를 실행 한 결 과 는 항상 10000000(천만)을 인쇄 한 것 으로 우리 의 자물쇠 가 정확 하고 믿 을 수 있 으 며 완벽 하 다 는 것 을 의미한다.
총결산
(1)스스로 자 물 쇠 를 쓰 려 면 준 비 를 해 야 합 니 다.하나의 변수,하나의 대기 열,Unsafe 류.
(2)원자 업데이트 변 수 는 1 로 잠 금 성공 을 설명 합 니 다.
(3)원자 업데이트 변 수 는 1 실패 로 잠 금 획득 에 실 패 했 음 을 설명 하고 대기 열 에 들 어가 줄 을 서 십시오.
(4)대기 열 끝 노드 를 업데이트 할 때 다 중 스 레 드 경쟁 이 므 로 원자 업 데 이 트 를 사용 해 야 합 니 다.
(5)대기 열 헤드 노드 를 업데이트 할 때 하나의 라인 만 있 고 경쟁 이 존재 하지 않 기 때문에 원자 업 데 이 트 를 사용 할 필요 가 없다.
(6)대기 열 노드 의 이전 노드 prev 의 사용 은 매우 교묘 합 니 다.그것 이 없 으 면 하나의 자 물 쇠 를 실현 하기 어렵 습 니 다.쓴 사람 만 이 알 고 있 습 니 다.믿 지 않 으 면 해 보 세 요^^
달걀
(1)우리 가 실현 한 자물쇠 지원 은 다시 들 어 갈 수 있 습 니까?
답:다시 들 어 갈 수 없습니다.왜냐하면 우 리 는 매번 state 를 1 로 업데이트 하기 때 문 입 니 다.다시 들 어 갈 수 있 도록 지원 하 는 것 도 간단 합 니 다.자 물 쇠 를 가 져 올 때 잠 금 이 현재 스 레 드 에 의 해 점유 되 어 있 는 지 확인 합 니 다.만약 그렇다면 state 의 값 을 1 로 추가 하고 자 물 쇠 를 풀 때 1 을 줄 이면 됩 니 다.0 으로 줄 일 때 잠 금 이 풀 렸 음 을 표시 합 니 다.
(2)우리 가 실현 한 자 물 쇠 는 공평 한 자물쇠 입 니까?아니면 불공평 한 자물쇠 입 니까?
답:불공평 한 자 물 쇠 는 자 물 쇠 를 얻 을 때 우리 가 먼저 시도 한 것 이다.여 기 는 엄격 한 줄 이 아니 기 때문에 불공평 한 자물쇠 이다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.

좋은 웹페이지 즐겨찾기