Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

To Dare Is To Do!

java 면접 질문 공부 본문

Java

java 면접 질문 공부

Nick_Choi 2024. 9. 2. 16:38

자바의 모든 클래스는 Object 클래스를 상속받습니다.
그리고 Object클래스에는 equals() 와 hashCode() 라는 메소드가 선언되어 있습니다.

이 메소드들은 각각 어떤 역할일까요? 이 둘의 차이점은 무엇일까요?

더보기

자바에서 equals() 메소드는 두 객체가 "논리적으로" 동일한지를 판단하는 데 사용됩니다. 기본적으로 Object 클래스의 equals() 메서드는 두 객체의 참조를 비교하여, 두 객체가 같은 메모리 주소를 가리키는지를 확인합니다 그러나 대부분의 경우, 객체의 필드 값이 같은지를 기준으로 객체를 비교하는 것이 더 유용하기 때문에 equals() 메서드를 오버라이드하여 사용합니다. 반면 hashCode() 메소드는 객체의 해시 코드를 반환한다. 이 때 해시 코드는 정수형 값으로 해시 기반의 컬렉션에서 객체를 빠르게 저장하고 검색하기 위해 사용됩니다.


equals()와 hashCode()는 서로 연관되어 있어, equals()를 오버라이딩할 때는 hashCode()도 반드시 일관성 있게 오버라이딩해야 합니다.
equals()가 같은 두 객체는 반드시 같은 hashCode()를 가져야 하지만, 반대로 같은 hashCode()를 가지는 두 객체가 반드시 같은 equals() 결과를 반환하지는 않을 수 있습니다.

"hashCode" 를 잘못 오버라이딩하면 "HashMap" 등 hash 콜렉션의 성능이 떨어질 수가 있습니다.
어떤 케이스일 때 그럴 수 있을까요?

더보기

hashcode를 잘못 오버라이딩한 상황을 먼저 정의하면

  1. 같은 hashcode를 반환하는 경우
  2. equals 메서드와 일관성이 없는 hashcode를 반환하는 경우 등이 있을 거라고 생각합니다.

이렇게 hashcode를 재정의하는 과정에서 위와 같은 상황이 발생한다면

    1. hash 자료구조에 저장되는 객체들이 모두 동일한 버킷에 저장되어 빈번한 해시 충돌이 발생할 것입니다. 이러면 해시 테이블 본래의 이점을 상실할 뿐만 아니라 최악의 경우 O(n)의 성능을 보일 수 있습니다.
    2. equals에서 true가 나왔음에도 불구하고 다른 해시 코드를 가질 수 있어 같은 객체가 중복 저장되어 추후 객체를 찾거나 삭제하는 작업의 성능이 저하될 수 있습니다

"HashMap"은 내부적으로 어떻게 구현되어있길래 그렇게 빨리 값을 탐색할 수 있을까요?

더보기

Java에서 키-값 쌍을 저장하기 위해 사용하는 해시 테이블 기반의 데이터 구조입니다

각 키는 고유하며, 키를 사용하여 해당하는 값을 빠르게 검색할 수 있습니다.

해싱이라는 과정을 통해 key를 받아서 정수값인 해시코드를 반환하고

반환된 해시코드는 hash 배열의 각 요소인 버킷의 인덱스가 됩니다.

 

키(key)를 주면 해싱 함수에 의해 해시코드로 변환되고, 해당 해시코드는 배열의 각 요소인 버킷의 인덱스 역할을 한다. 해당 버킷을 찾아가면 값을 삽입 및 조회할 수 있다.

hashmap은 키를 사용하여 값을 빠르게 검색하거나 수정할 수 있으며 이는 시간 복잡도 o(1)의 효과를 얻을 수 있다.

기존 "HashMap" 의 시간복잡도는 얼마이고, "hashCode" 를 잘못 오버라이딩 했을때의 시간복잡도는 얼마일까요?

더보기

HashMap의 시간 복잡도는 다양한 상황에 따라 달라질 수 있습니다.
정상적인 상황과 hashCode 메서드를 잘못 오버라이딩했을 때의 시간 복잡도를 비교하여 설명드리겠습니다.

