자바 동시 프로그래밍 원칙 3(CountDownLatch, Semaphore, CopyOnWriteArrayList, ConcurrentHashMap)

一、CountDownLatch,세마포어:

1.1 CountDownLatch란 무엇입니까? 무슨 소용이야? 최하층은 어떻게 실현되는가?

CountDownLatch의 본질은 실제로 카운터입니다.

다중 스레드 동시 처리 업무를 할 때 다른 스레드가 처리를 완료할 때까지 기다린 다음 병합과 같은 후속 작업을 수행해야 합니다.사용자에게 응답할 때 CountDownLatch를 사용하여 계산할 수 있습니다.다른 스레드가 나타난 후 메인 스레드는 깨어났다.

CountDownLatch 자체는 AQS를 기반으로 구현됩니다.

새 CountDownLatch인 경우 특정 값을 직접 지정하십시오. 이 값은 state 속성에 복사됩니다.

자식 스레드가 작업 처리를 마치면 countDown 메서드를 실행하고 내부 상태를 상태 - 1로 직접 부여합니다.

상태가 0이 되면 await에 의해 중단된 스레드가 깨어납니다.

CountDownLatch는 재사용할 수 없으며 사용 후 냉각됩니다.

1.2 세마포어란? 무슨 소용이야? 최하층은 어떻게 실현되는가?

세마포어는 전류 제한 기능에 사용할 수 있는 도구 클래스입니다.

예를 들어 Hystrix는 제한된 수의 동시 스레드가 필요한 세마포어 격리를 포함하므로 세마포어를 사용하여 구현할 수 있습니다.

예를 들어 현재 서비스에서 동시에 작동하는 데 최대 10개의 스레드가 필요한 경우 세마포어를 10으로 설정합니다. 어떤 작업 제출도 세마포어를 얻을 필요가 없으며 작업을 시작하고 완료되면 세마포어를 반환합니다.

세마포어 역시 AQS를 기반으로 구현된다.

세마포어 구성 시 세마포어 리소스 개수 지정 획득 시 세마포어 개수 지정 CAS는 원자성을 보장하며 리턴은 비슷하다.

1.3 메인 쓰레드가 끝나면 프로그램이 멈추나요?

기본 스레드가 종료되었지만 여전히 실행 중인 사용자 스레드가 있으면 종료되지 않습니다!

메인 스레드가 끝나면 나머지는 데몬 스레드, 종료!

둘, CopyOnWriteArrayList :

2.1 CopyOnWriteArrayList는 어떻게 스레드 안전을 보장합니까? 단점이 있습니까?

CopyOnWriteArrayList는 데이터를 쓸 때 ReentrantLock을 기반으로 원자성을 보장합니다.

둘째, 데이터를 쓸 때 복사본이 복사되어 쓰여지며, 쓰기가 성공하면 CopyOnWriteArrayList의 배열에 쓰여집니다.

데이터를 읽을 때 데이터 불일치가 없는지 확인하십시오.

데이터 양이 상대적으로 많으면 데이터를 쓸 때마다 복사본을 복사해야 하므로 너무 많은 공간을 차지합니다. 데이터 양이 비교적 많은 경우 CopyOnWriteArrayList를 사용하지 않는 것이 좋습니다.

원자성을 보장하기 위해 쓰기 작업이 필요하고, 읽기 작업은 동시성이 보장되며, 데이터의 양이 많지 않습니다~

三、ConcurrentHashMap(JDK1.8)

3.1 HashMap이 스레드로부터 안전하지 않은 이유는 무엇입니까?

질문 1: JDK1.7에 루프가 있습니다(확장 중).

문제 2: 데이터가 덮어쓰여지고 데이터가 손실될 수 있습니다.

질문 3: 둘째, 전통적인 ++인 카운터는 요소 수와 HashMap이 작성된 횟수를 기록할 때 부정확하게 기록합니다.

질문 4: 데이터 마이그레이션 및 용량 확장으로 인해 데이터가 손실될 수도 있습니다.

3.2 ConcurrentHashMap은 스레드 안전성을 어떻게 보장합니까?

1: 테일 플러그, 나사산 안전을 보장하기 위해 CAS로 확장

2: Array 쓰기 시에는 CAS를 기반으로 보안이 보장되며, Linked List에 삽입되거나 Red-Black Tree에 삽입 시에는 synchronized를 기반으로 보안이 보장된다.

3: 여기서 ConcurrentHashMap은 LongAdder에 의해 구현된 기술이며 맨 아래 계층은 여전히 ​​CAS입니다. (원자 길이)

