dev-course day15

2rlokr·2025년 3월 24일

dev-course

목록 보기
15/43
post-thumbnail

오늘 배운 것

쿠키와 세션

HTTP 프로토콜의 특징

HTTP의 비연결성 (Connectionless)

HTTP 프로토콜은 클라이언트에서 서버에 요청(Request)을 보내면 서버는 클라이언트에 응답(Response)을 하고 연결을 끊는 특징을 가지고 있다.

서버는 요청에 대한 응답을 끝내면 더 이상 그 연결에 대해 기억하거나 유지하지 않는다.

HTTP의 무상태성 (Stateless)

HTTP 통신은 연결을 끊는 순간 클라이언트와 서버의 통신이 끝나며 서버는 클라이언트의 상태 정보를 유지하지 않는 특징이 있다. 필요한 상태에 대한 정보를 클라이언트가 가지고 온다.

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

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

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

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

사용자의 요청에 대한 정보를 기록하기 위한 수단으로, 웹 브라우저에 저장되는 작은 데이터 파일이다.

정의
서버가 클라이언트(사용자의 브라우저)에게 정보를 저장하도록 요청하고, 이후 클라이언트가 다시 서버에 요청을 보낼 때 그 정보를 자동으로 포함시켜서 보낸다.

저장 위치
사용자의 웹 브라우저에 저장된다. 사용자가 브라우저를 닫거나 설정에 따라 쿠키를 삭제하지 않는다면, 쿠키는 장기적으로 저장될 수 있다.

용도

  • 사용자 로그인 상태 유지
  • 사이트 방문 기록 추적
  • 사용자 맞춤형 설정(언어, 테마 등) 저장
  • 광고 및 분석 데이터 수집

리소스
클라이언트에 저장되고, 클라이언트의 메모리를 사용하기 때문에 서버 자원을 사용하지 않는다.

세션 (Session)

서버 측에서 관리되는 사용자와의 연결 정보를 의미한다.

정의
사용자가 서버에 접속할 때마다 서버는 고유한 세션 ID를 생성하고, 그 ID를 클라이언트에게 전달한다. 클라이언트는 이후 요청을 보낼 때마다 이 세션 ID를 포함시켜서 서버에 전달하고, 서버는 해당 세션 ID를 통해 사용자 정보를 식별한다. (세션은 일반적으로 로그인한 사용자와 관련된 정보를 저장하는 데 사용된다.)

저장 위치
서버의 메모리나 데이터베이스에 저장된다. 세션 ID는 클라이언트의 쿠키에 저장되거나 URL 파라미터로 전송된다.

용도

  • 사용자 로그인 정보 유지
  • 사용자의 장기적인 상호작용(쇼핑 카트 정보 등)을 저장
  • 보안이 중요한 정보를 저장할 때 사용

리소스
서버에 저장되고, 서버의 메모리로 로딩되기 때문에 세션이 생길 때마다 리소스를 차지한다.

싱글톤 패턴 (Singleton Pattern)

싱글톤 패턴

싱글톤 패턴은 OOP에서 특정 클래스가 단 하나만의 인스턴스를 생성하여 사용하기 위한 패턴

여러 개의 스레드가 공유해야 할 정보들을 하나의 인스턴스에 담아놓고, 싱글턴 패턴으로 설계하면 모든 스레드에 공통적으로 적용할 수 있다.

1. 클래스 로딩 시 인스턴스 초기화 (Eager Initialization)

public class Singleton {

	// 클래스 로딩 시 인스턴스를 하나 생성
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() { // private로 생성자를 선언 - 외부에서 인스턴스를 생성하지 못한다.

    }

    public static Singleton getINSTANCE() { // 인스턴스를 반환하는 메서드
        return INSTANCE;
    }

}
  • 인스턴스가 클래스 로딩 시에 미리 생성된다.
// Thread-safe한지 테스트한 결과
Thread 1 - Singleton@15db9742
Thread 2 - Singleton@15db9742
  • INSTANCE클래스 로딩 시 초기화되므로 멀티스레드 환경에서도 한 번만 생성된다.
  • 클래스 로딩 시 INSTANCE 객체가 생성되므로, 멀티스레드 환경에서도 한 번만 객체가 생성된다.
  • 출력된 객체의 해시코드가 동일하므로, 두 개의 스레드가 같은 객체를 참조하는 것을 알 수 있다.