1. 정상적인 상황 (올바른 hashCode 구현)

  • 탐색, 삽입, 삭제의 평균 시간 복잡도: O(1)
    • HashMap은 키를 해싱하여 해시 테이블의 특정 버킷으로 바로 접근할 수 있으므로, 일반적인 경우 탐색, 삽입, 삭제 모두 평균적으로 O(1)의 시간 복잡도를 가집니다. 이는 해시 충돌이 거의 없는 경우에 해당합니다.
  • 최악의 경우 시간 복잡도: O(n)
    • 해시 충돌이 빈번하게 발생하여 모든 요소가 하나의 버킷에 체이닝 방식으로 연결 리스트로 저장되면, 최악의 경우 특정 키에 대해 O(n)의 시간 복잡도가 발생할 수 있습니다. 다만, Java 8 이후 트리화가 적용되면 최악의 경우가 O(log n)으로 줄어듭니다.

2. hashCode를 잘못 오버라이딩한 경우

  • 항상 같은 hashCode 값을 반환하는 경우:
    • 만약 모든 객체의 hashCode가 동일한 값을 반환한다면, 모든 키가 동일한 버킷에 저장됩니다. 이 경우 모든 요소가 하나의 연결 리스트에 저장되므로, 탐색, 삽입, 삭제의 시간 복잡도가 O(n)으로 증가합니다. 트리화가 적용되면 O(log n)으로 개선되긴 하지만, 여전히 O(1)보다 훨씬 비효율적입니다.
  • equals와 일관되지 않은 hashCode:
    • hashCode가 올바르게 오버라이딩되지 않아 equals가 true를 반환하는 객체들이 서로 다른 해시 값을 가지면, HashMap은 동일한 객체로 인식하지 못하고 다른 버킷에 저장하게 됩니다. 이로 인해 중복된 키가 여러 버킷에 걸쳐 저장되거나, 잘못된 버킷에 접근하는 경우 탐색과 삭제 시 예상치 못한 결과와 성능 저하가 발생할 수 있습니다. 이 경우 시간 복잡도는 분석하기 어렵지만, 효율이 크게 떨어지며 O(n)에 가까운 성능 저하가 발생할 가능성이 큽니다.

StringBuilder와 StringBuffer의 차이는 무엇일까요?

더보기

둘 다 String 클래스와 비슷하게 문자열을 다루기 위한 클래스지만, 몇 가지 중요한 차이점이 있습니다.

StringBuilder

  • 문자열을 변경할 수 있는 가변객체
  • 동기화를 지원하지 않아 스레드에서 안전하게 사용하려면 단일 스레드에서만 사용해야 한다.
  • 동기화가 없으므로 StringBuffer보다 빠른성능을 보여줌

사용 시기

  • 단일 스레드 환경에서 문자열을 빈번하게 변경해야 하는 경우
  • 문자열의 추가, 삭제, 변경이 자주 발생하는 경우
  • 웹 애플리케이션에서 사용자의 입력을 처리하거나, 파일에서 데이터를 읽어오는 경우와 같이 문자열의 변경이 빈번하게 발생하는 상황에서는 StringBuilder의 사용을 고려
  • 문자열 이어 붙이는 작업이 많을 시 사용

StringBuffer

  • 문자열을 변경할 수 있는 가변객체
  • 내부적으로 동기화를 지원하여 멀티스레드 환경에서 안전하게 사용할 수 있음
  • 동기화로 인한 오버헤드가 있을 수 있음 (성능문제)
    • 모든 메소드에 대해 synchronized를 통해 blocking을 거는 것은 성능 상으로 좋지 않음
    • 여러 동기화 메소드로 이루어진 하나의 메소드를 여러 스레드가 호출할 때는 스레드 안전하지 않을 수 있음

사용 시기

  • 문자열 변경이 거의 없는 경우
  • 문자열 리터럴을 많이 사용하는 경우
  • 멀티스레드 환경에서 문자열을 안전하게 사용해야 하는 경우

각 클래스의 차이