4: ConcurrentHashMap이 확장될 때 데이터 마이그레이션에 동시성 문제가 없도록 한 지점은 CAS를 기반으로 합니다.두 번째로 ConcurrentHashMap은 동시 확장 작업도 제공합니다. 예를 들어 배열 길이가 64에서 128로 확장됩니다. 두 개의 스레드가 동시에 확장되면

스레드 A는 64-48 인덱스의 데이터 마이그레이션 작업을 받고 스레드 B는 47-32 인덱스 데이터 마이그레이션 작업을 받습니다. 핵심은 작업을 받을 때 CAS를 기반으로 스레드 안전성을 확보한다는 것입니다.

3.3 ConcurrentHashMap이 빌드된 후 배열이 생성됩니까? 그렇지 않다면 어레이 초기화의 스레드 안전성을 어떻게 보장할 수 있습니까?

ConcurrentHashMap은 지연 로딩 메커니즘이며 대부분의 프레임워크 구성 요소는 지연 로딩입니다~

초기화 쓰레드의 안전성을 보장하기 위해 CAS를 기반으로 하는데, 이는 CAS가 sizeCtl 변수를 수정하여 쓰레드 초기화 데이터의 원자성을 제어할 뿐만 아니라 DCL을 사용하는 것을 포함한다. 그리고 중간에 CAS를 기준으로 sizeCtl을 수정한다.배열 미초기화 판정을 한다.

이미지.png

3.4 로드 팩터가 0.75인 이유는 무엇이며, 길이가 8이 되면 연결된 목록이 레드-블랙 트리로 변하는 이유는 무엇입니까?

그리고 ConcurrentHashMap의 로드 팩터는 수정할 수 없습니다!

0.75의 부하 계수는 두 가지 방식으로 설명할 수 있습니다.

왜 0.5가 아니고 1이 아닌가?

0.5: 로드 팩터가 0.5인 경우 데이터의 절반을 더하면 확장되기 시작합니다.

  • 장점: 해시 충돌이 적고 쿼리 효율성이 높습니다.
  • 단점 : 증축이 너무 잦고 공간활용률이 낮다.

1: 로드 팩터가 1이면 어레이의 길이에 데이터를 추가하여 확장을 시작합니다.

  • 장점 : 용량증설이 적고 공간활용이 좋다.
  • 단점: 해시 충돌이 특히 자주 발생하고 데이터가 연결 목록에 걸려 쿼리 효율성에 영향을 미치며 연결 목록이 너무 길어서 레드-블랙 트리를 생성할 수 없어 쓰기 효율성에 영향을 미칩니다. .

0.75는 두 가지 측면을 모두 고려하여 중간 선택이라고 할 수 있습니다.

푸아송 분포에 대해 이야기해 보자면 부하율이 0.75일 때 푸아송 분포에 따르면 링크드 리스트의 길이가 8에 도달할 확률은 매우 낮으며 소스 코드의 로고는 0.00000006이고 레드 블랙 트리는 매우 낮습니다.

ConcurrentHashMap은 red-black tree를 도입하고 있지만, red-black tree는 쓰기에 대한 유지 비용이 높기 때문에 가능하면 사용하셔도 됩니다.

6이 링크드 리스트로 변질된 것은 트리가 7개의 값으로 가득 차 있기 때문인데, 7은 링크드 리스트와 레드-블랙 트리 간의 빈번한 변환을 방지하기 위해 변질되지 않았기 때문입니다. 빈번한 변환을 피하십시오.

Put 작업이 너무 빈번한 장면은 확장 기간 동안 Put 막힘을 유발합니까?

일반적으로 막히지 않습니다.

put 연산 중에 현재 인덱스 위치에 데이터가 없는 것으로 확인되면 데이터가 정상적으로 이전 배열로 드롭되기 때문입니다.

Put 작업 중 현재 위치 데이터가 새 어레이로 마이그레이션되어 현재 정상적으로 삽입되지 않는 것으로 확인되면 용량 확장을 돕고 확장 작업을 신속하게 종료하고 인덱스를 다시 선택합니다. 위치 쿼리

3.5 ConcurrentHashMap은 언제 확장되며 확장 프로세스는 무엇입니까?

  • ConcurrentHashMap의 요소 수가 로드 팩터 계산을 위한 임계값에 도달한 다음 용량을 직접 확장합니다.
  • putAll 메소드를 호출하여 대량의 데이터를 쿼리할 때 직접 확장 작업이 발생할 수 있으며, 삽입된 데이터가 다음 확장 임계값보다 크면 대량의 데이터를 직접 확장하여 삽입합니다.
  • 배열의 길이가 64보다 작고 연결 목록의 길이가 8보다 크거나 같으면 확장이 트리거됩니다.이미지.png

