포스트

(Day 28) Object 클래스와 해시(Hash), Thread-Safe

28일차 23/12/21

복습

배운 내용 돌아보기

인터페이스

인터페이스 문법의 용도?

추상클래스에서 더 나아가서 어떤 메서드를 구현해야 할지만 알려주고 싶은 경우.

인터페이스 상속과 구현에 대한 설명?

인터페이스는 인터페이스를 상속할 수 있다.

인터페이스의 다중 상속이 가능한 경우, 불가능한 경우?

메서드 시그너처가 겹치는 메서드가 구현이 안되어 있으면 상관 없다. 구현되어 있으면 어떤 메서드를 써야 하는지 알 수 없으니 문제가 된다.

인터페이스의 deafult 메서드의 용도?

인터페이스를 구현한 기존의 클래스에 영향을 미치지 않으면서 인터페이스에 메서드를 추가하려는 경우다.

인터페이스와 결합하여 추상클래스를 사용할 때 이점을 설명할 수 있는가?

인터페이스의 메서드를 일부만 구현해두고 싶은 경우! 그러면 추상클래스에서 먼저 일부만 구현해두고 나머지는 콘크리트 클래스에서 구현하도록 한다. 보통 이렇게 한다. 일부 구현 -> 일부 구현 -> 일부 구현 -> 전체 구현..

Object 클래스에 대해 설명할 수 있는가?

Object 클래스는 모든 자바 클래스의 최상위 클래스이다. toString(), equals(), hashCode() 와 같은 메서드들을 가지고 있다.

Object 클래스의 메서드들에 대해 설명해보아라.

toString() 메서드는 클래스의 패키지 이름을 포함한 이름 + @ + 해쉬코드 문자열을 만들어서 반환해준다. 서브클래스에서 필요하다면 재정의(Overriding) 할 수 있다. equlas() 메서드는 ==와 같다. 레퍼런스 변수의 인스턴스 주소만 비교한다는 것이다. 그래서 값을 비교하고 싶은 경우 등이라면 재정의가 필요하다. String 클래스 등은 미리 재정의를 해 두었다. (래퍼 클래스?) hashCode() 메서드는 마치 지문처럼, 데이터에서 가능하면 중복이 적은, 어떤 일정한 크기의 값을 얻어내는 클래스다. 해시 알고리즘은 MD4, MD5, SHA256, SHA512, PGP 등이 대표적이다. 중복될 확률이 적고, 연산 요구량이 적으면 좋은 해시 알고리즘이다. (일단 동일한 확률로 해시값을 만들 수 있다고 가정한다면 해시값 크기가 클 수록 중복 확률은 줄어들 것이다.)

학습

해시에 대한 추가 설명

해시 알고리즘에서 사용하는 식별자의 크기(해시값 사이즈)가 크면 클수록, 해시 알고리즘이 사용해야 하는 컴퓨팅 파워는 커진다. 그러나 해시값의 충돌 가능성은 그만큼 낮아진다.

굉장히 욕을 먹는, 웹하드의 그리드 컴퓨팅은 해시 알고리즘을 아주 애용한다. 그리드 컴퓨팅은 어떻게보면 그냥 아무 일도 안하고 돈을 버는 일이다. 웹하드는 어떤 파일을 보관하고 전송하는 것이 결국 제공하는 가치인데, 자사의 웹서버를 통해서 이 스토리지와 트래픽을 감당하지 않고, 고객의 스토리지와 네트워크를 통해서 감당하는 것이 그리드 컴퓨팅이다.

사용자가 웹하드에서 무언가를 다운받으려고 할 때 자동으로 그리드컴퓨팅을 위한 소프트웨어가 설치되고 실행된다. 이건 사용자 컴퓨터에서 무슨 일을 할까?

사용자 시스템에서 유의미한 (다른 고객이 원할만한) 파일들의 해시값을 전부 다 뽑는다. 그리고 그 해시값을 원하는 다른 고객이 있으면 그 고객의 컴퓨터와 네트워크를 이용해서 보내주는 것이다.. 왜 해시값을 쓰는가? 파일의 이름과 확장자를 쓰면 안되나? 고객이 다운받고나면 그 파일의 이름을 바꿀 수 있다.

당연히 해시값으로 구별하다보면 충돌로 인해 원하는 파일이 아니어도 받게 될 수 있다. 특히 이렇게 보통 약관은 보지도 않는 사용자에게 얹어가는(?) 그리드 컴퓨팅의 경우 식별자(생성되는 해쉬 값)을 크게 하여 해시 연산을 많이 하는 것이 부담스러울 것이다. 사용자가 컴퓨터가 느려졌다고 느낄테니.

그래서 충돌나는 경우도 있고, 정상적으로 받은 것 같은데 잘못된(다른) 파일을 받은 경우도 생길 수 있다.

해시값 충돌

데이터는 다른데 같은 해시값이 나온 경우 해시값이 충돌난 것이다. 이런 문제는 데이터 크기보다 작은 크기의 값을 저장하는 해시 알고리즘의 특성상 어쩔 수 없이 존재할 수 밖에 없다.

이런 충돌 문제를 줄이기 위해서, 자주 쓰는 데이터들은 중복을 줄이고, 자주 사용하지 않는 데이터들에서 그 중복이 발생하도록 하는 쪽으로 해시 알고리즘이 발달하게 된다.

해시는 이름하고도 같다

이름이 3자면 동명이인이 많아질 것이다. 이름이 10자면 동명이인이 거의 없어지겠지만 부르기가 힘들 것이다.

Object 클래스의 hashCode()…