특성  String  StringBuffer  StringBuilder
불변성 불변 가변 가변
쓰레드 안전성 안전 안전 안전하지 않음
성능 동기화 없음으로 빠름 동기화로 인해 느림 동기화 없음으로 매우 빠름
사용 시기 문자열 변경이 거의 없는 경우 멀티스레드 환경에서 문자열 변경 단일 스레드 환경에서 문자열 변경

왜 동기화(synchronized)가 걸려있으면 느린걸까요?

더보기

동기화(synchronized)는 멀티스레드 환경에서 한 번에 하나의 스레드만 특정 블록이나 메서드에 접근할 수 있도록 보장하는 메커니즘입니다.
이는 동시성 문제를 방지하지만, 스레드 경쟁, 락 오버헤드, 락 경합과 같은 성능에 부정적인 영향을 미칠 수 있습니다.

 

  • 스레드 경쟁:
    • synchronized 블록에 진입하기 위해 여러 스레드가 경쟁합니다. 이 과정에서 스레드 간의 컨텍스트 스위칭이 발생하며, 이 스위칭은 CPU 자원을 소모합니다.
  • 락 오버헤드:
    • 동기화된 메서드나 블록에서는 JVM이 락을 획득하고 해제하는 과정을 거칩니다. 이 락을 얻기 위한 비용이 크며, 특히 다중 스레드 환경에서는 성능 저하가 더욱 두드러집니다.
  • 락 경합:
    • 여러 스레드가 동시에 같은 락을 획득하려고 할 때 경합이 발생합니다. 이로 인해 스레드가 대기 상태에 머물게 되고, 전체적인 성능이 떨어지게 됩니다.

 

싱글 스레드로 접근한다는 가정하에선 "StringBuilder" 와 "StringBuffer" 의 성능이 똑같을까요?

더보기

싱글 스레드 환경에서도 StringBuffer는 synchronized 키워드로 인해 성능이 StringBuilder보다 느립니다. synchronized 키워드는 내부적으로 락을 관리하고, 이를 확인하는 과정이 포함되어 있기 때문에 오버헤드가 발생합니다. 따라서 싱글 스레드 환경에서 StringBuilder가 StringBuffer보다 더 빠르게 동작합니다.

 

synchronized 키워드의 동작 원리

  • 모니터 락 (Monitor Lock): synchronized 블록이나 메서드는 객체의 모니터 락을 획득해야 합니다.
    이 과정에서 JVM이 스레드의 상태를 변경하며, 이는 추가적인 오버헤드를 발생시킵니다.
    락이 필요하지 않더라도 이 과정을 거치기 때문에 synchronized 메서드는 성능이 떨어집니다.
  • JVM의 내부 동작:
    • synchronized 키워드를 사용하면, JVM은 해당 메서드나 블록에 대한 모니터 락을 요청합니다.
      이때 JVM은 현재 스레드가 모니터 락을 소유하고 있는지 확인하며, 락을 얻지 못한 스레드는 대기하게 됩니다.
    • 비록 싱글 스레드 환경에서는 스레드 간 경합이 없지만, 모니터 락을 관리하기 위한 오버헤드가 여전히 존재합니다.

System.out.println 메소드는 현업에서 절대 쓰지 말라고하는 메소드인데요. 그 이유가 무엇일까요?

더보기

현업에서 System.out.println 메소드를 피해야 하는 이유는 이 메소드가 블로킹 I/O를 사용하고 있으며, synchronized로 인해 동기화 오버헤드가 발생하기 때문입니다.
이러한 성능 문제는 특히 대용량의 로그 출력이나 멀티스레드 환경에서 더 두드러집니다.

따라서 SLF4J와 같은 로깅 프레임워크를 사용해 비동기 로깅을 하거나, 성능에 미치는 영향을 최소화할 수 있는 방안을 고려하는 것이 바람직한 것으로 알고 있습니다.

synchronized 키워드는 왜 현업에서 큰 성능 저하를 일으킬 수 있을까요?

