데브코스 15일차 TIL

Heesu Song·2025년 3월 24일

데브코스 - 백엔드

목록 보기
11/32
post-thumbnail

이번 주말에는 문래랑 노들섬에 갔다 왔다. 날이 좋아져서 그런지 사람이 정말 많았고, 바람이 선선하게 불어서 이제 봄이 가까워지고 있다는게 실감이났다. 노들섬에서는 특별한 것 없이 친구랑 걸어 다니면서 떠들기만 했는데, 그 순간이 너무 행복하게 느껴졌다.
데브코스를 시작하고 힘든 부분도 많았지만 확실히 좋은점도 생겼는데, 공부와 할일의 체계가 잡혔다는 점과, 무엇보다 일상의 이런 작은 행복들이 너무나 소중해 졌다는 거… (왜냐면 평일이 헬이니까)

다시 한 주가 시작 됐지만 열심히 살다 보면 또 주말이 오니까 화이팅!
(그리고 월요일은 참 악랄한 요일이다… 한 주의 시작인데 야구까지 안한다니)

과제 - Java

  1. 싱글톤 패턴 도입
  2. 각자의 인스턴스가 싱글톤 객체를 나누어 받아야 된다.

1. 싱글톤 패턴

‘나’를 한번만 생성해 꺼내서 사용함

→ 같은 클래스로부터 여러개의 객체를 만들지 못하고 오직 하나의 객체만 유지 된다.

public class Singleton{

	private static final Singleton INSTANCE = new Singleton();
	
	private Singleton(){
	}
	
	public static Singleton getInstance(){
		return INSTANCE;
	}
	
}
  • 두 개의 싱글톤 객체를 생성해서 Thread-safe인지 테스트 해보면, 두 객체의 해시 값이 같은걸 볼 수 있다. →Thread-safe 하다면 하나의 인스턴스만 공유된다.
  • 클래스가 로드 될때 객체가 사용 되지 않아도 무조건 함께 생성된다 → 메모리 낭비 → 인스턴스가 null이면 새 객체를 생성해주고 아니라면 이미 존재하는 인스턴스를 반환해주면 된다. → 🌱Spring에서 이 문제를 해결 - 자주 사용되는 객체를 매번 생성하지 않고 재사용하기 때문에 메모리 낭비를 줄이 수 있다.

volatile (동기화 보장)

  • 자바에서 멀티스레드 환경에서 변수의 가시성을 보장하기 위해 사용하는 키워드
  • CPU 캐시 대신 항상 메인 메모리(RAM)에서 값을 읽고 쓰도록 강제하는 역할

volatile이 필요한 이유
각 스레드는 메인 메모리 로부터 값을 복사해 CPU 캐시 에 저장하여 작업한다. CPU 가 2개 이상이라면 멀티 스레드 환경에서 각 스레드는 서로 다른 CPU 에서 동작하고 있으며 이는 각 스레드가 같은 변수에 대해 읽기, 쓰기 동작을 수행할 시 각자의 CPU 캐시 에 메인 메모리 의 값과 다른 값을 갖고 있을 수 있게 된다.

하지만 자바에서 어떠한 변수에 volatile 키워드를 붙이면 해당 변수는 모든 읽기와 쓰기 작업이 CPU 캐시가 아닌 메인 메모리 에서 이루어지게 되고 이로써 해당 변수 값에 대해 가시성 을 보장할 수 있다.

  1. 가시성(Visibility) 보장
  • volatile 변수를 읽으면 항상 메인 메모리에서 값을 가져온다.
  • 다른 스레드가 변경한 값을 즉시 확인 가능
  1. 재정렬(Reordering) 방지
  • 자바 컴파일러와 CPU는 최적화를 위해 명령어 순서를 바꾸는데, volatile을 사용하면 명령어 순서 변경을 방지

