교육자
전: 우아한형제들 기술이사, 카카오, SK플래닛
진짜 실무에 필요한 제대로 된 개발자가 될 수 있도록, 교육하는 것이 저의 목표입니다.
저의 개발 인생 이야기
EO 인터뷰 영상
개발바닥 - 시골 청년 개발왕 되다
취업과 이직에 대한 고민 해결
강의
로드맵
전체 3수강평
- 스프링 MVC 1편 - 백엔드 웹 개발 핵심 기술
- 스프링 핵심 원리 - 기본편
- 스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
게시글
질문&답변
다대다 관계를 어떻게 풀어내야 할지 고민입니다.
안녕하세요. ho_0214님다대다 관계로 풀어내야 하는가 아닌가에 대한 핵심적인 질문은하나의 회원이 여러 부서에 소속될 수 있는가. 그리고 하나의 부서에 여러 회원이 소속될 수 있는가 입니다.이 둘을 동시에 만족해야 한다면 반드시 중간 테이블을 두고 다대다 관계로 풀어내야 합니다.이런 상황이 아니라면 중간 테이블 없이 간단히 해결하는 것이 보통 좋은 방법입니다.감사합니다.
- 0
- 2
- 30
질문&답변
ThreadLocal을 지역변수로 선언하면 remove가 필요할까요?
안녕하세요. rlawhdals6님ThreadLocal은 지역변수로 사용해도 remove를 사용해야 합니다.왜냐하면 ThreadLocal의 값은 Thread 객체 내부의 ThreadLocalMap에 저장되기 때문입니다.결과적으로 스레드 객에 안에 값이 저장됩니다.물론 스레드 풀을 사용하지 않고, 스레드가 곧바로 종료되는 구조라면 문제가 되지 않습니다.하지만 정말 메서드 내부에서만 쓰고, 깊은 호출 스택에 값 전달이 필요 없다면 그냥 일반 지역변수를 사용하면 됩니다.ThreadLocal을 쓴다는 것 자체가 "파라미터로 넘기기 귀찮거나 프레임워크 훅에서 꺼내 써야 해서 스레드 컨텍스트에 숨겨둔다"는 의도가 대부분이기 때문입니다.감사합니다.
- 0
- 2
- 53
질문&답변
[제보] pdf내 gradle dependencies 명령어가 동작하지 않는 이슈 제보
최신우님 감사합니다 🙂다음 패치에 반영하겠습니다!
- 0
- 1
- 39
질문&답변
배열 객체 생성
안녕하세요. yne325님예제의 경우에는 쉽게 풀어서 설명하기 위해서 student1, student2를 별도로 만든 것입니다 🙂따라서 배열에서 생성하고 사용하셔도 괜찮습니다.그러면 생각하신 것 처럼 참조 변수 student1, student2가 없어도 되니 메모리가 아주 조금이나마 아껴지겠지요?감사합니다.
- 0
- 2
- 43
질문&답변
private 멤버 변수 할당
안녕하세요. dkdkdkslfkdl님main은 우리의 환경을 조립하는 역할을 담당합니다. 따라서 main은 K3Car를 포함한 모든 인스턴스를 알고 있어야 합니다.이 부분의 핵심은 이후에 변경사항에서 더 자세히 알게 되실건데요. 핵심은 변경 이후에 Driver를 변경하지 않고 다형성을 활용해 구체 클래스를 변경할 수 있다는 점입니다.감사합니다.
- 0
- 2
- 35
질문&답변
ControllerV3 질문
안녕하세요. 재우양님ControllerV3를 만들 때 파라미터로 Map을 전달하는 것은 HttpServletRequest와 HttpServletResponse와 같은 서블릿 기술에 대한 컨트롤러의 종속성을 제거하기 위함입니다.구체적으로 설명하면 다음과 같습니다:Map을 사용하는 이유:ControllerV2까지는 컨트롤러가 HttpServletRequest와 HttpServletResponse를 직접 파라미터로 받았습니다. 이로 인해 컨트롤러는 서블릿 기술에 직접적으로 의존하게 되었습니다.ControllerV3의 핵심 목표는 이러한 서블릿 종속성을 제거하여 컨트롤러의 코드를 더욱 단순하게 만들고, 테스트 작성을 용이하게 하는 것입니다.HttpServletRequest 객체를 통해 직접 파라미터를 조회하는 대신, 프론트 컨트롤러(FrontControllerServletV3)가 HttpServletRequest로부터 파라미터 정보들을 미리 추출하여 Map 형태로 변환한 다음, 이 paramMap을 ControllerV3로 넘겨줍니다. 이렇게 함으로써 ControllerV3는 서블릿 기술을 전혀 몰라도 동작할 수 있게 됩니다.파라미터로 HttpServletRequest request만 받으면 안 되는 이유:만약 HttpServletRequest request를 여전히 파라미터로 받게 된다면, ControllerV3는 여전히 서블릿 기술에 종속되게 됩니다. 이는 ControllerV3를 설계한 주요 목표인 서블릿 종속성 제거와 모순됩니다.ControllerV3는 "서블릿 기술을 전혀 사용하지 않는다"는 특징을 가지며, 이 덕분에 "구현이 매우 단순해지고, 테스트 코드 작성이 쉽다"는 장점을 얻습니다. HttpServletRequest를 받으면 이러한 장점들이 사라지게 됩니다.요약하자면, Map의 사용은 HttpServletResponse뿐만 아니라 HttpServletRequest를 포함한 모든 서블릿 관련 객체로부터 컨트롤러를 분리하여 더 유연하고 테스트하기 쉬운 구조를 만들기 위한 설계 결정입니다.감사합니다.
- 0
- 2
- 38
질문&답변
원자적 연산의 의미
안녕하세요. 티티티님원자적 연산(Atomic Operation)의 의미는 다음과 같습니다.컴퓨터 과학에서 원자적 연산의 기본 의미는 해당 연산이 더 이상 나눌 수 없는 단위로 수행된다는 것을 뜻합니다. 즉, 이 연산은 중단되지 않고, 다른 연산과 간섭 없이 완전히 실행되거나 전혀 실행되지 않는 성질을 가집니다. 이는 마치 핵분열이 불가능하다고 여겨졌던 과거의 원자(atom) 개념처럼, 더 이상 쪼갤 수 없는 가장 작은 작업 단위를 의미합니다.예시를 통한 이해:volatile int i = 0; 일 때, i = 1; 이라는 연산은 원자적 연산입니다. 이는 오른쪽에 있는 값 1을 왼쪽 변수 i에 대입하는 단 하나의 순서로 실행되기 때문입니다.하지만 i = i + 1; 또는 i++와 같은 연산은 겉보기와 달리 원자적 연산이 아닙니다. 이 연산은 다음과 같이 여러 단계로 나누어 실행됩니다:i의 값을 읽어온다.읽어온 값에 1을 더한다.더한 값을 다시 i 변수에 대입한다. 멀티스레드 환경에서는 이 세 단계 중 1번과 3번 연산 사이에 다른 스레드가 i의 값을 변경할 수 있어 문제가 발생할 수 있습니다."여러 코드 뭉치지만 하나의 연산으로 취급"하는 경우질문 주신 "넓은 의미에서는 일종의 트랜잭션처럼, 여러 코드뭉치지만 하나의 연산으로 취급할 때에도 사용되나요?"에 대한 답변은 '네, 맞습니다' 입니다. 엄밀히 말해 원자적 연산 자체는 나눌 수 없는 단일 작업이지만, 여러 단계로 구성된 논리적인 작업을 멀티스레드 환경에서 마치 하나의 원자적 연산처럼 안전하게 보이도록 처리할 때 이러한 개념이 적용됩니다. 이는 크게 두 가지 방식으로 달성됩니다.동기화(Synchronization) 방식 (락 기반)synchronized 키워드나 Lock 등을 사용하여 임계 영역(critical section)을 설정할 수 있습니다. 임계 영역은 여러 스레드가 동시에 접근하면 데이터 불일치나 예상치 못한 동작이 발생할 수 있는 위험하고 중요한 코드 부분입니다.이러한 동기화 메커니즘은 한 번에 하나의 스레드만 해당 임계 영역에 접근하도록 강제합니다. 따라서 i = i + 1;처럼 여러 단계로 이루어진 연산이라도, 전체 과정이 다른 스레드의 방해 없이 순차적으로 실행되도록 보장하여 논리적으로는 하나의 원자적 단위처럼 작동하게 만듭니다. 이는 비관적인 접근법으로, 항상 락을 획득하여 다른 스레드의 접근을 막습니다.CAS (Compare-And-Swap) 연산 기반 (락 프리)CAS 연산은 락을 사용하지 않고 원자적인 연산을 수행하는 방법으로, "락 프리(lock-free)" 기법이라고도 불립니다. 이는 낙관적(optimistic) 접근법으로, 충돌이 발생할 것이라고 가정하지 않고 일단 작업을 시도한 뒤, 충돌이 발생하면 재시도합니다.핵심은 CPU 하드웨어 수준에서 두 개의 연산(값 확인, 값 변경)을 하나의 원자적인 명령으로 묶어서 제공한다는 점입니다. 예를 들어, AtomicInteger의 incrementAndGet() 메서드는 내부적으로 이 CAS 연산을 활용합니다.AtomicInteger는 getValue를 읽어온 후 compareAndSet(getValue, getValue + 1)을 시도합니다. 만약 이 CAS 연산이 성공하면 true를 반환하고, 실패하면 false를 반환하며 do-while 루프를 통해 다시 시도합니다. 즉, 여러 단계의 연산(읽기-수정-쓰기)을 CAS 연산과 재시도(retry) 루프의 조합을 통해 마치 하나의 원자적인 연산처럼 안전하게 처리하는 것입니다.따라서, 원자적 연산의 엄밀한 정의는 '더 이상 나눌 수 없는 최소 단위'를 의미하지만, 실제 멀티스레드 프로그래밍에서는 synchronized나 CAS와 같은 기술을 활용하여 여러 코드 뭉치나 논리적인 작업 단위를 마치 하나의 중단되지 않는 트랜잭션처럼 처리하여 동시성 문제를 해결하는 데 사용됩니다. 이는 애플리케이션의 안정성과 정확성을 보장하는 데 매우 중요합니다.감사합니다.
- 0
- 1
- 46
질문&답변
컨테이너 호출 방법 질문
안녕하세요. horizon님AI 인턴이 잘 답변해주었는데요.이후의 로드맵에서 자연스럽게 각각의 테스트에서 컨테이너를 호출하는 방법들을 설명합니다 🙂감사합니다.
- 0
- 2
- 37
질문&답변
@Configuration 를 왜 사용할까요?
안녕하세요. horizon님AI 인턴이 잘 답변을 남겨주었는데요.추가로 다음 링크를 보시면 도움이 되실거에요.https://inf.run/oHfYM감사합니다.
- 0
- 2
- 45
질문&답변
volatile
안녕하세요. 티티티님답변이 늦어서 죄송합니다.말씀하신 대로 BasicInteger 예제에서 volatile 키워드를 사용하지 않았음에도 불구하고 어느 정도 값이 수정된 것처럼 보인 현상에 대해 설명드리겠습니다. 이는 자바의 메모리 가시성(memory visibility) 문제와 CPU의 캐시 메모리 작동 방식, 그리고 컨텍스트 스위칭이 복합적으로 작용한 결과입니다.다음과 같은 점들을 고려하여 이해해볼 수 있습니다:CPU 캐시와 메인 메모리: 현대 CPU는 성능 향상을 위해 메인 메모리보다 훨씬 빠른 캐시 메모리를 사용합니다. 각 스레드는 일반적으로 자신에게 할당된 CPU 코어의 캐시 메모리에서 데이터를 읽고 씁니다. 한 스레드가 캐시 메모리의 값을 변경하더라도, 이 변경 사항이 즉시 메인 메모리에 반영되거나 다른 CPU 코어의 캐시 메모리에 전파되지 않을 수 있습니다.volatile 키워드는 이러한 메모리 가시성 문제를 해결하기 위해, 해당 변수에 대한 읽기/쓰기 작업이 항상 메인 메모리에 직접 접근하도록 강제하여, 한 스레드의 변경 사항이 다른 스레드에게 즉시 보이도록 보장합니다.BasicInteger의 문제점: BasicInteger 예제에서 value++ 연산은 다음과 같이 세 단계로 나눌 수 있습니다:value의 현재 값을 읽는다.읽은 값에 1을 더한다.새로운 값을 value에 대입한다.이 세 단계는 원자적(atomic) 연산이 아닙니다. 즉, 여러 스레드가 동시에 value++를 실행할 때, 한 스레드가 1단계(읽기)를 수행한 후 3단계(대입)를 완료하기 전에 다른 스레드가 1단계(읽기)를 수행하여 잘못된 값을 읽을 수 있습니다. 이것이 경합 조건(Race Condition) 이고, BasicInteger가 기대하는 1000이 아닌 다른 숫자가 나오는 근본적인 이유입니다.volatile은 이러한 읽기-수정-쓰기 연산 자체를 원자적으로 만들지는 않습니다. volatile을 적용한 VolatileInteger도 여전히 1000에 도달하지 못했던 이유가 바로 여기에 있습니다.컨텍스트 스위칭의 비결정성: 이전 volatile 플래그 예제에서 컨텍스트 스위칭이 캐시 동기화를 보장하지 않는다고 말씀드렸던 것처럼, BasicInteger의 경우에도 캐시가 메인 메모리에 반영되는 시점은 비결정적입니다.하지만 Thread.sleep()이나 콘솔 출력(로그 출력 포함)과 같은 특정 작업은 스레드가 잠시 쉬게 만들고 컨텍스트 스위칭을 유발할 수 있습니다. 이때, 해당 스레드의 캐시 내용이 메인 메모리에 반영되거나 다른 스레드의 캐시가 메인 메모리의 최신 값을 가져올 기회가 생길 수 있습니다.IncrementPerformanceMain에서 sleep(10)을 넣어 스레드가 동시에 increment()를 호출하도록 유도했는데, 이 sleep() 호출 자체가 컨텍스트 스위칭을 빈번하게 발생시켜, 캐시 동기화가 더 자주 발생할 가능성을 높입니다.결과적으로 BasicInteger가 1000에 도달하지는 못하더라도, 일부 업데이트가 반영될 수 있었던 것은 이러한 컨텍스트 스위칭으로 인한 비주기적인 캐시 동기화가 운 좋게(?) 발생했기 때문이라고 볼 수 있습니다. 이는 "작동할 수도 있고, 안 할 수도 있다"는 비결정적이고 불안정한 상태의 전형적인 예시입니다.핵심 정리:BasicInteger의 문제는 value++ 연산의 비원자성에 있습니다. 이는 메모리 가시성(volatile이 해결하는 문제)과는 다른 차원의 문제입니다.컨텍스트 스위칭은 캐시 동기화를 유발할 수 있는 한 가지 요인이지만, 이를 보장하지 않습니다. BasicInteger가 1000에 도달하지 못한 것은 바로 이 보장이 없기 때문입니다.따라서 멀티스레드 환경에서 value++처럼 공유 자원을 읽고 쓰는 작업을 안전하게 처리하려면 synchronized 키워드를 사용하거나, AtomicInteger와 같은 원자적인 연산을 제공하는 클래스를 사용해야 합니다.감사합니다.
- 0
- 2
- 54