결론
싱글톤 인스턴스가 멀티스레드 환경에서도 안전하게 동작한다. 클래스 인스턴스가 한 번만 초기화되는 방식을 사용하여 스레드 안정성을 보장한다.

2. null 체크 후 인스턴스 생성 (Lazy Initialization)

public class Singleton {

    private static Singleton instance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        System.out.println("Singleton.getInstance");
        if ( instance == null ) { 
            System.out.println(Thread.currentThread().getName() + " - 인스턴스 생성!");
            instance = new Singleton();
        }

        return instance;
    }
}
  • instancenull일 경우에만 인스턴스를 생성한다.
  • 최초 호출 시 인스턴스를 생성하며, 이후에는 같은 인스턴스를 반환한다.
  • Singleton을 호출하지 않을 수도 있는데, 클래스 로딩 시 인스턴스를 생성하는 것보다 처음 호출될 때 초기화해주는 방식으로 메모리 낭비를 방지한다.
  • 이 구현은 멀티스레드 환경에서 문제가 발생할 수 있다. 여러 스레드가 동시에 getInstance()를 호출할 경우 ,instancenull인 상태에서 동시에 객체를 생성하려는 시도가 발생할 수 있다.
// 멀티 스레드 환경에서 테스트 실행결과
Thread 1 - Singleton@15db9742
Thread 2 - Singleton@6d06d69c
  • 두 개의 스레드가 각각 다른 인스턴스를 생성했다.
  • 동시성 문제로 인해 두 개의 인스턴스가 생성된 것이다.

결론

  • 위의 Singleton 클래스는 멀티스레드 환경에서 안전하지 않으며, 두 개의 인스턴스가 생성되는 문제가 발생할 수 있다.
  • 이 문제는 동기화를 추가하거나, 더블 체크 잠금 (Double-Checked Locking) 방식을 사용하여 해결할 수 있다.

3. 더블 체크 잠금 (Double-Checked Locking)

매번 synchronized 동기화를 실행하는 것이 문제라면, 최초 초기화할 때만 적용하고 이미 만들어진 인스턴스를 반환할 때는 사용하지 않도록 하는 기법이다. 이 때 인스턴스 필드에 volatile 키워드를 붙여주어야 I/O 불일치 문제를 해결할 수 있다.

volatile 키워드

자바 변수를 메인 메모리에 저장하겠다는 것을 명시한다. 매번 변수의 값을 읽을 때마다 CPU의 cache가 아닌 Main Memory에서 읽는 것이다.

하나의 변수를 여러 스레드에서 사용할 때 사용하는 키워드이다.
volatile은 CPU cache에 저장된 값을 읽는 것보다는 성능이 안 좋지만, 컴파일러에게 해당 데이터에 대해 멀티스레드로 접근하고 있음을 알려주어 해당 변수의 값의 읽기, 쓰기에 대해 동기화를 보장하고 각 스레드가 캐시된 값을 사용하지 않게 된다. 이는 변경된 값이 다른 스레드에 즉시 반영되도록 한다.

그렇다면 synchronized 와의 차이점은?

  • synchronized : 행위(메서드 및 블럭)에 대한 동기화
  • volatile : 행위의 타겟(변수)에 대한 동기화

그렇다면 volatile만으로 동기화가 보장되나?

volatile은 단순히 읽기/쓰기 작업에 대해서만 일관성을 보장한다. 즉, 복합적인 작업(예: i++ 또는 i = i + 1 같은 연산)을 보장하지 않는다. 이런 경우에는 synchronized와 같은 다른 동기화 메커니즘이 필요하다.

synchronized 키워드

동기화를 위해 사용되는 자바 키워드이다. 여러 스레드가 동시에 공유 자원에 접근하는 것을 제어하고, 데이터 일관성을 보장하기 위해 사용된다.

메서드 동기화

public synchronized void someMethod() {
    // 코드
}

메서드 전체가 동기화되며, 객체 레벨에서의 동기화로 this 객체에 대한 락이 걸린다.

블록 동기화

public void someMethod() {
    synchronized (this) {
        // 동기화할 코드 블록
    }
}

객체나 클래스에 대해 동기화를 지정할 수 있다.

단점

  • 동기화는 락을 획득하고 해제하는 과정에서 오버헤드가 발생하기 때문에, 성능에 영향을 줄 수 있다.
  • 여러 스레드가 서로 자원을 기다리면서 교착 상태(Deadlock)에 빠질 수 있다. 스레드가 서로 서로의 자원을 기다리며 무한 대기 상태에 빠질 수 있다.