더보기
  • 락 경합:
    synchronized 키워드는 한 번에 하나의 스레드만 해당 메소드나 블록에 접근할 수 있도록 모니터 락을 사용합니다. 여러 스레드가 동시에 synchronized된 코드에 접근하려고 할 때, 락을 얻기 위해 기다려야 합니다. 이로 인해 스레드들이 대기 상태에 들어가며, 락 경합이 심할수록 성능이 저하됩니다.
  • 컨텍스트 스위칭 (Context Switching)
    스레드가 락을 얻기 위해 대기 상태에 들어가면, 운영체제는 다른 스레드를 실행해야 합니다. 이 과정에서 컨텍스트 스위칭이 발생합니다. 컨텍스트 스위칭은 CPU가 한 스레드의 상태를 저장하고 다른 스레드로 전환하는 과정으로, 상당한 오버헤드를 발생시킬 수 있습니다.
  • 병렬성 제한 (Reduced Parallelism)
    synchronized는 스레드 간의 동시 실행을 제한합니다. 스레드가 병렬로 실행될 수 있는 상황에서도, 락이 걸리면 하나의 스레드만 실행 가능해집니다. 이는 CPU 자원을 효율적으로 사용하지 못하게 만들고, 애플리케이션의 확장성을 저해할 수 있습니다.
  • 데드락 (Deadlock)
    잘못된 락 사용은 데드락을 초래할 수 있습니다. 데드락이 발생하면, 여러 스레드가 서로의 락을 기다리며 영원히 멈추게 됩니다. 이는 시스템 성능에 심각한 문제를 일으킵니다.

 

synchronized 키워드는 동기화의 비용을 동반하며, 특히 멀티스레드 환경에서 성능 저하를 초래할 수 있습니다. 락 경합, 컨텍스트 스위칭, 병렬성 제한, 데드락 등의 이슈로 인해, 주의해서 사용해야 한다.

 

예시: 한 대의 컴퓨터에서 여러 사람이 동시에 같은 파일을 열려고 하면, 컴퓨터는 파일을 순서대로 열어야 해서 대기 시간이 발생하는 것과 비슷합니다.

Blocking IO는 왜 성능을 저하시킬 수 있을까요?

더보기
  • 스레드 대기 (Thread Blocking)

Blocking I/O는 요청한 I/O 작업(예: 파일 읽기/쓰기, 네트워크 통신 등)이 완료될 때까지 스레드를 대기 상태로 만듭니다. 이 동안 해당 스레드는 아무 작업도 하지 못하고, 다른 작업을 처리하지도 못합니다. 이는 자원을 비효율적으로 사용하는 결과를 초래합니다.

  • 리소스 소모 (Resource Consumption)

Blocking I/O는 대기 상태에서 많은 스레드를 유지시킨다. 만약 여러 스레드가 동시에 I/O 작업을 수행하려 한다면, 각 스레드는 I/O 작업이 다른 작업을 하지 않음에도 불구하고 끝날 때까지 대기해야 하며, 이는 스레드 풀의 크기를 크게 만들고 이유 없이 메모리와 CPU 자원을 차지하는 것을 유지시킨다.
특히 많은 연결을 처리해야 하는 서버 애플리케이션에서, 스레드 수가 증가함에 따라 메모리와 CPU 자원의 소모도 급격히 증가합니다.

  • 스케일링 한계 (Scaling Limits)

Blocking I/O는 스레드 수에 제한이 있는 환경에서 스케일링 문제를 일으킵니다. 스레드가 대기 상태에서 자원을 점유하기 때문에, 높은 동시성을 필요로 하는 시스템에서는 더 많은 스레드를 생성하거나 더 많은 자원을 할당해야 하는데, 이는 시스템의 성능 한계를 초래합니다.

  • 컨텍스트 스위칭 (Context Switching)

Blocking I/O로 인해 스레드가 대기 상태에 들어가면, 운영체제는 다른 스레드로 전환해야 합니다. 이 과정에서 컨텍스트 스위칭이 발생하는데, 이는 CPU가 스레드의 상태를 저장하고 복원하는 작업으로, 성능에 부정적인 영향을 미치는 오버헤드가 발생합니다.

  • 응답 시간 지연 (Increased Latency)

Blocking I/O는 대기 시간이 발생하기 때문에, 전체 응답 시간이 증가할 수 있습니다. 이는 사용자 경험을 저하시킬 수 있으며, 특히 실시간 처리나 낮은 지연 시간이 중요한 애플리케이션에서 문제가 됩니다.

 

