개발 일지/주간 개발 일지

[03월 5주차] Thread API 전체적인 흐름 파악하기, 고정 소수점과 부동 소수점

계란💕 2023. 4. 1. 23:35

Thread의 isAlive()

  • 개발자가 커스텀스레드 생성하면 반드시 run()을 오버라이드해야한다. 
  • run() 메서드 안에서 실행 중이면 true를 반환한다. run() 메서드가 끝나거나 실행 전이면 false를 반환한다. 

Thread API 전체적인 흐름

  • 회사에서 스레드를 컨트롤할 수 있는 API를 만들어서 이 내용을 되짚어보려고 한다. 

 

  • Listener 엔티티
    • 카프카와 관련된 데이터(server IP 주소, port 번호,  topic name, groupId 등등)가 들어가는 객체이다. 
    • 카프카 서버로부터 데이터를 받는 서버이면서 엔티티이기 때문에 Listener라는 이름이 붙은 걸로 예상
    • Kafka -> Listner server -> Executor server 이렇게 데이터를 전송한다. 

 

  • ListenerHistory 테이블에는 Listener와 관련한 이력을 저장한다. 
    • 따라서, 다대일 연관 관계 매핑이 필요하다. 
    • successYn: Executor 서버로 보낸 OkHttp API 요청에 대해서 정상적인 HttpStatus 응답을 받았는지 여부
    • 에러메시지는 위에서 보낸  OkHttp  API 요청에 대해서 실패했을 경우 받는 에러메시지를 뜻한다. 
    • (어떤 회원에 대한 로그인 이력을 저장하듯 리스너 이력을 저장하는 엔티티)

 

 

  • ThreadService
    • ThreadService는 내가 만든 커스텀 스레드인 MyThread에 관련한 메서드가 들어있다. 
    • addThread(ListenerId): 테이블에 존재하는 리스너에 대해서 스레드를 만들어 실행한다. 
    • terminateThread(ListenerId): 실행 중인 스레드를 종료시킨다. 
    • Map<String, MyThread>형태의 멤버변수 listenerThreadMap을 선언한다. 
    • (map은 listenerId를 가지고 해당 스레드 정보를 찾기 위해 만든 필드이다. 뒷 부분에서 url 뒤 편에 {listenerId}를 넣어서 해당 스레드를 종료하거나 실행시키는 API를 만들기 위해 꼭 필요하다.)

 

 

  • EventListener 클래스
    • 인터페이스  ApplicationListener<ApplicationStartedEvent>를 implements 하면 onApplicationEvent()를 오버라이드한다.
    • 애플리케이션이 뜨자마자 항상 어떤 메서드(@Override onApplicationEvent())를 실행시킨다. 
    • 메서드 안 에서 몇 가지 조건을 만족하는 리스너를 조회한다. 조건을 만족하는 인스턴스를 모아서 availableListenerList에 담는다. 
    • 리스트를 조회해서 리스너를 스레드에 담아서 하나씩 스레드를 실행시킨다. 그리고 ThreadService 안의 listenerThreadMap<listenerId, MyThread>에 해당 (리스너와 리스너를 매개변수로 받아서 실행 중인) 스레드 정보를 함께 담아준다.   
      • 그러면 EventListener 클래스에 ThreadService 를 이용해서 listenerThreadMap에다가 스레드 정보, 리스너 id를 담아주도록 한다. 
      • onApplicationEvent(): 애플리케이션이 구동하자마자 반드시 실행되는 메서드이다.
    • EventListener에 ThreadService라는 변수를 선언해서 Map<> 형태로 스레드 정보를 저장한다. 
      • 그런데 ThreadController에서  EventListener를 직접 호출하지는 않지만 Service를 변수로 선언하면 이전에 onApplicationEvent()를 실행하면서 저장해준 Map 정보를 읽어올 수 있다. 
      • 아마도  스프링 컨테이너에 빈으로 등록할 때 싱글톤이 기본값이라서 어느 클래스에서 Service 클래스를 호출해도 같은 Map 정보를 불러올 수 있다.  (싱글톤static과 성질이 유사하다.)

 

 

  • Listener를 필드로 가지는  MyThread<T extends Thread> 클래스를 만든다. 
    • MyThread: Thread를 상속하는 제네릭 클래스를 만든다 .
    • 기본적으로 run()을 반드시 오버라이드해야한다. 
    • run() 안에는 KafkaConsumer에 들어있는 메시지를 while문으로 poll()하면서 Kafka에 저장된 메시지를 consume한다. (즉, 어떤 스레드를 인위적으로 멈추기 전까지는 애플리케이션이 계속 돌아가면서  멀티스레드가 연속으로 실행된다.)
    • MyThread의 flag 변수는 해당 스레드를 계속 실행할지 말지 여부를 나타낸다. false로 바뀌는 경우 스레드가 정지되고 run() 에서 빠져간다. 
    • run() 안에 있는 while문은 flag가 true인 경우만 계속 돌아간다. 
      • === Apache Kafka === 
      • KafkaConsumer를 이용해서 Map 형태의 config를 담아준다. (회사에서 Kafka를 띄운 서버 주소, topic name 등등 카프카 관련 설정 정보)
      • 무한 반복문을 돌려서 메시지 큐에 담긴 모든 메시지를 poll() 해준다. 
      • === MyThread ===
      • MyThread  안에는 runThreadStop() 메서드가 있다. 
      • runThreadStop()는 리스너 id 일치(매개변수와 실제 실행중인 스레드 일치 여부) 를 확인한 후, 해당 스레드의  flag를 false로 만든다.  runThreadStop()가 실행되자마자 while문 멈추도록 한다. 실행 중인 스레드에 대한 정지 요청을 보낸다. (스레드의 특성상 요청 보내자마자 바로 정지되는 게 아니다. 따라서, 정지 요청의 시작 여부라고 볼 수 있음)
      • === ThreadService ===
      • ThreadService에서 스레드를 정지시키려면(terminateThread() 메서드)  MyThread에 선언한 runThreadStop()를 가장 먼저 호출한다. 
      •  while문 안에서는 해당 클래스의 Listener가 몇 가지 조건을 만족할 경우, ListenerHistory 안에 해당 리스너를 저장한다. 
      • (OkHttp: 반복문 마지막에서 MyThread를 이용해서 리스너 ID를 URL 맨 뒤에 매개변수로 넣어서 다른 서버에 Request를 보내고  Response(정상, 실패)까지 받을 예정인데 이 부분은 잠시 보류하고 운영 직전에 테스트 해보려고 한다.)

 

 