자바의 Object 클래스의 hashCode() 메서드는 4바이트 int값을 반환한다.. 2^32 경우를 표현 가능한 식별자이므로, 대략 42억개의 식별자를 가질 수 있는 것이다.

왜 이클립스에서는 hashCode(), equals() 둘 다를 같이 만들어주나?

hashSet 에서 데이터를 저장할 때, equals()와 hashCode()를 둘 다 호출하여 중복 여부를 검사한다. 둘 다 true이면(&&) 중복 엘리먼트로 판단한다는 것이다. 즉 인스턴스는 달라도 내용물이 같으면 해시셋에 저장하기 싫은게 대부분의 비지니스 로직일텐데, 이걸 매번 구현하려니 불편할 것이다. 그래서 IDE에서 구현하는 기능을 추가해줬고, hashCode()와 eqauls()가 함께 hashSet에서 사용되니 동시에 추가하도록 구현한 것이다.

Wrapper 클래스

감싸는 클래스 프리미티브 및 String과 같은 주요 데이터들을 감싸는 클래스 래퍼 클래스나 스트링 클래스에서는 equals()와 hashCode() 클래스를 오버라이드 해 두었다. 그래서 인스턴스별로 해시코드를 만드는 것이 아니라 그 값을 기준으로 만든다. 비교할 때도 값을 비교하지 레퍼런스 주소를 비교하지 않는다.

Cloneable

Cloneable 인터페이스를 구현하는 경우, Object의 clone() 메서드를 사용할 수 있다. 이 인터페이스는 JVM에게 clone()을 허용한다는 것을 알려주는 것이 목적이므로 Ctrl 누르고 인터페이스를 확인해보면 아무런 추상 메서드도 선언되어 있지 않다… 멋진 사람들이 고민한 결과 인터페이스에 이런 내용을 추가했고 지금까지 이렇게 쓰고 있는 것이겠지만 인터페이스에서 이런 기능이 나오니 지금까지 배운것과 달라서 어색하긴 하다.

Shallow Copy VS Deep Copy

얕은 복제: 객체가 포함하는 의존 객체는 복제하지 않는다. 깊은 복제: 객체가 포함하는 의존 객체도 복제한다.

깊은 복제는 의존 객체를 복제하는 코드를 개발자가 작성해줘야 한다. 그리고 clone() 메서드를 오버라이드하여 재정의해주어야 한다.

질문이 자주 나온다. (어느정도 근무 조건이 좋은 경우)

문자열 + 레퍼런스

문자열 결합 "aaa" + obj 이러한 표현식이 있다면 JVM 은 이것을 어떻게 처리할까? (컴파일러 오류 안난다!) "aaa" + obj.toString() 으로 변환되며, 문자열간의 + 연산자는 문자열을 이어준다.

toString()은 오버라이딩하지 않으면, 패키지경로.클래스명@해시 식별자(4바이트 int) 를 반환한다. 그래서 만약 목적이 인스턴스의 변수 값들을 문자열로 받고 싶은 것이었다면 재정의 해 주어야 한다.

toLowerCase()

쓰레드 맛보기

운영체제는! 선점형 운영체제다. CPU에 대한 우선순위는 운영체제가 독자적으로 결정한다. 여러 쓰레드를 사용하는 멀티쓰레드가 실행될 때, 한 쓰레드를 도중에 중단시키고 다른 쓰레드를 실행시킬 수도 있다. 이는 운영체제가 결정한다! 이런 상황이 문제가 될 수 있고, 싫다면 Thread Safe 하게 만들어라.

StringBuilder, StringBuffer 두 클래스의 차이는? StringBuilder는 Thread Safe하지 않다. 멀티쓰레드를 사용한다면 문제가 되지만, 그렇지 않다면 스레드세이프를 위한 여러 검사들을 하지 않아도 되므로 성능이 더 좋다.

StringBuffer

StringBuffer는 한번에 한 쓰레드만이 버퍼를 다루도록 제한된다. 이 제한으로 멀티쓰레드로 실행되는 환경이더라도, 이로 인한 문제는 발생되지 않는다. 이 문제를 방지하기 위한 Lock, Unlock을 구현하는데도 자원이 소모되기에 StringBuilder 보다 느리다. StringBuilder는 여러 쓰레드가 동시에 버퍼를 사용하는 것을 막지 않는다.

StringBuilder

StringBuffer와 달리 멀티스레드 환경을 대비한 lock, unlock 기능이 구현되어 있지 않다. 그래서 더 빠르다. Single Thread 환경에서만 사용하자. Thread-Safe 하지 않다.

Thread-Safe

  1. 여러 스레드가 동시에 작업을 하더라도 문제가 발생하지 않도록 조치를 했음을 의미한다.

    조치: 같은 변수의 값을 여러 스레드가 동시에 변경하려고 할 때 한번에 한 스레드만이 작업하도록 제한함. (같은 변수라는 건 좀 더 멋지게 표현하면 임계영역(Critical Section이다. 한번에 한 스레드만이 작업하는 것을 좀 더 멋지게 표현하면 동기화(Synchronization)이다.)

  2. 여러 스레드가 동시에 진입해서 명령을 실행하더라도 문제가 발생하지 않는 코드다.

    문제가 발생하지 않는 코드: 대표적으로 변수의 값을 조회만 하는 코드,

StringBuffer VS StringBuilder

| | StringBuffer | StringBuilder | | ———– | ———— | ————- | | 동기화 | Yes | No | | Thread-Safe | Yes | No | | 속도 | 느리다 | 빠르다 | 동기화: 여러 스레드 진입을 제한함

이 개념은 다른 클래스들의 비교/선택/판단에도 동일하게 적용된다.

  • HashTable (동기화 처리 됨)
  • HashMap (동기화 처리 안됨)
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.