=> Blocking I/O에서는 스레드가 아무 작업도 하지 않으면서도 메모리와 CPU 자원을 차지하게 됩니다. 이 자원은 다른 중요한 작업에 사용될 수 있지만, 멈춘 스레드 때문에 그럴 수 없게 되는 것입니다.

synchronized 가 Blocking IO 와 만나면 어떻게 환장의 성능하락을 만들 수 있는걸까요?

더보기
  • 한 스레드가 synchronized 블록에 들어감:
    특정 스레드가 synchronized 키워드를 가진 코드 블록에 들어가면, 이 코드 블록을 보호하기 위해 잠금(lock)이 걸립니다. 이 동안 다른 스레드들은 이 블록에 접근할 수 없습니다.
  • Blocking I/O로 인해 스레드가 멈춤:
    이제 그 스레드가 synchronized 블록 내에서 I/O 작업을 시작합니다. 하지만 I/O 작업은 시간이 걸리고, 이 동안 스레드는 아무것도 하지 않고 멈춰서 기다리게 됩니다.
  • 다른 스레드들이 대기:
    다른 스레드들은 이 synchronized 블록에 들어가고 싶어도, 현재 I/O 작업을 기다리는 스레드가 블록을 점유하고 있기 때문에 기다려야 합니다. 그런데 그 I/O 작업이 끝나기까지 시간이 걸리므로, 이 대기 시간이 길어집니다.
  • 자원 낭비:
    멈춰있는 스레드와 기다리는 스레드들은 모두 CPU와 메모리를 사용합니다. 하지만 이 자원들은 아무 유용한 작업을 하지 않고 낭비됩니다.

synchronized와 Blocking I/O가 함께 있을 때, 하나의 스레드가 I/O 작업을 수행하는 동안 다른 스레드들이 아무것도 하지 못하고 기다리게 되며, 이로 인해 시스템 성능이 크게 저하될 수 있습니다.

 

ArrayList 는 내부적으로 어떻게 구현되어있을까요?

더보기

 

ArrayList는 내부적으로 배열(array) 을 사용하여 요소를 저장합니다. 이 배열은 고정된 크기를 가지지만, 필요한 경우 크기를 동적으로 조절할 수 있습니다.

배열은 연속된 메모리 공간에 저장되기 때문에 인덱스를 이용한 **빠른 접근(O(1))**이 가능합니다.

ArrayList는 요소를 추가할 때, 만약 현재 배열이 꽉 차 있다면, 새로운 배열을 만들고 기존 배열의 요소들을 새로운 배열로 복사합니다.
새로운 배열은 일반적으로 기존 배열 크기의 1.5배 또는 2배로 크기를 증가시킵니다.이렇게 함으로써, 공간을 효율적으로 사용할 수 있습니다.

스레드는 왜 써야하는 것일까요?

더보기

스레드는 프로그램의 성능과 반응성을 향상시키기 위해 동시 작업 처리, 자원 효율성, 병렬 처리 등을 가능하게 합니다. 이를 통해 여러 작업을 동시에 수행하거나, 사용자 인터페이스와 백그라운드 작업을 분리하여 보다 원활한 사용자 경험을 제공할 수 있습니다.

 

1. 동시 작업 처리

스레드를 사용하면 프로그램이 여러 작업을 동시에 수행할 수 있습니다.
ex) 웹 서버는 클라이언트 요청을 동시에 처리할 수 있게 해줍니다.
스레드를 이용해 동시에 여러 작업을 처리하면 전체적인 성능과 응답 속도가 향상됩니다.

 

2. 자원 효율성

스레드는 같은 프로세스 내에서 메모리와 자원을 공유하므로, 새로운 프로세스를 생성하는 것보다 더 적은 자원으로 여러 작업을 동시에 수행할 수 있습니다. 이로 인해 메모리와 CPU 자원을 더 효율적으로 사용할 수 있습니다.

 

3. 반응성 향상

스레드를 사용하면 프로그램의 사용자 인터페이스(UI)와 백그라운드 작업을 분리할 수 있습니다.