synchronized

  • 멀티스레드 환경에서 한 번에 하나의 스레드만 특정 코드 블록이나 메서드를 실행할 수 있도록 보장하는 키워드
  • 동기화(Blocking)를 통해 원자성을 보장하고, Race Condition(경쟁 상태) 문제를 해결하는 역할을 한다.

synchronized가 필요한 이유

멀티스레드 환경에서는 여러 스레드가 변수나 컬렉션과 같은 공유 자원에 동시에 접근하면 문제가 발생할 수 있다.

→ synchronized를 이용하면 한번에 하나의 스레드만 접근이 가능해진다.(원자성 보장)

  1. 메서드 전체를 동기화
public synchronized void increment() {
       count++;
    }
  1. 특정 코드 블록만 동기화
public void increment() {
    synchronized (this) {
        count++;
    }
}
  1. 클래스 단위로 동기화
public static synchronized void staticMethod() {
    // 클래스 전체에서 한 번에 하나의 스레드만 실행 가능
}
  • static synchronized는 클래스 레벨의 락을 사용 → 같은 클래스의 모든 인스턴스에 적용 된다.
  1. 특정 객체를 동기화
public void increment() {
    synchronized (Counter.class) { // 클래스 단위 락
        count++;
    }
}
  • Counter.class에 대한 클래스 단위의 락을 사용 (static 필드 보호 시 유용)

싱글톤 패턴에서 volatile과 synchronized의 역할

싱글톤 패턴을 구현할 때 멀티스레드 환경에서 인스턴스가 두 개 생성되는 문제를 방지하려면 volatile과 synchronized를 적절히 사용해야 한다.

synchronized만 사용했을 경우

public class Singleton {
	private static Singleton instance;
	
	private Singleton(){}
	
	public static synchronized Singleton getInstance() {
		if(instance == null) { // 1번
			instance = new Singleton(); // 2번
		}
		return instance;
	}
}

단일 쓰레드가 대상 메소드를 호출시작~종료까지 다른 쓰레드가 접근하지 못하도록 lock 을 하기 때문에 위와 같이 getInstance()메소드를 synchronized로 처리하면 멀티 쓰레드에서 동시 접근으로 인한 인스턴스 중복생성 문제는 해결되게 된다.

‼️

하지만, synchronized getinstance()의 경우 인스턴스를 리턴 받을 때마다 Thread 동기화 때문에 불필요하게 lock이 걸리게 되어 비용 낭비가 크다.

