4개 정도 분량의 스크립트를 들었는데
와 예전의 나랑 지금의 나랑 좀 많이 다른 것 같아서 많은 생각과 감정이 들었다.
멘토님도 보고 싶고 암튼 기술적으로도 내가 많이 배웠는데도 아직도 많이 배울 것이 남았구나 싶었다.
컨커런트 해시맵은 어떻게 동시성을 제어할 수 잇게 되었어?
컨커런트 해시맵(ConcurrentHashMap)은 자바의 `java.util.concurrent` 패키지에 포함된 자료구조로, 멀티스레드 환경에서의 동시성을 효율적으로 다루기 위해 설계되었습니다. 이 해시맵은 여러 스레드가 동시에 읽고 쓰기 작업을 할 수 있도록 동시성 제어를 구현하고 있어, 고성능을 유지하면서도 데이터의 일관성을 보장합니다.
### 동시성 제어 방법
#### 1. 세그먼트(segment)와 버킷(bucket)
- **세그먼트화**: 초기의 `ConcurrentHashMap` 구현에서는 내부적으로 여러 개의 세그먼트(기본적으로 해시 테이블)를 사용하여, 각 세그먼트가 독립적인 lock을 가지고 있습니다. 이런 구조 덕분에 다른 세그먼트에 대한 동시 접근이 가능해져 동시성이 개선됩니다.
- **버킷 접근**: 각 세그먼트는 여러 버킷으로 구성되어 있으며, 특정 키에 대한 해시 연산 결과로 어느 세그먼트에 접근할지 결정됩니다.
#### 2. 락 분할(lock striping)
- **락 분할 기법**: 각 세그먼트는 별도의 락을 사용하기 때문에, 하나의 세그먼트에 락이 걸리더라도 다른 세그먼트는 영향을 받지 않아 동시 다발적인 접근이 가능합니다. 이는 전체 맵에 단일 락을 사용하는 것보다 락 경쟁을 현저히 줄여줍니다.
#### 3. 락 없는 읽기
- **읽기 연산 최적화**: `ConcurrentHashMap`은 읽기 연산에 대해 락을 사용하지 않습니다. 대신 복수의 스레드가 동시에 읽기를 수행할 수 있도록 허용하여 읽기 성능을 극대화합니다.
#### 4. 자바 8 이후의 개선
- **레드-블랙 트리 사용**: 자바 8에서는 해시 충돌이 많이 발생하는 버킷들을 연결 리스트 대신 레드-블랙 트리로 변환하여 성능을 향상시켰습니다. 이는 검색 시간을 향상시키며, 특히 많은 수의 엔트리가 있는 경우 유리합니다.
- **배열 접근 최적화**: 또한 자바 8에서는 전체 배열에 대한 락을 거는 대신, 특정 노드에 대해 CAS(compare-and-swap) 연산을 사용하여 업데이트의 원자성을 보장합니다. 이는 락 경쟁을 더욱 줄여 성능을 개선합니다.
### 사용 예제
import java.util.concurrent.ConcurrentHashMap;
public class Example {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key1", 100);
map.put("key2", 200);
System.out.println(map.get("key1")); // 100
System.out.println(map.get("key2")); // 200
map.remove("key2");
System.out.println(map.get("key2")); // null
}
}
### 결론
`ConcurrentHashMap`은 멀티스레드 환경에서 고성능을 유지하면서도 데이터의 안정성을 보장하기
위해 세그먼트화된 락 시스템, 락 없는 읽기 접근, 그리고 자바 8 이후의 구조적 개선을 통해 효과적인 동시성 제어를 실현합니다. 이러한 특성 때문에 고성능 동시성 프로그램에 널리 사용됩니다.
해시맵 내부 구조
`HashMap`은 자바의 `java.util` 패키지에 포함된 매우 중요한 자료 구조 중 하나로, 키-값 쌍을 저장하는 데 사용됩니다. 이 구조는 "해싱"이라는 기법을 사용하여 특정 키가 가진 값에 빠르게 접근할 수 있게 설계되었습니다. `HashMap`의 효율적인 성능은 이 내부 구조에 의존적입니다.
### HashMap의 내부 구조
#### 1. 버킷 배열 (Bucket Array)
- `HashMap`은 내부적으로 배열을 사용하여 데이터를 저장합니다. 이 배열을 통틀어 "버킷 배열"이라고 하며, 각 배열의 인덱스를 "버킷"이라고 합니다.
- 각 키의 `hashCode()` 메소드로부터 반환된 해시코드를 사용하여 배열의 인덱스를 계산합니다. 인덱스는 `hashCode()`가 반환한 값을 `HashMap`의 현재 크기로 나눈 나머지로 결정됩니다 (즉, `index = hashCode(key) & (n-1)` 식을 사용, 여기서 `n`은 배열의 크기).
#### 2. 해시 충돌 (Hash Collision)
- 두 개 이상의 키가 동일한 버킷에 할당될 때, 이를 "해시 충돌"이라고 합니다. `HashMap`은 연결 리스트를 사용하여 같은 버킷에 위치한 엔트리들을 연결합니다.
- 자바 8 이후로는 연결 리스트의 길이가 일정 기준(기본적으로 8)을 초과하면, 리스트는 레드-블랙 트리로 변환됩니다. 이는 검색 시간을 개선하기 위함입니다.
#### 3. `hashCode()`와 `equals()`
- `hashCode()` 메소드는 객체의 메모리 주소를 기반으로 한 고유한 정수를 반환합니다. 이 정수값은 `HashMap`에 객체를 저장하거나 검색할 때 인덱스 계산에 사용됩니다.
- `equals()` 메소드는 키의 동등성을 확인할 때 사용됩니다. 해시코드가 같더라도 실제 객체가 다를 수 있기 때문에, `equals()`로 두 키가 실제로 같은지 최종 확인을 합니다.
#### 4. 동적 재해싱 (Dynamic Rehashing)
- `HashMap`은 "로드 팩터(load factor)"와 "초기 용량(initial capacity)" 개념을 사용하여 시간과 공간 효율을 균형잡습니다. 로드 팩터는 `HashMap`이 얼마나 차 있을 때 크기를 늘릴지를 결정하는데, 기본값은 0.75입니다.
- 삽입 연산 중, 저장된 엔트리의 수가 로드 팩터와 초기 용량의 곱을 초과하면, `HashMap`은 "재해싱" 과정을 수행합니다. 이는 모든 엔트리를 새로운, 크기가 두 배인 버킷 배열에 재분배하는 과정을 말합니다.
import java.util.HashMap;
public class Example {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("apple", 50);
map.put("banana", 20);
map.put("cherry", 30);
System.out.println(map.get("apple")); // 출력: 50
System.out.println(map.get("banana")); // 출력: 20
map.remove("banana");
System.out.println(map.get("banana")); // 출력: null
}
}
### 결론
`HashMap`은 매우 빠른 접근 시간을 제공하며, 내부적으로 배열, 연결 리스트 혹은 레드-블랙 트리를
사용하여 데이터를 저장합니다. 이러한 구조 덕분에 해시 충돌을 효과적으로 처리할 수 있으며, 동적 재해싱을 통해 데이터의 균형을 유지합니다. `HashMap`의 이러한 내부 메커니즘이 모든 키-값 쌍의 빠른 검색, 삽입, 삭제 작업을 가능하게 합니다.
Public Protected Default Priviate 접근 제어자 설명 해줘
Java에서 접근 제어자(Access Modifiers)는 클래스, 변수, 메소드 및 생성자에 대한 접근을 제어하는데 사용됩니다. 접근 제어자는 해당 멤버(변수, 메소드)나 클래스를 어떤 다른 클래스가 접근할 수 있는지, 사용할 수 있는지를 정의합니다. Java에서는 크게 네 가지 접근 제어자를 사용합니다: `public`, `protected`, `default`(명시하지 않으면 자동 적용), 그리고 `private`.
### 1. Public
- `public` 접근 제어자는 클래스, 메소드, 생성자, 인터페이스 등에 적용할 수 있습니다.
- `public`으로 선언된 멤버는 자바의 모든 클래스에서 접근할 수 있습니다.
- 다른 패키지의 클래스에서도 이 멤버들을 접근할 수 있으며, 어떤 제한도 없습니다.
**예제:**
public class Car {
public int year;
public Car() {
// 생성자
}
public void display() {
System.out.println("년도: " + year);
}
}
이 경우, `Car` 클래스와 그 내부의 `year` 변수, `display()` 메소드, 그리고 생성자는 어디서든 접근 가능합니다.
### 2. Protected
- `protected` 접근 제어자는 클래스 멤버에 대해 더 제한적인 접근을 허용합니다.
- `protected` 멤버는 같은 패키지 내의 다른 클래스나, 다른 패키지의 자식 클래스에서 접근할 수 있습니다.
**예제:**
```java
public class Vehicle {
protected String brand = "Ford"; // 차량 브랜드
protected void honk() { // 경적 소리
System.out.println("뿌뿌!");
}
}
```
이 경우, `Vehicle` 클래스의 `brand` 변수와 `honk()` 메소드는 같은 패키지 내의 클래스나 `Vehicle` 클래스를 상속받은 하위 클래스에서 사용할 수 있습니다.
### 3. Default (Package-Private)
- 명시적으로 접근 제어자를 지정하지 않은 경우, 자바는 기본적으로 `default` 접근 제어자를 적용합니다.
- `default` 접근 제어자로 선언된 멤버는 같은 패키지 내의 클래스들만 접근할 수 있습니다.
- 다른 패키지의 클래스에서는 접근할 수 없습니다.
class Helper {
int addTwoNumbers(int a, int b) {
return a + b;
}
}
이 경우, `Helper` 클래스와 `addTwoNumbers()` 메소드는 같은 패키지 내의 다른 클래스들에서만 접근 가능합니다.
### 4. Private
- `private` 접근 제어자는 가장 제한적인 접근을 제공합니다.
- `private`으로 선언된 멤버는 같은 클래스 내에서만 접근할 수 있습니다.
- 해당 클래스 외부에서는 어떠한 접근도 허용하지 않습니다.
public class Person {
private String name = "John Doe";
private void printName() {
System.out.println(name);
}
}
이 경우, `Person` 클래스의 `name` 변수와 `printName()` 메소드는 `Person` 클래스 내부에서만 접근 가능하며, 외부에서는 접근할 수 없습니다.
### 요약
접근 제어자는 객체 지향 프로그래밍에서 데이터 은닉과 캡슐화를 지원하는 중요한 기능입니다. 각 접근 제어자는 데이터와 메소드의 보안 수준을 설정하여, 외부로부터의 무단 접근을 방지하
'개인 개발 공부 일지' 카테고리의 다른 글
| 파이썬 시작, 기초 (0) | 2024.04.29 |
|---|---|
| IT 회사 면접 후기 (0) | 2024.04.22 |
| PHP 입문과 궁금했던 점 (0) | 2024.03.27 |
| Clasic ASP (1) | 2024.03.27 |
| JS, JSX 차이와 같은 점 (React) (1) | 2024.03.20 |