ex) UI 스레드는 사용자의 입력을 처리하고, 별도의 스레드는 데이터 처리나 네트워크 통신을 수행합니다. 이렇게 하면 UI가 멈추지 않고 원활하게 작동할 수 있습니다.

 

4. 병렬 처리

멀티코어 프로세서에서 스레드를 사용하면 여러 스레드를 동시에 실행할 수 있어, 데이터 처리나 계산 작업을 병렬로 수행할 수 있습니다. 이는 계산 속도를 크게 향상시킬 수 있습니다.

 

5. 비동기 작업

스레드는 비동기적으로 작업을 수행할 수 있게 해줍니다.
ex) 파일 다운로드, 데이터베이스 쿼리, 네트워크 통신 등을 별도의 스레드에서 실행하면 주 프로그램 흐름이 차단되지 않고 계속 진행될 수 있습니다.

스레드를 쓰면 동시에 여러 일을 처리할 수 있으니 한 1만개정도 띄우면 너무 좋지 않을까요?
사실 좋지 않은데.. 왜 좋지 않을까요? 스레드를 사용하는데에 있어 어떤 비용이 들까요?

더보기
스레드는 유용하지만, 너무 많은 스레드를 생성하면 오히려 시스템 성능이 저하될 수 있습니다.
스레드가 많아질수록 메모리와 시스템 자원의 소모량도 증가하여 시스템이 불안정해질 수 있습니다.
특히 스레드는 스택 메모리를 할당받기 때문에 스레드가 많아질수록 메모리 소모가 커집니다.
뿐만 아니라 스레드가 많아지면 CPU는 여러 스레드 사이를 빠르게 전환해주는 문맥 교환 과정을 거치게 됩니다.
이 과정에서 CPU 오버헤드가 발생하여 오히려 성능이 저하될 수 있습니다.

0이 들어있는 변수에 10개의 스레드가 동시에 접근해서 ++ 연산을 하면 우리 예상과 다르게 10이 나오지 않습니다. 왜 그럴까요?

더보기
결과가 우리의 예상과 다르게 나오는 이유는 경쟁 조건 때문입니다.

경쟁조건은 여러 스레드가 동시에 공유된 자원에 접근하여 읽거나 쓰는 과정에서 발생하는 문제입니다.

++연산은 3단계로 이루어집니다.

1. 변수의 값을 읽고

2. 해당 값에 1을 더하고

3. 메모리에 더한 값을 덮어씁니다.

이러한 과정을 10개의 스레드가 수행하는 과정에서 연산들이 원자적으로 이뤄지지 않기 때문에 한 스레드가 연산 후 값을 반영하여도 다른 스레드의 연산결과에 의해 무시되어 결국 10보다 작은 값의 결과를 얻게 됩니다.

 

이러한 문제를 해결하기 위해서는 동기화가 필요합니다.

스레드들이 동시에 변수를 수정하지 못하도록 ++연산을 수행하는 코드를 synchronized 블록으로 감싸거나 Atomic 클래스를 사용하여 원자적 연산을 보장해야 합니다.

 

synchronized 키워드는 특정 코드 블록이나 메서드에 대해 동시에 하나의 스레드만 접근할 수 있도록 락(lock)을 걸어줍니다. 이 락은 특정 객체에 대해 걸리며, 다른 스레드들이 같은 객체에 대해 락이 걸린 synchronized 블록이나 메서드에 접근하려고 할 때, 해당 스레드들은 락이 풀릴 때까지 기다려야 합니다.

자바에서 동시성과 관련된 예약어를 모두 말씀해주세요.

더보기

자바에서 동시성과 관련하여 주로 사용되는 키워드는 synchronized와 volatile 등이 있습니다.
synchronized: 메서드나 블록을 동기화하여, 동시에 오직 하나의 스레드만 접근할 수 있도록 제한합니다.
volatile
: 변수의 값을 메인 메모리에서 직접 읽고 쓰도록 하여, 스레드 간에 변수의 일관성을 보장합니다.

 

volatile 키워드는 어떤 키워드일까요?

더보기