DCL (Double-Checked Locking)

public class Singleton {

    private volatile static Singleton instance; // 자바 변수를 CPU cache에 저장 x, 메인 메모리에 저장

    private Singleton() {

    }

    public static Singleton getInstance() {
        if (instance == null) { // 첫 번째 null 체크
            synchronized (Singleton.class) { 
                if (instance == null) { // 두 번째 null 체크 (double-checked locking)
                    System.out.println(Thread.currentThread().getName() + " - 인스턴스 생성!");
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

}
  • volatile 키워드를 사용해 읽기/쓰기에 대한 일관성을 보장한다.
  • 첫 번째 null 체크는 첫 번째 스레드가 인스턴스를 생성할 때 실행된다.
  • 동기화된 블록(synchronized)을 사용하여, 인스턴스 생성 코드가 여러 스레드에서 동시에 실행되지 않도록 보장한다.
  • 더블 체크 잠금 : 두 번째 null체크는 동기화된 코드 블록 안에서 다시 한 번 확인한다. 동기화된 블록 내에서 인스턴스를 한 번만 생성하도록 보장한다.
테스트 결과
Thread-0 - 인스턴스 생성!
Thread-1 - Singleton@15db9742
Thread-0 - Singleton@15db9742
  • thread-0에서 처음 인스턴스를 생성하여, "인스턴스 생성!" 메시지가 출력된다.
  • 두 스레드가 동일한 싱글톤 인스턴스를 참조하기 때문에, 해시코드가 동일하다.

결론
동기화더블 체크 잠금으로 Singleton 클래스는 멀티스레드 환경에서 안전하게 동작하도록 설계되었다. volatile 키워드가 synchronized 블록을 적절히 사용하여, 동시성 문제를 해결하고, 인스턴스가 중복 생성되지 않도록 보장한다.
더블 체크 잠금 기법을 사용하여 첫 번째 null 체크에서는 동기화 없이 빠르게 진행되고, 처음 실제 객체 생성 시에만(인스턴스가 생성되지 않았을 경우에만) 동기화를 하여 동기화 비용을 줄일 수 있고, 성능을 최적화한다.

정기평가에서 놓쳤던 것 정리

람다식

람다식의 매개변수가 1개일 때는 괄호를 생략할 수 있으나, 0개나 2개 이상일 때는 괄호를 해주어야 한다.

case 1

int x = 10;
Runnable increment = () -> {
		x += 1;  
};
increment.run();
  • 자바는 람다식 내에서 외부 변수를 수정할 수 없기 때문에 컴파일 오류가 발생한다.

case 2

int x = 10;
Runnable r = () -> {
		x = 20;  // 람다식 내에서 x 값을 변경
		System.out.println("x inside lambda: " + x);
};
r.run();
System.out.println("x outside lambda: " + x);  // 원본 x는 변하지 않음
  • 람다식 내에서 변수의 값을 수정하더라도 원본 변수에는 영향을 미치지 않는다.

case 3

Person p = new Person("John");
Runnable r = () -> {
		p.name = "Alice";  // 람다식 내에서 객체 필드 수정
		System.out.println("Inside lambda: " + p.name);
};
r.run();
System.out.println("Outside lambda: " + p.name);  // 원본 객체도 변경됨
  • 람다식 내에서 참조 타입 객체의 필드를 수정하면, 원본 객체가 변경된다.
  • 배열의 경우에도, 외부 변수인 배열은 참조 타입이므로 배열 내 값을 수정할 수 있습니다.

case 4

int x = 10;
Runnable r = (int n) -> {
		n = 20;  // 컴파일 에러
		System.out.println(n);
};
r.run();
  • 람다식에서 매개변수 자체를 변경하는 것은 불가능하다. 람다식의 매개변수는 읽기 전용이므로, 매개변수를 새로운 값으로 재할당할 수 없다.

case 5

public class LambdaExample {
    public static void main(String[] args) {
        Runnable r = () -> {
            int x = 10;  // 람다식 내에서 로컬 변수 선언
            System.out.println("x inside lambda: " + x);
        };
        r.run();
    }
}
  • 람다식 내부에서 선언된 변수는 람다식 내부에서만 유효하다. 즉, 람다식 외부에서는 그 변수를 사용할 수 없다. 이 변수는 람다식의 로컬 변수이기 때문에 람다식이 실행되는 동안만 존재한다.

INNER JOIN

두 테이블에서 조건에 맞는 값이 있는 경우만 결과로 반환한다. 즉, 두 테이블에 모두 일치하는 데이터가 있을 때만 출력된다.

students 테이블

student_idstudent_namecourse_id
1John101
2Alice102
3Bob103
4Charlie104
5DavidNULL

courses 테이블

course_idcourse_name
101Math
102English
103Science
104History
105Art
SELECT students.student_name, courses.course_name
FROM students
INNER JOIN courses
ON students.course_id = courses.course_id;

다음과 같은 sql문을 실행했을 때, ON의 조건에 따라 두 테이블의 일치하는 데이터만 결과로 가져온다.

결과

student_namecourse_name
JohnMath
AliceEnglish
BobScience
CharlieHistory
  • INNER JOINstudents 테이블과 courses 테이블에서 course_id가 일치하는 데이터만 결과로 반환

LEFT JOIN

왼쪽 테이블의 모든 행을 반환하고, 오른쪽 테이블에서 일치하는 값이 없으면 NULL로 반환한다. 즉, 왼쪽 테이블에 있는 모든 데이터를 보여주며, 오른쪽 테이블에 일치하는 값이 없으면 NULL로 표시한다.

SELECT students.student_name, courses.course_name
FROM students
LEFT JOIN courses
ON students.course_id = courses.course_id;

똑같이 course_id로 비교하는데, 왼쪽 테이블인 students 는 모두 가져오고 students 테이블의 course_id를 기준으로 courses에서 일치하는 데이터를 가져온다.

결과

student_namecourse_name
JohnMath
AliceEnglish
BobScience
CharlieHistory
DavidNULL
  • LEFT JOINstudents 테이블의 모든 행을 출력하고, courses 테이블에서 일치하는 course_id를 찾는다.

팀 활동 후기

저번 주 금요일에 팀활동을 빠졌더니 팀원들을 되게 오랜만에 보는 것 같아서 반가웠다 ㅋㅋ 매일 그 2시간이 신기하네요.. 내적 친밀감이 이런 건가? 오늘은 스크럼 이외에 스몰톡도 좀 나눠서 좋았다. 하지만, 이제 그 스몰톡이 스을 정리되고 이야기를 쭉 이어갈 정도는 아니라 다음으로 진행해야 하는데 그 때가 좀 어색한 듯
"(정적) ..."
"자 그럼 이제 다음으로 넘어가서.. 저는 ~~~(스크럼 이야기)" ㅋㅋㅋㅋㅋ
아무튼 오랜만에 팀원들 봐서 반가웠다 + 스몰톡 즐거웠다 이런 느낌

느낀점

오늘 실습을 마무리 했다. 과제를 하면서 고민되는 부분이 있었는데 싱글톤 패턴으로 해결할 수 있다는 걸 배워서 유익했던 것 같다.

어제 기도 좀 빨리고..ㅎ 스터디 준비로 새벽 4시에 자서 그런가 오늘 정말 피곤했다.. 그래서 서서 수업을 들었다. 다행히 서서 들으니 잠은 안 왔다.

오늘 정기평가 시험을 쳤다. 사실 치는 동안만 해도 몇 문제 헷갈리네.. 했었는데 제출 후 점수를 보고 깜짝 놀랐다... 여기서 1차 충격..

오늘 수업 끝나고 스터디 하면서 오늘 본 시험에 대해서도 얘기를 나누었는데, 다들 잘 본 것 같았다. (일단 나처럼 충격먹은 사람은 없어보였다. 다들 chill..) 모르겠는 문제가 있으면 물어보라고 하셔서 질문을 하기 시작했는데, 질문하는 사람은 it's me 나 뿐이었고,, 내가 물어보면 다들 대답해주셨다... 나만 몰랐구나.. 하 뭔가 속상쓰했다.. 2차 충격.. 그렇지만, 스터디의 의미가 이런 거 아니겠나.., 모르는 거 인정하기로 했잖아!! 하면서 모르는 거 다 질문하긴 했는데 그냥 그 이후의 기분이.. 조금 속상했다. 요새 하루종일 모니터 앞에서 앉아있는데 아직도 이렇게 부족하구나 싶었다.

왜인지는 모르겠지만 온몸의 관절이 어제부터 아파서리,, 힘도 없고 :(( 오늘은 그런 날이네.. 참.. 힘이 안 나는 날이다.

그래도 오늘 JOIN 이해해서 그건 좋네

0개의 댓글