커스텀 Thread 만들어서 멀티스레드를 이용한 이유

  • 하나의 인스턴스를 조회할 때마다 새로운 싱글스레드를 생성하여 실행시킬 것이다. MyThread안에 리스너 객체를 하나씩 넣어서  run()을 돌리기 위해 필요하다.
  • run()에는 해당 리스너에 대해 공통적으로 실행하고 싶은 로직을 넣을 수 있다. 
  • 멀티스레드의 효율성과 run() 안에 내용을 새롭게 구현하기 위해서 멀티스레드를 사용한다고 이해했다. 
  • 그리고 url 주소 뒷 부분에  {listenerId} 를 매개변수로 넣어서 해당 스레드를 정지, 실행시키기 위해서 멀티스레드 구조로 되어 있어야한다. 

 


에러 해결

  • 오류
    • class file has wrong version 61.0, should be 52.0
          Please remove or make sure it appears in the correct subdirectory of the classpath.
  • 원인: Java 버전  <=> Spring Boot 버전 호환이 안되서 발생 
  • 해결: Spring boot 버전을 2점대로 낮췄다

yml 설정 오류

<hide/>
***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
	If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
	If you have database settings to be loaded from a particular profile you may need to activate it (no profiles are currently active).

 

  • 오류: 데이터베이스에 대한 properties 설정이 없어서 발생한 오류
  • 원인: properties 설정
  • 해결
    • yml 파일에다가 값을 넣어준다.
    • 주의) yml 파일은 들여쓰기에 주의하지 않으면 같은 오류가 연달아 발생한다. 
<hide/>
spring:
  datasource:
    url: jdbc:postgresql:// 데이터베이스 주소
    username: 아이디
    password: 비번
    driver-class-name: org.postgresql.Driver
    hikari:
      maximum-pool-size: 4
  jpa:
    properties:
     hibernate:
      show_aql: true
      format_sql: true
      use_sql_comments: true
      default_schema: "스키마 이름"
      order_inserts: true
      order_updates: true
      jdbc:
        batch_size: 500
    ddl-auto: update

HttpMessageNotReadableException

HttpMessageNotReadableException
<hide/>
{
    "timestamp": "2023-04-01T16:13:54.541+00:00",
    "status": 400,
    "error": "Bad Request",
    "trace": "org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public org.springframework.http.ResponseEntity<?>

 

  • 오류: RequstBody를 정상적으로 넣었는데 이 부분이 잘못됐다는 오류가 난다. 
  • 원인
    • RequstBody를 형식에 맞춰 넣었지만 오류가 난다. 
  • 해결
    • postman 으로 요청을 보낼 때, Header에  content-length를 꼭 넣어줘야한다. 
    • cf) host를 안 넣어주면 JSON 형식으로 반환되지 않고 다음과 같이 html 형태로 에러 정보가 반환된다. 따라서, host, content-length, content-type 을 모두 넣어줘야한다. 
    •  

 

