JavaConcurrency - 스 레 드 안전 한 집합 CurrentHashMap 을 어떻게 이해 합 니까?

15824 단어 JavaConcurrency
만약 에 다 중 스 레 드 가 하나의 데이터 구 조 를 동시에 수정 하려 면 예 를 들 어 산 목록 은 이 데이터 구 조 를 파괴 하기 쉽다. 예 를 들 어 하나의 스 레 드 는 표 에 새로운 요 소 를 삽입 하기 시작 할 수 있 고 산 목록 각 통 간 의 링크 관 계 를 조정 하 는 과정 에서 통제 권 을 박탈 당 했다 고 가정 할 수 있다.다른 스 레 드 도 같은 링크 보다 시작 하면 잘못된 링크 를 사용 하여 혼란 을 일 으 킬 수 있 으 며 이상 또는 사 순환 에 빠 질 수 있 습 니 다.
공유 데이터 구 조 를 보호 하기 위해 잠 금 을 선택 할 수 있 지만 스 레 드 안전 실현 을 대체 하 는 것 이 더 쉬 울 수 있 습 니 다.
효율 적 인 매 핑, 집합 과 대기 열: java. util. concurrent 패 키 지 는 매 핑, 질서 있 는 집합 과 대기 열의 효율 적 인 실현 을 제공 합 니 다. Concurrent HashMap Concurrent SkipListMap Concurrent SkipListSet Concurrent LinkedQueue 등 기하학 적 으로 복잡 한 단발 머리 를 사용 하여 데이터 구조의 서로 다른 부분 에 동시 접근 하여 경쟁 을 극소 화 합 니 다.
스 레 드 안전 집합 원자 업데이트: Concurrent HashMap 은 계수 코드 를 증가 시 키 고 아래 코드 는 안전 하지 않 습 니 다.
long oldValue=map.get(word);
long newValue=oldValue == null ?1:oldValue+1;
map.put(word,newValue);

다 중 스 레 드 작업 시 get 과 put 코드 는 데이터 구 조 를 파괴 하지 않 습 니 다. 그러나 작업 서열 은 원자 가 아니 기 때문에 결 과 는 예측 할 수 없습니다.
정확 한 방법 은 Concurrent HashMap 의 replace 방법 을 사용 하 는 것 입 니 다. 원자 방식 으로 새로운 값 으로 원 가 를 바 꿀 것 입 니 다. 전 제 는 이전에 다른 스 레 드 가 원 가 를 다른 값 으로 바 꾸 지 않 았 다 는 것 입 니 다.그리고 replace 가 성공 할 때 까지 계속 이렇게 해 야 합 니 다.이것 은 CAS 조작 이다.
do{
	long oldValue=map.get(word);
	long newValue=oldValue == null ?1:oldValue+1;
}while(!map.replace(word,oldValue,newValue))

사용 가능:
map.putIfAbsent(word,new LongAdder());
map.get(word).increment();

첫 번 째 문 구 는 롱 애 더 가 원자 증 가 를 완성 할 수 있 도록 확보한다.putIfAbsent 가 맵 의 값 을 되 돌려 주기 때문에 (원래 값 일 수도 있 고, 새로 설 정 된 값 일 수도 있 습 니 다) 한 문장 으로 조합 할 수 있 습 니 다:
map.putIfAbsent(word,new LongAdder().increment());

컴퓨터 방법 을 사용 할 때 키 와 계산 새 값 함 수 를 제공 할 수 있 습 니 다.이 함수 수신 키 와 연 결 된 값 (값 이 없 으 면 null) 은 새 값 을 계산 합 니 다.예 를 들 어 정수 계수기 의 맵 을 다음 과 같이 업데이트 할 수 있 습 니 다.
map.computer(word,(k,v)-> ==null? 1 : v+1);

Concurrent HashMap 에 null 값 이 있 으 면 안 됩 니 다.맵 에 주어진 키 가 존재 하지 않 음 을 null 값 으로 표시 하 는 방법 이 많 습 니 다.
또한 컴퓨터 IfPresent 와 컴퓨터 IfAbsent 방법 도 있다. 그들 은 각각 원래 값 이 있 는 상태 에서 만 새 값 을 계산 하거나 원래 값 이 없 는 상태 에서 만 새 값 을 계산한다.LongAdder 카운터 맵 을 다음 과 같이 업데이트 할 수 있 습 니 다.
map.computerIfAbsent(word,k->new LongAdder()).increment();

이것 은 이전의 putIfAbsent 호출 과 거의 같 지만 LongAdder 구조 기 는 새로운 카운터 가 필요 해야만 호출 할 수 있 습 니 다.
키 를 추가 할 때 보통 특수 처 리 를 해 야 합 니 다.merge 방법 을 이용 하면 이 점 을 매우 편리 하 게 할 수 있다.이 방법 은 키 가 존재 하지 않 을 때 사용 하 는 초기 값 을 나타 내 는 인자 가 있 습 니 다.그렇지 않 으 면 원래 값 과 초기 값 을 결합 하기 위해 서 당신 의 체통 함 수 를 호출 할 것 입 니 다.
map.merge(word,1L,(existingValue,newValue) -> existingValue + newValue);
//         
map.merge(word,1L,Long::sum);