자바에서 변수의 값이 여러 스레드 간에 일관성 있게 유지되도록 하는 데 사용됩니다.
자바 메모리 모델에서는 각 스레드가 자신의 캐시 메모리를 사용해 변수를 읽고 쓸 수 있습니다. 이 때문에 한 스레드에서 변경한 변수 값이 다른 스레드에서 보이지 않는 문제가 발생할 수 있습니다.

volatile 키워드는 이러한 문제를 해결합니다.

  • 메인 메모리 접근: volatile로 선언된 변수는 항상 메인 메모리에서 읽고 쓰도록 강제됩니다. 스레드가 이 변수를 읽을 때마다 메인 메모리에서 값을 가져오고, 쓸 때마다 메인 메모리에 즉시 반영합니다.
  • 가시성 보장: 이를 통해, 어떤 스레드가 volatile 변수를 수정하면, 다른 모든 스레드는 즉시 그 변경된 값을 볼 수 있습니다.
가시성이란?
멀티스레드 환경에서 각각의 스레드가 공유자원에 대해서 모두 같은 상태를 바라보고 있는 것을 의미한다.

사용 사례

volatile 키워드는 주로 다음과 같은 상황에서 사용됩니다:

  • 플래그 변수: 여러 스레드가 접근하는 상태 플래그나 플래그 체크를 위해 사용됩니다.
  • Double-checked Locking: 싱글톤 패턴에서 인스턴스를 생성할 때 volatile을 사용해 인스턴스 변수의 가시성을 보장합니다.

이렇게 volatile 키워드는 자바에서 스레드 간 변수의 가시성을 보장하기 위한 중요한 도구입니다.


 

Blocking IO Non-Blocking IO 의 차이를 말씀해주세요.

더보기
 

블로킹 I/O와 논블로킹 I/O는 데이터를 읽고 쓰는 방식에서 중요한 차이가 있습니다.

  • 블로킹 I/O: I/O 작업이 완료될 때까지 프로그램 실행이 멈추며, 간단하고 직관적이지만 리소스 활용도가 낮아 성능 저하를 초래할 수 있습니다. 특히, 서버 환경에서는 많은 요청을 처리하는 데 비효율적입니다.
  • 논블로킹 I/O: I/O 작업이 완료되지 않아도 프로그램이 다른 작업을 계속할 수 있도록 하며, 리소스 활용도를 높이고 시스템 응답성을 개선합니다. 이 방식은 복잡하지만 고성능 서버와 멀티태스킹 애플리케이션에서 효과적입니다.

논블로킹 I/O는 복잡성을 증가시킬 수 있지만, 적절한 방법으로 관리하면 높은 효율성을 제공할 수 있습니다.

 

Blocking IO 가 일어나면 스레드에는 무슨 일이 생길까요?

더보기

Blocking IO가 발생하면, 해당 I/O 작업을 수행하는 스레드는 입출력 작업이 완료될 때까지 멈추게 됩니다.
이 스레드는 I/O 작업이 완료될 때까지 대기 상태에 들어가며, 그동안 다른 작업을 수행할 수 없습니다.

이 상태에서 스레드는 CPU 자원을 사용하지 않으며, 운영체제의 스케줄러에 의해 다른 스레드나 프로세스에 CPU 자원이 할당됩니다. 하지만 블로킹된 스레드는 I/O 작업이 완료될 때까지 계속 대기 상태에 있기 때문에, 해당 스레드에서 다른 작업을 처리할 수 없는 상황이 됩니다.

결과적으로, 블로킹 I/O가 발생하면 스레드가 비효율적으로 사용될 수 있으며, 특히 많은 수의 블로킹 I/O 작업이 동시에 발생하는 경우, 시스템 자원(예: 스레드 수)이 고갈되어 성능 저하가 발생할 수 있습니다.

스레드가 멈춰있는 동안 CPU는 어떻게 될까요?

더보기
 

스레드가 멈춰있는 동안, 즉 스레드가 작업을 하지 않고 대기 상태에 있을 때,
CPU는 그 스레드에 할당된 시간을 낭비하지 않고 다른 스레드나 프로세스의 작업을 처리합니다.
이 과정에서 운영체제의 스케줄러가 작동하여 CPU 자원을 효율적으로 활용합니다.

