GiYeong
Java - 5 본문
강한 결합과 느슨한 결합
강한 결합
객체의 의존 관계에서 강한 결합이란, 어떤 객체가 다른 객체에 강한 의존성을 가지고 있음을 말한다.
객체 간에 강한 결합을 이루고 있으면, 한 클래스가 변경될 경우 연관된 다른 클래스도 변경되어야 하며, 이는 유지보수 측면에서 굉장히 불리한 케이스이다.
느슨한 결합
클래스의 자료구조, 메서드를 추상화 할 수 있는 인터페이스를 사용해서 의존성을 최소화하여 강한 결합을 약화시키고 느슨한 결합을 이루게 할 수 있다.
직렬화와 역직렬화
직렬화
객체를 직렬화하여 전송 가능한 형대로 만드는 것을 의미한다. 즉, 객체들의 데이터를 연속적인 데이터로 변형하여 Stream을 통해 데이터를 읽도록 해준다.
직렬화를 하기 위해서는 Serializable 인터페이스를 구현하여야 한다.
public class exampleClass implements Serializable {
}
보통 클래스의 멤버 변수는 전부 직렬화 대상에 해당되지만, 보안 상의 문제 등으로 일부를 직렬화 대상에서 제외하고 싶다면 transient를 통해 제외할 수 있다.
public class exampleClass implements Serializable {
private String id;
private transient String password; // 직렬화 대상에서 제외된다.
private String name
exampleClass2 example; // exampleClass2가 Serializable 인터페이스를 구현하고 있어야지만 직렬화가 가능하다.
}
기본 자료형이 아닌 다른 객체를 멤버 변수로 사용하는 경우, 해당 멤버 변수의 클래스가 Serializable 인터페이스를 구현하고 있어야지만 직렬화가 가능하다. 하나라도 구현되어있지 않으면 직렬화는 불가능하다.
역직렬화
직렬화된 파일 등을 역으로 직렬화하여 다시 객체의 형태로 만드는 것을 의미한다. 즉, 저장된 파일을 읽거나 전송된 Stream 데이터를 읽어 원래 객체의 형태로 복원한다.
동시성 이슈(공유자원 접근)
CPU가 어떤 작업을 처리하기 위해 데이터가 필요할 때, CPU는 RAM의 일부분을 속도가 빠른 CPU Cache Memory로 읽어들인다. 읽어들인 데이터로 명령을 수행하고, RAM에 저장하기 위해서는 데이터를 CPU Cache Memory에 쓴 다음, RAM에 쓰기 작업을 수행한다. 하지만 CPU가 Cache에 쓰기 작업을 수행했다고 해서 바로 RAM으로 쓰기 작업을 수행하지는 않는다. 또한 읽기 작업도 해당 데이터가 RAM에서 변경이 되었다고 해도, 언제 CPU Cache Memory가 업데이트될 지 보장하지 않는다.
동시성 프로그래밍에서는 CPU와 RAM의 중간에 위치하는 CPU Cache Memory와 병렬성이라는 특징때문에 다수의 스레드가 공유 자원에 접근할 때 2가지 문제가 발생할 수 있다.
1. 가시성 문제
2. 원자성(동시 접근) 문제
2가지 문제는 동시성보다는 병렬성때문에 발생하는 문제이지만, Java 스레드는 동시성의 성질을 가지고 있기 때문에 이를 Java에서는 동시성 프로그래밍에서 발생하는 문제점이라고 부른다.
가시성 문제
여러 개의 스레드가 사용됨아 따라, CPU Cache Memory와 RAM의 데이터가 서로 일치하지 않아 생기는 문제를 의미한다.
이를 해결하기 위해서는 가시성이 보장되는 변수(valatile 키워드)를 CPU Cache Memort가 아닌 RAM에서 바로 읽도록 보장해야 한다.
private static volatile boolean isChecked;
하지만 가시성이 보장된다고 동시성이 보장되는 것은 아니다.
volatile 키워드는 어디까지나 해당 변수를 메인 메모리로부터 읽을 수 있게 해주는 것이고, 다른 스레드에 의해 해당 변수의 값은 언제든 바뀔 수 있다.
즉, 가시성이란 공유 데이터를 읽는 경우의 동시성만 보장해주는 것이다.
원자성 문제
원자성은 가시성과 멀티 스레드 환경에서 스레드 같의 공유 메모리 이슈를 발생한다는 점에서 공통점이 있지만, 시스템 관점에서 보면 차이점이 있다.
가시성
CPU - Cache - Memory 관계 상의 개념
원자성
한 줄의 프로그램 문장이 컴파일러에 의해 기계어로 변경되면서, 이를 기계가 순차적으로 처리하기 위한 여러 개의 Machine Instruction이 만들어져 실행되기 때문에 일어나는 현상
예를 들어 프로그램 언어적으로 i++ 문장은 i를 메모리로부터 읽고, 읽은 값에 1을 더하고, 연산된 값을 메모리에 저장하는 명령어도 쪼개진다.
멀티 스레드 환경에서는 한 스레드가 각 기계 명령어를 수행하는 동안, 다른 스레드가 개입하여 공유 변수에 접근하여 같은 기계 명령어를 수행할 수 있으므로 값이 꼬이게 된다.(race condition)
원자성 문제를 해결하기 위해서는 synchronized 또는 atomic을 사용해야 한다.
원자성 문제를 해결하면 가시성의 문제도 해결된다. synchronized 블럭을 들어가기 전에 CPU Cache Memory와 Main Memory를 동기화해주며, atomic의 경우에는 CAS 알고리즘에 의해 원자성 문제와 CPU Cache Memort에 잘못된 값을 참조하는 문제를 동시에 해결해 준다.
1. synchronized
멀티 스레드 환경에서 동시성 제어를 위해 공유 객체를 동기화하는 키워드이다. synchronized 블록 안에서 관리되는 자원들은 원자성을 보장할 수 있다.
2. atomic
CAS(Compared And Swap)라는 알고리즘으로 작동하여 원자성을 보장한다.
CAS 알고리즘이란 CPU Cache Memory와 RAM을 비교하여 일치한다면 CPU Cache Memory와 RAM에 적용하고, 일치하지 않는다면 재시도함으로서 원자성을 보장한다.
Mutable 객체와 Immutable 객체
Mutable 객체 | Immutable 객체 |
객체가 생성된 이후에 상태가 변경될 수 있는 객체 | 객체가 생성된 이후에 상태가 변경되지 않는 객체 |
이미 존재하는 객체에 재할당(값 변경) | 새로운 객체를 생성하여 재할당 |
값을 변경할 수 있는 메서드를 제공 | 값을 변경할 수 있는 메서드를 제공하지 않음 |
Getter와 Setter 존재 | Getter와 Setter 미존재 |
thread safe하지 않을 수 있음(병렬 처리 시, 값 보장할 수 없음) | thread safe(병렬 처리 시, 값 보장) |
ex) StringBuffer, StringBuilder, java.util.Date 등 | ex) Legacy classes, Wrapper classes, String class 등 |
NULL
객체가 없거나 상태가 정의되지 않은 상태