메모: 컴퓨터 or merge 에 들 어 온 함수 가 null 로 돌아 오 면 맵 에서 기 존 항목 을 삭제 합 니 다.컴퓨터 or merge 를 사용 할 때, 당신 이 제공 한 함수 가 너무 많은 일 을 할 수 없다 는 것 을 기억 해 야 합 니 다.이 함수 가 실 행 될 때 맵 에 대한 다른 업 데 이 트 를 막 을 수 있 습 니 다.물론 이 함수 도 맵 의 다른 부분 을 업데이트 할 수 없습니다.
CurrentHashMap 의 일괄 작업: 자바 8 은 병렬 해시 맵 에 일괄 작업 을 제공 합 니 다. 다른 스 레 드 가 맵 을 처리 하 더 라 도 이 작업 들 은 안전하게 실 행 될 수 있 습 니 다. 현재 맵 의 스냅 샷 을 동결 하지 않 아 도 됩 니 다.세 가지 조작:
  • 검색 (search) 은 함수 가 null 이 아 닌 결 과 를 만 들 때 까지 모든 키 or 값 에 함 수 를 제공 합 니 다.그리고 검색 종료, 이 함수 의 결 과 를 되 돌려 줍 니 다
  • 규약 (reduce) 은 모든 키 or 값 을 조합 합 니 다. 여기 서 제공 하 는 누적 함수
  • 를 사용 해 야 합 니 다.
  • foreach 는 모든 키 or 값 에 함수
  • 를 제공 합 니 다.
    동작 마다 4 가지 버 전이 있 습 니 다: operation Keys: 처리 키 operation Values: 처리 값 operation: 처리 키 와 값 operation Entries: 처리 MapEntry 대상
    상기 각 작업 에 대해 매개 변수 화 한도 값 (threshold) 을 지정 해 야 합 니 다.맵 에 포 함 된 요소 가 이 한도 값 보다 많 으 면 일괄 작업 을 병행 합 니 다.일괄 작업 이 한 스 레 드 에서 실행 되 기 를 원한 다 면 한도 값 LongMAX 를 사용 할 수 있 습 니 다.VALUE。희망 커미션 이 많 을 수 있 는 스 레 드 운행 일괄 작업 을 토 한 적 이 있 으 며 한도 값 1 을 사용 할 수 있 습 니 다.
    String result =map.search(threshold,(k,v)-> 1000? k:null);
    

    첫 번 째 출현 횟수 가 1000 번 이 넘 는 단 어 를 찾 으 면 result 는 첫 번 째 일치 하 는 단어 이 며, 조작 함수 가 모든 입력 에 null 로 돌아 가면 null 로 돌아 갑 니 다.
    foreach 방법 은 두 가지 형식 이 있 습 니 다.첫 번 째 는 각 맵 항목 에 만 소비자 함 수 를 제공 합 니 다. 예 를 들 어:
    map.forEach(threshold,(k,v)-> System.out.println(k+"->"+v));
    

    두 번 째 형식 은 변환기 함수 도 있 습 니 다. 이 함 수 는 먼저 제공 해 야 합 니 다. 그 결 과 는 소비자 에 게 전 달 됩 니 다.
    map.forEach(threshold,(k,v)->k+"->"+v,//Transformer
    			System.out::println);//Consumer
    

    변환 기 는 필터 로 사용 할 수 있 습 니 다. 변환기 가 null 로 돌아 오 면 이 값 은 소리 없 이 건 너 갑 니 다.예 를 들 어 1000 개 이상 의 항목 만 인쇄 합 니 다.
    map.forEach(threshold,
    			(k,v)-> v>1000? k+"->"+v : null,// filter and transformer
    			System.out::println);//the nulls are not passed to the consumer
    

    reduce 작업 은 누적 함수 로 입력 을 조합 합 니 다. 예 를 들 어 다음 과 같이 모든 가 치 를 계산 할 수 있 습 니 다.
    Long sum =map.reduceValues(threshold,Long::sum);
    

    foreach 와 유사 하 며 변환기 함수 도 제공 할 수 있 습 니 다. 다음 과 같이 가장 긴 키 의 길 이 를 계산 할 수 있 습 니 다.
    Integer maxLength=map.reduceKeys(threshold,
    	String::length,//Transformer
    	Integer::max) //Accumulator
    

    변환 기 는 하나의 필터 로 서 null 로 돌아 가 원 하지 않 는 입력 을 제거 할 수 있 습 니 다. 예 를 들 어:
    Long count =map.reduceValues(threshold,
    					v -> v>1000 ? 1L : null,
    					Long::sum);
    

    맵 이 null 이거 나 모든 항목 이 거 르 면 reduce 작업 은 null 로 돌아 갑 니 다.요소 가 하나 밖 에 없다 면, 리 턴 기 변환 결 과 는 누적 기 를 사용 하지 않 습 니 다.
    int, long, double 출력 에 대해 해당 하 는 특수 화 작업 이 있 는데 각각 접미사 ToInt, ToLong 과 ToDouble 이 있 습 니 다.입력 을 기본 형식 값 으로 바 꾸 고 기본 값 과 누적 기 함 수 를 지정 해 야 합 니 다.map 가 null 일 때 기본 값 을 되 돌려 줍 니 다.
    long sum =map.reduceValuesToLong(threshold,
    			Long::longValue,//Transformer to primitive type
    			0,//Default value for empty map
    			Long::sum)//Primitive type accumulator
    

    좋은 웹페이지 즐겨찾기