스레드가 멈춰있는 동안 CPU는 다른 작업을 실행하는 데 사용될 수 있습니다.
예를 들어, 다른 활성 스레드나 프로세스에 CPU 자원을 할당해 실행하는 식입니다. 이 방식으로 CPU는 가능한 한 쉬지 않고 계속해서 일을 하도록 유지됩니다.

CPU가 쉬는 것을 막으려면 어떻게 해야할까요?

더보기

CPU가 쉬는 것을 막으려면, Blocking IONon-Blocking IO로 전환하거나, 비동기 프로그래밍을 도입하는 방법이 효과적입니다.

  • Non-Blocking IO 사용:
    • Non-Blocking IO는 I/O 작업이 완료될 때까지 스레드를 대기 상태로 두지 않고, 즉시 제어권을 반환합니다. 이를 통해 CPU는 다른 작업을 계속 수행할 수 있습니다.
  • 비동기 프로그래밍:
    • 비동기 프로그래밍을 사용하면 I/O 작업이 완료될 때까지 스레드를 차단하지 않고, 작업이 완료되면 콜백 함수나 Future/Promise와 같은 메커니즘을 통해 처리할 수 있습니다. 이렇게 하면 CPU가 다른 작업을 처리할 수 있도록 유연하게 사용할 수 있습니다.

 

스레드를 늘리면 단점이 무엇일까요?

더보기

메모리 사용 증가:

  • 스레드마다 메모리가 할당되는데, 특히 스택 메모리가 스레드마다 필요합니다.
  • 스레드 수가 많아질수록 메모리 사용량이 크게 증가해 시스템의 메모리 자원이 빠르게 소진될 수 있습니다.

컨텍스트 스위칭 비용 증가:

  • 컨텍스트 스위칭은 CPU가 현재 실행 중인 스레드의 상태를 저장하고, 다른 스레드로 전환하는 과정입니다.
  • 스레드 수가 많아지면 컨텍스트 스위칭이 자주 발생해, 이로 인해 CPU 사이클이 낭비되고 성능이 저하될 수 있습니다.

스레드 관리 복잡성 증가:

  • 많은 스레드를 효율적으로 관리하기가 어려워지고, 잘못된 스레드 관리는 **데드락(Deadlock)**이나 경쟁 조건(Race Condition) 같은 동시성 문제를 초래할 수 있습니다.

CPU 오버헤드:

  • 스레드가 너무 많으면 CPU가 모든 스레드를 처리하려고 하다 보니 각 스레드에 할당되는 시간(타임 슬라이스)이 줄어들어, 실제 작업을 처리하는 대신 스레드 전환에 더 많은 시간이 소요될 수 있습니다.

응답성 저하:

  • 스레드 수가 과도하게 많으면 각 스레드에 할당되는 CPU 시간이 줄어들어 개별 작업의 응답 속도가 느려질 수 있습니다. 결국, 전체 시스템의 응답성이 떨어질 수 있습니다.

Non-Blocking IO는 CPU 활용률이 어떨까요?

더보기
Non-Blocking I/O는 CPU 활용률을 극대화할 수 있으며, 시스템 자원을 더 효율적으로 사용할 수 있는 방법 중 하나입니다. I/O 작업 중 발생하는 대기 시간을 줄이고, CPU가 계속해서 유용한 작업을 수행하도록 만들어, 전반적인 시스템 성능을 향상시킬 수 있습니다.

 

Serializable 은 무엇일까요?

더보기

직렬화(Serialization)는 객체를 바이트 스트림으로 변환하여, 파일에 저장하거나 네트워크로 전송할 수 있게 만드는 과정입니다. 이 바이트 스트림은 나중에 역직렬화(Deserialization) 과정을 통해 다시 객체로 복원할 수 있습니다.

'Java' 카테고리의 다른 글

자바 면접 스터디(2)  (0) 2024.10.11
자바 면접 스터디(1)  (0) 2024.10.04
Stream  (0) 2024.08.07
thread pool  (3) 2024.08.06
hashcode(), equals()  (0) 2024.08.04