POST

 

 

Request Header 종류

  • content-type: 컨텐츠 타입과 문자열 인코딩 (UTF-8 등)을 명시할 수 있다. 
  • content-length: 요청과 응답 메시지의 본문 크기를 바이트 단위로 표시한다. 메시지 크기에 따라 자동으로 만들어진다. 
  • host: 서버의 도메인 네임이 나타나는 부분이다. host 헤더는 반드시 하나 있어야한다. 

 


Note

  • 스레드에서 run()을 구현한 다음 start()만 호출하면 run()은 자동으로 실행된다. 

PostgreSQL의 자료형 Numeric이란?

  • 정수나 소수를 저장 가능하다. 
  • 함수와 같이 precision, scale 변수를 통해 설정하려는 타입 값을 전달 가능하다. 
    • ex) numeric(20, 5): 소수점 앞에 20개의 숫자 표시 가능하고 소수점 뒤에는 5개의 숫자를 표현 가능하다. 
  •  고정 소수점을 저장하는 데이터 타입 중 하나이다. 정확한 십진수를 표현 가능하다. (부동 소수점과 대비된다.)

 

 

Java의 자료형 BigDecimal이란?

  • Java에서 숫자를 정밀하게 저장하고 표현할 수 있는 방법이다. 
  • Java에서 소수점을 저장할 수 있는 가장 큰 타입은 double이다. 
  • 숫자에 민감한 금융 거래 시, BigDecimal은 필수이다. 
  • BigDecimal은 double에 비해서 표현 가능한 길이가  2배 이상 길기 때문에 정교하게 연산 가능하다. 
  • 장점: 10진수로 표현하므로 정확성이 높다.
    • double, float의 경우에는 2진수로 표현하므로 10진수로 표현하는 BigDecimal의 정확도가 높다. 
    • 부동 소수점 회피: double, float의 경우에는 부동 소수점 문제가 발생할 수 있다. (double, float에서는 0.1 + 0.2 를 하면 0.3이 정확하게 나오지 않을 수도 있다.) BigDecimal에서는 이런 문제가 발생하지 않는다. 
    • 큰 숫자를 다룰 수 있다. 
  • 단점
    • 계산 비용이 많이 발생해서 속도가 느리다. (double은 연산 비용이 낮아서 속도가 빠름)
    • 메모리 사용량이 크다. (double은 대용량으로 처리할 때 효과적이다. )

 

 

PostgreSQL의 Numeric <-> Java BigDecimal

  • PostgreSQL의 Numeric  필드를 만들기위해서 자바 엔티티에서는 어느클래스를 활용해야될까?
  • Numeric 은  정수만 저장하는 Integer와 다르게 정수 소수까지 저장할 수 있다. 
  • Java의 BigDecimal이라는 클래스를 활용한다.
  • MySQL의  Decimal과 유사하다. 

 


고정 소수점과 부동 소수점의 공통점과  차이점

  •  부동 소수점(floating point)이란?
    • 2진수를 정규화해서 지수부가수부로 나눠 표현한다. 
    • 컴퓨토 공학에서의 정규화는 1.xxx * 2^n 형태로 바꾸는 것을 의미한다. 
    • 실수를 표현할 때 소수점의 위치를 고정하지 않는 것을 말한다. 
    • C, C++, Java 같은 언어에서는 수를 표현하기 위해 정수 타입, 부동 소수점 타입을 제공한다. 
    • 부동 소수점은 고정 소수점보다 훨씬 넓은 범위의 수를  표현할 수 있다. (유효 숫자: 12345, 소수점 위치: 3)
    • 하지만, 항상 오차가 존재한다. 10진수를 정확하게 표현할 수는 없다

출처 -&nbsp;https://gguguk.github.io/posts/fixed_point_and_floating_point/

 

  • 고정 소수점(fixed point)이란?
    • 10진수를 2진수로 바꿔서 저장한다. 정수부와 소수부로 나뉜다. 
    • 자릿수가 제한되므로 표현 범위가 한정된다.
    • ex)  123.456: 정수 123, 소수 456을 나눠서 표현해야한다. 따라서 한정된 비스에서 정수와 소수를 분할해서 배치해야되는 경우 고정 소수점이 나타낼 수 있는 범위가 매우 한정된다.
    • 정수 연산 처럼 연산을 처리하고 부동 소수점보다 빠르다

7.625 를 고정 소수점으로 나타내기

 

참고) 

고정 소수점 사진: https://gsmesie692.tistory.com/94

고정 소수점 사진: https://gguguk.github.io/posts/fixed_point_and_floating_point/

부동 소수점   https://dataonair.or.kr/db-tech-reference/d-lounge/expert-column/?mod=document&uid=52381

 


찾아보기

  • content