자바 동기 화 자물쇠 잠 금
(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)우리 가 실현 한 자 물 쇠 는 공평 한 자물쇠 입 니까?아니면 불공평 한 자물쇠 입 니까?
답:불공평 한 자 물 쇠 는 자 물 쇠 를 얻 을 때 우리 가 먼저 시도 한 것 이다.여 기 는 엄격 한 줄 이 아니 기 때문에 불공평 한 자물쇠 이다.
이상 이 바로 본 고의 모든 내용 입 니 다.여러분 의 학습 에 도움 이 되 고 저 희 를 많이 응원 해 주 셨 으 면 좋 겠 습 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Is Eclipse IDE dying?In 2014 the Eclipse IDE is the leading development environment for Java with a market share of approximately 65%. but ac...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.