여러 스레드가 하나의 메서드를 동시에 호출할 때 그 메서드가 어떻게 동작하느냐는 클라이언트와 클래스 간의 중요한 계약과도 같다. 만약 API 문서에 아무런 언급이 없다면 사용자는 나름의 가정을 하고 사용해야 한다. 만약 이 가정이 틀린다면 프로그램은 심각한 오류로 이어질 수 있다. API 문서에 synchronized 한정자 synchronized 한정자가 달려있는 메서드는 스레드 안전하다고 생각할 수 있다. 하지만 이는 몇 가지면에서 틀렸다. 메서드 선언에 synchronized 한정자를 선언할지는 구현 이슈일 뿐 API에 속하지 않는다. 따라서 이것만으로는 해당 메서드가 스레드 안전하다고 믿기 어렵다. 멀티스레드 환경에서도 API를 안전하게 사용하려면 클래스가 지원하는 스레드 안전성 수준을 정확히 명..
고수준의 동시성 유틸리티가 Java5에서 도입되면서 wait와 notify를 사용해야 할 이유가 많이 줄었다. wait와 notify는 올바르게 사용하기 아주 까다로우니 고수준 동시성 유틸리티를 사용하자. 동시성 유틸리티 java.util.concurrent 동시성 유틸리티는 실행자 프레임워크, 동시성 컬렉션, 동기화 장치 이렇게 세 범주로 나눌 수 있다. 실행자 프레임워크 앞선 item80에서 실행자 프레임워크에 대한 내용을 다뤘다. 이 내용에 대한 내용은 item80을 참고하자. 동시성 컬렉션 List, Queue, Map 등 표준 컬렉션 인터페이스에 동시성을 추가해 구현한 고성능 컬렉션이다. 동기화를 내부에서 수행하여 높은 동시성에 도달할 수 있다. 내부에서 동기화를 수행하므로 동시성을 무력화할 수..
과거에는 클라이언트가 요청한 작업을 백그라운드 스레드에 위임해 비동기적으로 처리하기 위해 단순한 작업 큐를 사용했다. 하지만 이는 안전 실패나 응답 불가에 대한 예외 코드 작성 등으로 수많은 코드 작업이 발생했다. 다행히도 실행자 프레임워크의 등장으로 이제 이러한 수고를 덜 수 있게 되었다. 실행자 프레임워크(Executor Framework) 실행자 프레임워크는 java.util.concurrent 패키지에 속해있으며, 인터페이스 기반의 유연한 태스크 실행 기능을 담고 있다. 기존보다 모든 면에서 뛰어난 작업 큐를 이제는 단 한 줄로 생성할 수 있다. // 작업 큐 생성 ExecutorService exec = Executors.newSingleThreadExecutor(); // 실행할 태스크를 넘김..
과도한 동기화는 성능을 떨어뜨리고, 교착상태에 빠드리고, 예측할 수 없는 동작을 낳기도 한다. 응답 불가(교착 상태)와 안전 실패(데이터 훼손)를 피하려면 동기화 메서드나 동기화 블록 안에서는 제어를 절대 클라이언트에 양도하면 안 된다. 외계인 메서드 응답 불가와 안전 실패를 유발할 수 있는 메서드 즉, 동기화된 영역에서 재정의할 수 있는 메서드 혹은 클라이언트가 넘겨준 함수 객체 등을 동기화된 클래스 관점에서 외계인 메서드라고 한다. 동기화된 클래스는 외계인 메서드가 무슨 일을 할지 알지 못하며 통제할 수도 없고 외계인 메서드가 하는 일에 따라 동기화된 영역은 예외를 일으키거나, 교착상태에 빠지거나, 데이터를 훼손할 수도 있다. 외계인 메서드 예제 ForwardingSet class Forwarding..
동기화(synchronized) synchronized(동기화) 키워드는 해당 메서드나 블록을 한번에 한 스레드씩 수행하도록 보장한다. 1. 동기화에 대한 오해 많은 프로그래머가 동기화를 배타적 실행을 막는 용도로만 생각한다. 즉, 한 스레드가 변경하는 중이라 상태가 일관되지 않은 순간의 객체를 다른 스레드가 접근하지 못하도록하는 용도로만 생각한다. 하지만 동기화에는 중요한 기능이 하나 더 있다. 동기화된 메서드나 블록에 들어간 스레드가 같은 락(lock)의 보호하에 수행된 모든 이전 수정의 최종 결과를 보게 해준다. 2. 동기화가 필요한 이유 Java 언어 명세에는 "long과 double 외의 변수를 읽고 쓰는 동작은 원자적이다."라고 명세되어있다. 이는 항상 어떤 스레드가 정상적으로 저장한 값을 온..
API 설계자가 메서드 선언에 예외를 명시하는 이유는, 그 메서드를 사용할 때 적절한 조치를 취해달라고 말하는 것이다. 하지만 사람들이 자주 어기고 흘려버리는 경우가 많다. 예외를 무시하는 방법은 아주 쉽다. 단순히 메서드 호출을 try-catch로 감싼 후 catch에서 아무 일도 하지 않으면 된다. // catch를 비워두면 예외는 무시된다. try { ...... } catch(SomeException e) { // 아무일도 하지 않는다. } catch 블록을 비워두지 말자 예외는 문제 상황에 대처하기 위해 존재하는데 catch 블록을 비워두면 예외가 존재할 이유가 사라진다. 비유하자면 화재경보를 무시하는 수준이 아니라 아예 꺼버려, 다른 누구도 화재가 발생했는지 모르게하는 것과 같다. 이러한 상황..
호출된 메서드가 실패해도 해당 객체는 메서드 호출 전 상태를 유지하는 특성을 실패 원자적이라고 한다. 우리는 메서드를 가능한 한 실패 원자적으로 만들어야 한다. 작업 도중 예외가 발생해도 그 객체를 정상적으로 사용할 수 있는 상태라면 호출자가 오류 상태를 복구할 수 있을 테니 더 유용하기 때문이다. 메서드를 실패 원자적으로 만드는 방법 1. 메서드를 불변 객체로 설계한다. 불변 객체는 태생이 실패 원자적이며 객체가 불안정한 상태에 빠지는 일은 생기지 않는다. 불변 객체는 생성 시점에 고정되어 변하지 않기 때문이다. 메서드가 실패하면 새로운 객체가 만들어지지 않을 뿐이다. 2. 매개변수의 유효성을 검사한다. 가변 객체를 다루는 메서드를 실패 원자적으로 만들기 위한 가장 쉬운 방법은 수행에 앞서 매개변수의 ..
김영한님의 자바 ORM 표준 JPA 프로그래밍 강의를 들으며 공부한 내용을 정리한 글입니다. 1. JPA란? JPA(Java Persistence API)는 자바 진영의 ORM 기술 표준이다. ORM(Object Realational Mapping)이란? 객체와 RDBMS 사이를 중간에서 매핑해주는 객체 관계 매핑 기술이다. JPA는 애플리케이션과 JDBC 사이에서 동작하며 Java의 객체와 RDBMS 사이를 매핑하는 역할을 한다. 프로그래머가 Entity를 작성하면 JPA는 이를 분석해서 자동으로 필요한 SQL을 생성한다. 따라서 프로그래머는 조금 더 객체 지향적인 관점에서 프로그래밍을 할 수 있게 된다. 2. JPA가 왜 필요할까? 기존의 SQL 중심의 개발에서는 객체에 필드를 추가하면 그와 관련된 ..
프로그램이 실패하면 자바는 그 예외의 스택 추적(stack trace) 정보를 자동으로 출력한다. 스택 추적 정보는 예외 객체의 toString 메서드를 호출해 얻은 문자열로, 보통은 클래스 이름 뒤에 상세 메시지가 붙는 형태이다. 예외의 toString() 메서드에 실패 원인에 관한 정보를 가능한 많이 담아 반환하는 것은 굉장히 중요하다. 실패 원인을 얻을 수 있는 유일한 정보일 수도 있고 재현하기 어려운 오류일 경우 더 자세한 정보를 얻기 어렵기 때문이다. 예외에 관여된 모든 매개변수, 필드의 값을 실패 메시지에 담자 예를 들어, IndexOutOfBoundsException의 상세 메시지에는 범위의 최솟값과 최댓값, 그리고 범위를 벗어난 인덱스의 값을 담아야 한다. 원인이 될 수 있는 모든 매개변수..