확장 프로세스: (sizeCtl은 초기화 및 확장을 제어하는 ​​데 사용되는 int 유형 변수입니다.)

  • 각 확장 스레드는 oldTable의 길이를 기반으로 확장 식별 스탬프를 계산해야 합니다(두 확장 스레드의 배열 길이 불일치를 방지하기 위해). 둘째, 확장 식별 스탬프의 16비트가 1인지 확인하여 왼쪽 시프트가 16비트의 결과는 음수가 됨)
  • 첫 번째 확장된 스레드는 sizeCtl + 2이며, 이는 현재 용량을 확장할 스레드가 1개 있음을 의미합니다.
  • 첫 번째 확장 쓰레드를 제외하고 다른 쓰레드들은 sizeCtl + 1 증가할 것이고, 이는 다른 쓰레드가 확장을 돕기 위해 왔다는 것을 의미합니다.
  • 첫 번째 스레드는 새 배열을 초기화합니다.
  • 각 스레드는 데이터 마이그레이션 작업을 수신하고 oldTable의 데이터를 newTable로 마이그레이션합니다. 기본적으로 각 스레드는 매번 길이가 16인 마이그레이션 데이터 태스크를 수신합니다.
  • 데이터 마이그레이션이 완료되면 각 쓰레드가 다시 작업을 클레임하러 갈 때 받을 작업이 없음을 확인하고 확장을 종료하고 sizeCtl - 1로 설정한다.
  • 확장을 종료한 마지막 스레드는 -1을 찾은 후 1이 남았고 확장을 종료한 마지막 스레드는 처음부터 끝까지 마이그레이션되지 않은 남은 데이터가 있는지 다시 확인합니다(이 상황 기본적으로 발생하지 않음), 체크 후 -1, sizeCtl이 차감되고 확장이 완료됩니다.

3.6 ConcurrentHashMap의 카운터는 어떻게 구현됩니까?

이는 LongAdder의 메커니즘을 기반으로 구현되나 LongAdder의 참조를 직접 사용하지 않고 LongAdder의 원리에 따라 유사도가 80% 이상인 코드를 작성하여 직접 사용한다.

LongAdder는 원자성을 보장하기 위해 추가하기 위해 CAS를 사용하고 두 번째로 동시성을 보장하기 위해 세그먼트 잠금을 기반으로 합니다.

3.7 ConcurrentHashMap의 읽기 작업이 차단됩니까?

어디를 체크해도 막히지 않습니다.

쿼리 배열? : 첫 번째 블록은 요소가 배열에 있는지 확인한 다음 직접 반환하는 것입니다.

쿼리 연결 목록? : 두 번째 블록은 특별한 상황이 없으면 연결 리스트에서 next, next를 쿼리하면 됩니다.

확장할 때? : 세 번째 블록에서 현재 인덱스 위치가 -1이면 현재 위치의 모든 데이터가 새 배열로 마이그레이션되었음을 의미하며, 확장 여부와 상관없이 새 배열로 바로 이동하여 쿼리할 수 있습니다. 완료 여부.

레드-블랙 트리 쿼리? : 쓰레드가 레드-블랙 트리에 쓰고 있다면, 읽기 쓰레드는 여전히 레드-블랙 트리를 질의할 수 있습니까? Red-Black 트리는 균형을 유지하기 위해 회전할 수 있으며 회전으로 인해 포인터가 변경되어 문제가 발생할 수 있습니다. 따라서 red-black 트리를 변환할 때 red-black 트리뿐만 아니라 이중 연결 리스트도 유지하게 되는데, 이때 읽기 쓰레드가 차단되지 않도록 이중 연결 리스트를 쿼리하게 된다. red-black tree에 쓰레드 쓰기, 쓰기 대기 또는 읽기가 있는지 판단하는 방법은 TreeBin의 lockState에 따라 판단하는데 1이면 쓰는 쓰레드가 있고, 2이면 쓰는 쓰레드가 있다는 뜻이고, 4n이면 쓰기를 기다리는 쓰기 스레드가 있음을 의미하며, 이는 읽기 작업을 수행하는 여러 스레드가 있음을 의미합니다.

추천

출처blog.csdn.net/lx9876lx/article/details/129116483