1 컬렉션 기초
1. JCF란 무엇인가요?
- JCF (Java Collections Framework) : 자바에서 데이터를 효율적으로 저장하고 관리할 수 있도록 도와주는 도구 모음
- 컬렉션에는 다양한 종류가 있고, 각각 데이터의 특성에 따라 다르게 사용된다.
- 사용예시
- 데이터를 순서대로 저장하고 싶을 때 ⇒ List 인터페이스 (ArrayList, LinkedList, Vector)
- 중복 없이 저장하고 싶을 때 ⇒ Set 인터페이스 (HashSet, LinkedHashSet, TreeSet)
- 빠르게 검색할 때 ⇒ Map 인터페이스 (HashMap, TreeMap)
2. JCF의 계층 구조
- 대표적인 인터페이스는
Collection, List, Set, Map이다.
- Collection 인터페이스 : 모든 컬렉션의 기본 인터페이스
- List 인터페이스 : 데이터의 순서를 유지하고, 중복을 허용하는 컬렉션
- Set 인터페이스 : 중복 없이 데이터를 저장하고 싶을 때 사용하는 컬렉션
- Map 인터페이스 : 키(Key)와 값(Value) 쌍으로 데이터를 저장하고 관리하는 컬렉션

3. List 인터페이스와 주요 구현체
- List 인터페이스는 순서가 있는 데이터의 목록을 관리한다.
- 데이터를 추가한 순서대로 저장되고 중복이 허용된다.
- List의 주요 구현체
- ArrayList : 내부적으로 배열을 사용하여 데이터를 저장하는 리스트로, 랜덤 접근이 매우 빠르다.
- LinkedList : 각 데이터가 다음 데이터와 연결된 구조로써 삽입과 삭제가 빠르다.
- Vector : ArrayList와 유사하지만 여러 스레드가 동시에 접근할 때 안전하게 관리할 수 있도록 설계되었다.
4. ArrayList와 LinkedList의 차이
- ArrayList 데이터를 배열처럼 저장해 놓았기 때문에 인덱스를 통한 접근이 빠르다.
- 데이터를 찾을 때 데이터가 저장된 칸 마다 인덱스가 존재하기 때문에 원하는 값을 바로 찾을 수 있다.
- 데이터 추가, 삭제 시 중간에 새 데이터를 추가하거나 삭제하려고 하면 그 데이터의 뒤에 있는 데이터들을 모두 한 칸 씩 이동시켜야 한다. 그러므로 속도가 느리다.
- LinkedList 각 데이터가 이전 데이터와 다음 데이터에 대한 참조값을 가지고 있다. 이름 처럼 연결된 구조로 되어있다.
- 데이터를 찾을 때 첫 번째 데이터부터 다음 데이터를 하나하나씩 따라가면서 찾아야 한다. 중간에 있는 데이터를 검색하기 위해선 처음부터 쭉 따라서 찾아야 하므로 속도가 느리다.
- 데이터 추가, 삭제 시 중간에 새 데이터를 추가하거나 삭제할 때는 원하는 값에서 고리만 끊어주면 되기 때문에 중간 데이터 추가 삭제의 속도는 매우 빠르다.
5. ArrayList와 Vector의 차이
- ArrayList는 기본적으로 비동기적입니다. 여러 스레드가 동시에 ArrayList를 쓰려고 하면 충돌이 생겨 오류가 발생할 수 있다. 보통 싱글 스레드 환경에서 사용한다.
- Vector는 동기화 기능을 지원하여 여러 스레드가 동시에 접근하더라도 안전하게 데이터를 관리할 수 있다. 하지만, 동기화로 인해 속도는 ArrayList보다 느려질 수 있다.
6. Stack과 Queue란?
- Stack (스택): LIFO(Last In, First Out)구조
- 사용 예 : 되돌리기(Undo) 기능, 호출 스택 관리, 페어 확인 등에 사용
- Queue (큐) : **FIFO(First In, First Out)구조
- 사용 예 : 프린터 작업 처리, 대기열 관리 등에 사용
7. Set 인터페이스와 주요 구현체
- Set은 중복을 허용하지 않는 컬렉션으로, 각 요소는 유일해야 한다.
- Set의 주요 구현체
- HashSet : 중복 없이 데이터를 저장하고, 빠른 검색이 가능하지만 순서는 보장하지 않는다.
- TreeSet : 데이터를 정렬하여 저장하며, 이진 트리 구조를 사용한다.
- LinkedHashSet : 데이터를 저장한 순서대로 유지하고, 중복 없이 관리해요.
8. Map 인터페이스와 주요 구현체
- Map은 키(Key)-값(Value) 쌍으로 데이터를 저장하는 컬렉션
- Map의 주요 구현체
- HashMap : 빠르게 키-값 쌍을 검색할 수 있지만 순서는 보장되지 않습니다.
- TreeMap : 키를 기준으로 데이터를 정렬하여 저장한다.
- LinkedHashMap : 데이터를 저장한 순서를 유지하며, 검색 성능이 뛰어나다.
2. 컬렉션 심화
1. Collection과 Collections의 차이
- Collection은 데이터를 모아놓는 기본 인터페이스 이 인터페이스를 구현하면 자바의 컬렉션 프레임워크에 속하는 데이터 구조가 되며, List, Set, Queue 등이 이 Collection 인터페이스를 상속받아 다양한 데이터 저장 방식과 특징을 가진 클래스들로 구현된다.
- Collections는 Collection과 관련된 유틸리티 메서드들을 모아놓은 클래스 Collection 인터페이스를 구현한 컬렉션 객체들(ArrayList, HashSet, LinkedList 등)을 더 쉽게 사용할 수 있게 조작하는 도구들을 제공한다.
2. Map 인터페이스가 Collection을 상속받지 않은 이유
- Map은
키-값 쌍으로 데이터를 저장하는 독특한 구조를 가지고 있다. 일반적인 Collection과 달리 각 데이터에 고유한 키를 붙여 저장하므로 구조가 달라 Collection의 하위 인터페이스가 아니다.
- 데이터 저장 방식의 차이
- Collection 인터페이스는 데이터를 단일 값의 목록으로 저장한다.
- Map 인터페이스는 Key-Value 쌍으로 데이터를 저장한다.
- 데이터 접근 방식의 차이
- Collection 인터페이스의 데이터는 순서 또는 인덱스를 기반으로 접근할 수 있다.
- Map 인터페이스는 각 값을 고유한 키로 식별해서 데이터에 접근한다.
3. iterable과 iterator의 차이
- Iterable은 반복 가능한 객체를 의미
- Iterator는 iterable 객체에서 데이터를 하나씩 꺼낼 수 있도록 해주는 도구
| Iterable | Iterator |
|---|
| 역할 | 컬렉션이 반복 가능한 구조임을 나타내는 인터페이스 | 컬렉션의 요소를 하나씩 순회하는 객체 |
| 주요 메서드 | iterator() 메서드 | hasNext(), next(), remove() 메서드 |
| 사용 방식 | for-each 루프나 iterator() 메서드를 통해 Iterator 객체를 얻음 | 요소를 하나씩 순회하거나 조건에 따라 삭제할 때 사용 |
| 사용 목적 | 컬렉션이 반복 가능한지 정의 | 컬렉션의 요소를 순차적으로 접근하고 조작 |
3. 스레드
1. Java에서 스레드를 생성하는 방법
- Runnable 인터페이스 구현 :
Runnable 인터페이스를 구현하여 작업 내용을 지정하고 Thread 객체에 전달해 스레드를 실행하는 방식
- Thread 클래스 상속 :
Thread 클래스를 직접 상속받아 작업을 정의하는 방식
- Callable 인터페이스 구현 :
Callable 인터페이스는 Runnable과 비슷하지만 값을 반환하고 예외를 던질 수 있는 call() 메서드를 제공한다. ExecutorService를 사용해 스레드를 실행하는 방식
예시 코드
import java.util.concurrent.*;
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable 실행 중: " + Thread.currentThread().getName());
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread 실행 중: " + Thread.currentThread().getName());
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable 실행 결과: " + Thread.currentThread().getName();
}
}
public class ThreadExample {
public static void main(String[] args) throws Exception {
Thread runnableThread = new Thread(new MyRunnable());
runnableThread.start();
MyThread thread = new MyThread();
thread.start();
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get());
executor.shutdown();
}
}
2. 스레드 풀의 개념과 사용 이유
- 스레드 풀 : 미리 여러 개의 스레드를 생성해놓고, 필요할 때마다 재사용하는 구조
- 작동방식
1. 작업 요청이 들어오면 스레드 풀에서 사용가능한 스레드를 할당하여 작업을 시작한다.
- 작업이 완료되면 해당 스레드는 종료되지 않고 대기 상태에 들어가고 다음 작업에 재사용된다.
- 새로운 작업 요청이 들어왔는데 모든 스레드가 바쁘다면 요청이 대기열에 들어가게 된다.
왜 스레드 풀을 사용할까요?
- 매번 새 스레드를 생성한면 비용이 많이 들지만 스레드 풀은 스레드를 재사용해서 성능을 최적화할 수 있다.
- 스레드 수를 제한해서 시스템에서 과도한 스레드 생성으로 인한 성능 저하를 방지할 수 있다.
3. 스프링과 같은 프레임워크에선 스레드 풀의 스레드 개수를 수백개 이상으로 운영한다. Context Switching이 일어남에도 이런 선택을 내린 이유가 뭘까?
- 동시 처리량을 극대화하고 시스템의 전체적인 효율성을 높이기 위해서이다.
대규모 어플리케이션에서는 수많은 요청이 동시에 일어난다. 스레드 풀의 스레드 수가 많으면 이런 작업들을 병렬로 처리할 수 있기 때문에 응답속도가 빨라지고 대기시간이 줄어들게 된다.
그리고 많은 서버 어플리케이션의 작업은 CPU를 계속해서 사용하는것이 아닌 DB, 네트워크등의 I/O 작업을 수행하는데 이때 스레드가 대기하는 idle time이 생긴다. idle time이 길어질수록 어플리케이션의 성능이 떨어지기 때문에 다른 작업을 처리할 수 있도록 추가 스레드를 사용하는 것이 효율적이다.
🎈 3주차 스터디 회고
(줌에서 스터디가 진행됐는데 풍선 날리던 스터디원들이 웃기고 귀여워서 이모지는 풍선으로ㅋㅋㅋ)
스터디에 참여하는 모든 분들이 다들 준비를 열심히 해오셨다. 면접관의 역할을 맡아 질문을 건넬 때에도 고심해서 질문을 고르게 된다. 이미 정리해두신 파일에 있는 정보는 다 알고 있다는 가정하에 질문을 생각해보다 보니 너무 지엽적인 질문을 하는게 아닌가 싶을때도 있었다. 다음부턴 이 부분은 조심해야겠다. 어차피 주니어를 뽑는 면접인데 이정도로 지엽적인게 중요할까? 라는 생각이 들었기 때문이다. 면접자가 얼마나 기본 지식에 대해 알고 있는지를 질문하는 것이 더 중요하다고 생각한다.
면접 스터디를 진행하고 피드백 받은 부분을 다시 생각해보자면,,
내가 '뭐..' 라는 말을 자주쓴다니 ! 자주 쓰는 어구가 있는 건 알았지만 그 단어가 뭐. 일 줄이야. 남들이 보기에 그게 도드라져 보인다면 안좋게 보일 수 있으니 고치는 것이 좋겠다.
그리고 자신이 원하는 대답이 있는데 그 대답이 나오지 않아서 그 쪽으로 유도하면서 꼬리질문을 주신 스터디원이 계셨다. 하지만 난 그 분이 원하는 대답을 하지 못했다...
Map과 Collection이 JCF에서 분리가 된 구조체계를 이루고 있는데 왜 이렇게 나뉘는 걸까요? 라는 질문이 었는데 이에 대해서 추가로 블로그에 작성하고 링크를 곧 달겠다. (결심함)
여담으로 내가 면접관이 되어 어떤분께 질문을 드렸다. 처음에는 잘 모른다며 대답을 못하셨지만 힌트를 조금 드리면서 이런 방향으로 생각해보시면 어떨까요? 라고 제안하니 바로 스스로 정답을 도출해 나가는 것을 직관했는데 되게 인상깊었다. 내가 면접관이라면 이렇게 스스로 사고할 줄 아는 지원자를 뽑을 것 같다. 👍👍👍
+) 안드로이드 개발자분이 진짜 spring에서는 스레드를 백개, 이백개씩 만들어서 사용하냐고 여쭤 봤을때 내가 실무에서 스레드 객체를 실제로 다뤄본 적이 있었나? 고민해봤다. 잘 기억이 나지 않아서 프로젝트에서 뒤져보니 내가 만지지도 않았던 부분에 있었다.
일단 Spring Boot와 같은 프레임워크 자체가 개발자가 스레드 작업을 신경쓰지 않아도 되도록 설계되어있다. => 추상화가 잘 되어있다는 증명이기도 하다.
또한 내장 tomcat이 스레드풀을 만들어 관리를 해주고 있기 때문에 개발자는 비즈니스 로직이라던지 API설계에 집중하면 된다.
application.yml 파일을 보면
server:
tomcat:
threads:
max: 200
min-spare: 10
이런 부분을 찾을 수 있을 것이다. 설계에 참여하지 않았던 신입사원의 나..는 모를 수 밖에.. 이제 개인 프로젝트를 진행하면서 이 부분을 한 번 깊이 파 볼 예정이다.
(할수있겠지..?ㅎ)