DCL(Double-Checked-Locking) + volatile 사용

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) { // 첫 번째 체크 (락 없이 검사)
            synchronized (Singleton.class) {
                if (instance == null) { // 두 번째 체크 (락 내부에서 검사)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

DCL 패턴을 사용하면 최초 한 번만 synchronized를 수행하므로 성능과 안정성을 모두 보장할 수 있다.

최적의 방법 = volatile + synchronized를 조합한 DCL 패턴 사용

❓DCL은 또 뭔가

Double-Checked-Locking

DCL(Double Checking Locking)은 ThreadSafe한 싱글톤 설계를 위한 방식 중 하나이다.

DCL Singleton

DCL singleton패턴은 getInstance() 내부에서 instance를 생성하는 경우만 부분적으로 synchronized 처리를 하여 생성과 획득을 분리한 획기적인 방법이다. 즉 인스턴스가 생성되어 있는지 확인해보고 인스턴스가 없는 경우 lock을 잡고 instance를 생성하는 방법이다.

→ 소스코드 논리적으로는 문제가 없지만 컴파일러에 따라서 재배치(reordering)문제를 야기한다.


쿠키(Cookie)와 세션(Session)을 사용하는 이유

쿠키와 세션은 HTTP 프로토콜의 특징이자 약점을 보완하기 위해 사용한다.

HTTP 프로토콜은 비연결지향과 무상태성이라는 특징을 가지고 있다.

따라서 서버와 클라이언트가 통신을 할 때 통신이 연속적으로 이어지지 않고 한 번 통신이 되면 끊어진다.

또한 통신이 끊어지면 상태정보가 유지되지 않기 때문에 매번 페이지를 이동할 때마다 로그인은 다시 하거나, 상품 선택 후 구매 페이지에서 선택한 상품의 정보가 없거나 하는 등의 문제가 발생할 수 있다.

이러한 문제를 해결하는 방법이 바로 쿠키와 세션이다.

Session

  • 브라우저가 종료되기 전까지 클라이언트의 요청을 유지하게 해주는 기술을 세션이라고 한다.
  • 웹 서버에 웹 컨테이너의 상태를 유지하기 위한 정보를 저장한다.
  • 웹 서버에 저장되는 쿠키(세션 쿠키 / session cookie)이다.
  • 각 클라이언트에 고유 세션 ID(Session ID)를 부여한다.

Session의 동작방식

  1. 클라이언트가 서버에 로그인 요청
  2. 서버는 클라이언트의 로그인 요청의 유효성을 확인하고(아이디와 비밀번호 검사) unique한 id를 session ID로 생성하여 저장한다.
  3. 서버가 응답할 때 응답헤더에 세션 ID를 쿠키에 추가하여 응답한다.
  4. 클라이언트는 이후 서버에 요청할 때 전달받은 세션 ID를 쿠키에 자동으로 요청 헤더에 추가하여 요청한다.
  5. 서버에서는 요청 헤더의 세션 ID 값을 저장된 세션저 장소에서 찾아보고 유효한지 확인 후 요청을 처리하고 응답한다.

Cookie

  • 브라우저 측에서 사용자 정보를 저장하기 위한 수단
  • Key-Value쌍으로 구성되어 있는 데이터 파일

Cookie의 동작방식

  1. 클라이언트가 서버에 로그인 요청
  2. 서버는 클라이언트의 로그인 요청의 유효성을 확인하고(아이디와 비밀번호 검사) 응답헤더에 set-cookie: 를 통해 쿠키를 추가하여 응답
  3. 클라이언트는 이후 서버에 요청할 때 전달받은 쿠키를 자동으로 요청헤더에 추가하여 요청한다. 헤더에 쿠키값을 자동으로 추가하여 주는데 이는 브라우저에서 처리해주는 작업이다.

쿠키의 기한이 정해져 있지 않고 명시적으로 지우지 않는다면 반 영구적으로 쿠키가 남아있다.

JSESSIONID (Cookie)

  • 사용자가 웹사이트에 처음 접속하면 서버가 새로운 세션을 생성하고, 그 세션을 식별할 고유한 ID(JSESSIONID)를 발급한다.
  • 이후 사용자가 같은 세션을 유지하면서 요청을 보낼 때, JSESSIONID를 기반으로 서버가 해당 사용자를 구별한다.
  • JSESSION은 Set-Cookie 헤더를 통해 응답해 포함시키거나, URL뒤에 붙여서 전달하는 방식이 있다.

 JSESSIONID vs JWT

 JSESSIONID는 서버 기반의 세션 식별자로 서버에서 상태를 유지해야 하지만, JWT는 토큰 기반 방식으로 클라이언트가 인증 정보를 유지하므로 무상태(Stateless) 방식이다.

특징JSESSIONID (세션 기반)JWT (토큰 기반)
저장 위치서버 세션 (메모리, DB)클라이언트 (쿠키, 로컬스토리지)
인증 방식상태 유지 (Stateful)상태 없음 (Stateless)
보안 이슈세션 하이재킹, 세션 고정 공격토큰 탈취 시 위험
확장성서버가 세션 관리 부담서버 부담 적음

맨날 뭘 알아갈때마다 느끼지만 세상에 참 어려운게 많은 것 같다…
진짜 너무 궁금한게 있는데 대체 이런건 처음에 누가 만들기 시작하는 걸까..?
그리고 왜 만드는걸까^^

profile
Abong_log

0개의 댓글