
HTTP 프로토콜은 클라이언트에서 서버에 요청(Request)을 보내면 서버는 클라이언트에 응답(Response)을 하고 연결을 끊는 특징을 가지고 있다.
서버는 요청에 대한 응답을 끝내면 더 이상 그 연결에 대해 기억하거나 유지하지 않는다.
HTTP 통신은 연결을 끊는 순간 클라이언트와 서버의 통신이 끝나며 서버는 클라이언트의 상태 정보를 유지하지 않는 특징이 있다. 필요한 상태에 대한 정보를 클라이언트가 가지고 온다.
쿠키와 세션은 HTTP 프로토콜의
Connectionless와Stateless특징을 보완하기 이해 사용한다.
예를 들어, 서버와 클라이언트 간의 통신이 끊어지면, 상태 정보가 유지되지 않기 때문에 매번 페이지를 이동할 때마다 로그인은 다시 하거나, 상품 선택 후 구매 페이지에서 선택한 상품의 정보가 없는 등의 문제가 발생할 수 있다.
이러한 문제를 해결하는 방법이 바로 쿠키와 세션이다.
사용자의 요청에 대한 정보를 기록하기 위한 수단으로, 웹 브라우저에 저장되는 작은 데이터 파일이다.
정의
서버가 클라이언트(사용자의 브라우저)에게 정보를 저장하도록 요청하고, 이후 클라이언트가 다시 서버에 요청을 보낼 때 그 정보를 자동으로 포함시켜서 보낸다.
저장 위치
사용자의 웹 브라우저에 저장된다. 사용자가 브라우저를 닫거나 설정에 따라 쿠키를 삭제하지 않는다면, 쿠키는 장기적으로 저장될 수 있다.
용도
리소스
클라이언트에 저장되고, 클라이언트의 메모리를 사용하기 때문에 서버 자원을 사용하지 않는다.
서버 측에서 관리되는 사용자와의 연결 정보를 의미한다.
정의
사용자가 서버에 접속할 때마다 서버는 고유한 세션 ID를 생성하고, 그 ID를 클라이언트에게 전달한다. 클라이언트는 이후 요청을 보낼 때마다 이 세션 ID를 포함시켜서 서버에 전달하고, 서버는 해당 세션 ID를 통해 사용자 정보를 식별한다. (세션은 일반적으로 로그인한 사용자와 관련된 정보를 저장하는 데 사용된다.)
저장 위치
서버의 메모리나 데이터베이스에 저장된다. 세션 ID는 클라이언트의 쿠키에 저장되거나 URL 파라미터로 전송된다.
용도
리소스
서버에 저장되고, 서버의 메모리로 로딩되기 때문에 세션이 생길 때마다 리소스를 차지한다.
싱글톤 패턴은 OOP에서 특정 클래스가 단 하나만의 인스턴스를 생성하여 사용하기 위한 패턴
여러 개의 스레드가 공유해야 할 정보들을 하나의 인스턴스에 담아놓고, 싱글턴 패턴으로 설계하면 모든 스레드에 공통적으로 적용할 수 있다.
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 객체가 생성되므로, 멀티스레드 환경에서도 한 번만 객체가 생성된다. 결론
싱글톤 인스턴스가 멀티스레드 환경에서도 안전하게 동작한다. 클래스 인스턴스가 한 번만 초기화되는 방식을 사용하여 스레드 안정성을 보장한다.
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;
}
}
instance 가 null일 경우에만 인스턴스를 생성한다. Singleton을 호출하지 않을 수도 있는데, 클래스 로딩 시 인스턴스를 생성하는 것보다 처음 호출될 때 초기화해주는 방식으로 메모리 낭비를 방지한다.instance가 null인 상태에서 동시에 객체를 생성하려는 시도가 발생할 수 있다. // 멀티 스레드 환경에서 테스트 실행결과
Thread 1 - Singleton@15db9742
Thread 2 - Singleton@6d06d69c
결론
Singleton 클래스는 멀티스레드 환경에서 안전하지 않으며, 두 개의 인스턴스가 생성되는 문제가 발생할 수 있다.매번
synchronized동기화를 실행하는 것이 문제라면, 최초 초기화할 때만 적용하고 이미 만들어진 인스턴스를 반환할 때는 사용하지 않도록 하는 기법이다. 이 때 인스턴스 필드에volatile키워드를 붙여주어야 I/O 불일치 문제를 해결할 수 있다.
자바 변수를 메인 메모리에 저장하겠다는 것을 명시한다. 매번 변수의 값을 읽을 때마다 CPU의 cache가 아닌 Main Memory에서 읽는 것이다.
하나의 변수를 여러 스레드에서 사용할 때 사용하는 키워드이다.
volatile은 CPU cache에 저장된 값을 읽는 것보다는 성능이 안 좋지만, 컴파일러에게 해당 데이터에 대해 멀티스레드로 접근하고 있음을 알려주어 해당 변수의 값의 읽기, 쓰기에 대해 동기화를 보장하고 각 스레드가 캐시된 값을 사용하지 않게 된다. 이는 변경된 값이 다른 스레드에 즉시 반영되도록 한다.
그렇다면 synchronized 와의 차이점은?
그렇다면 volatile만으로 동기화가 보장되나?
volatile은 단순히 읽기/쓰기 작업에 대해서만 일관성을 보장한다. 즉, 복합적인 작업(예: i++ 또는 i = i + 1 같은 연산)을 보장하지 않는다. 이런 경우에는 synchronized와 같은 다른 동기화 메커니즘이 필요하다.
동기화를 위해 사용되는 자바 키워드이다. 여러 스레드가 동시에 공유 자원에 접근하는 것을 제어하고, 데이터 일관성을 보장하기 위해 사용된다.
메서드 동기화
public synchronized void someMethod() {
// 코드
}
메서드 전체가 동기화되며, 객체 레벨에서의 동기화로 this 객체에 대한 락이 걸린다.
블록 동기화
public void someMethod() {
synchronized (this) {
// 동기화할 코드 블록
}
}
객체나 클래스에 대해 동기화를 지정할 수 있다.
단점
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 키워드를 사용해 읽기/쓰기에 대한 일관성을 보장한다.synchronized)을 사용하여, 인스턴스 생성 코드가 여러 스레드에서 동시에 실행되지 않도록 보장한다.테스트 결과
Thread-0 - 인스턴스 생성!
Thread-1 - Singleton@15db9742
Thread-0 - Singleton@15db9742
결론
동기화와 더블 체크 잠금으로 Singleton 클래스는 멀티스레드 환경에서 안전하게 동작하도록 설계되었다. volatile 키워드가 synchronized 블록을 적절히 사용하여, 동시성 문제를 해결하고, 인스턴스가 중복 생성되지 않도록 보장한다.
더블 체크 잠금 기법을 사용하여 첫 번째 null 체크에서는 동기화 없이 빠르게 진행되고, 처음 실제 객체 생성 시에만(인스턴스가 생성되지 않았을 경우에만) 동기화를 하여 동기화 비용을 줄일 수 있고, 성능을 최적화한다.
람다식의 매개변수가 1개일 때는 괄호를 생략할 수 있으나, 0개나 2개 이상일 때는 괄호를 해주어야 한다.
int x = 10;
Runnable increment = () -> {
x += 1;
};
increment.run();
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는 변하지 않음
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); // 원본 객체도 변경됨
int x = 10;
Runnable r = (int n) -> {
n = 20; // 컴파일 에러
System.out.println(n);
};
r.run();
public class LambdaExample {
public static void main(String[] args) {
Runnable r = () -> {
int x = 10; // 람다식 내에서 로컬 변수 선언
System.out.println("x inside lambda: " + x);
};
r.run();
}
}
두 테이블에서 조건에 맞는 값이 있는 경우만 결과로 반환한다. 즉, 두 테이블에 모두 일치하는 데이터가 있을 때만 출력된다.
| student_id | student_name | course_id |
|---|---|---|
| 1 | John | 101 |
| 2 | Alice | 102 |
| 3 | Bob | 103 |
| 4 | Charlie | 104 |
| 5 | David | NULL |
| course_id | course_name |
|---|---|
| 101 | Math |
| 102 | English |
| 103 | Science |
| 104 | History |
| 105 | Art |
SELECT students.student_name, courses.course_name
FROM students
INNER JOIN courses
ON students.course_id = courses.course_id;
다음과 같은 sql문을 실행했을 때, ON의 조건에 따라 두 테이블의 일치하는 데이터만 결과로 가져온다.
결과
| student_name | course_name |
|---|---|
| John | Math |
| Alice | English |
| Bob | Science |
| Charlie | History |
students 테이블과 courses 테이블에서 course_id가 일치하는 데이터만 결과로 반환왼쪽 테이블의 모든 행을 반환하고, 오른쪽 테이블에서 일치하는 값이 없으면 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_name | course_name |
|---|---|
| John | Math |
| Alice | English |
| Bob | Science |
| Charlie | History |
| David | NULL |
students 테이블의 모든 행을 출력하고, courses 테이블에서 일치하는 course_id를 찾는다.저번 주 금요일에 팀활동을 빠졌더니 팀원들을 되게 오랜만에 보는 것 같아서 반가웠다 ㅋㅋ 매일 그 2시간이 신기하네요.. 내적 친밀감이 이런 건가? 오늘은 스크럼 이외에 스몰톡도 좀 나눠서 좋았다. 하지만, 이제 그 스몰톡이 스을 정리되고 이야기를 쭉 이어갈 정도는 아니라 다음으로 진행해야 하는데 그 때가 좀 어색한 듯
"(정적) ..."
"자 그럼 이제 다음으로 넘어가서.. 저는 ~~~(스크럼 이야기)" ㅋㅋㅋㅋㅋ
아무튼 오랜만에 팀원들 봐서 반가웠다 + 스몰톡 즐거웠다 이런 느낌
오늘 실습을 마무리 했다. 과제를 하면서 고민되는 부분이 있었는데 싱글톤 패턴으로 해결할 수 있다는 걸 배워서 유익했던 것 같다.
어제 기도 좀 빨리고..ㅎ 스터디 준비로 새벽 4시에 자서 그런가 오늘 정말 피곤했다.. 그래서 서서 수업을 들었다. 다행히 서서 들으니 잠은 안 왔다.
오늘 정기평가 시험을 쳤다. 사실 치는 동안만 해도 몇 문제 헷갈리네.. 했었는데 제출 후 점수를 보고 깜짝 놀랐다... 여기서 1차 충격..
오늘 수업 끝나고 스터디 하면서 오늘 본 시험에 대해서도 얘기를 나누었는데, 다들 잘 본 것 같았다. (일단 나처럼 충격먹은 사람은 없어보였다. 다들 chill..) 모르겠는 문제가 있으면 물어보라고 하셔서 질문을 하기 시작했는데, 질문하는 사람은 it's me 나 뿐이었고,, 내가 물어보면 다들 대답해주셨다... 나만 몰랐구나.. 하 뭔가 속상쓰했다.. 2차 충격.. 그렇지만, 스터디의 의미가 이런 거 아니겠나.., 모르는 거 인정하기로 했잖아!! 하면서 모르는 거 다 질문하긴 했는데 그냥 그 이후의 기분이.. 조금 속상했다. 요새 하루종일 모니터 앞에서 앉아있는데 아직도 이렇게 부족하구나 싶었다.
왜인지는 모르겠지만 온몸의 관절이 어제부터 아파서리,, 힘도 없고 :(( 오늘은 그런 날이네.. 참.. 힘이 안 나는 날이다.
그래도 오늘 JOIN 이해해서 그건 좋네