이전 포스팅 에선 n개의 스레드를 생성하여 실행하여 보았습니다.


작업의 동시성을 위해 thread는 매우 유용하게 사용되는데요,
하지만 공유되는 자원에 대해서 동기화 처리가 되지 않는다면 문제를 일으킬 수 있습니다.
이번 포스팅에선 thread를 동기화하여 안전하게 공유 자원을 사용하는 방법에 대해서 알아 보도록 하겠습니다.

 


비동기화

먼저, 동기화 처리가 되지 않는 thread의 결과값을 확인해보겠습니다
'이것이 자바다'의 나오는 예제입니다.

Calculator

public class Calculator {

    private int memory;

    public void setMemory(int memory) {
        this.memory = memory;

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ": " + this.memory);
    }
}

User1

public class User1 extends Thread {

    private Calculator calculator;

    public void setCalculator(Calculator calculator) {
        this.setName("User1"); // set thread name
        this.calculator = calculator;
    }

    public void run() {
        calculator.setMemory(100);
    }
}

User2

public class User2 extends Thread {

    private Calculator calculator;

    public void setCalculator(Calculator calculator) {
        this.setName("User2"); // set thread name
        this.calculator = calculator;
    }

    public void run() {
        calculator.setMemory(50);
    }
}

Main

public class MainThreadExample {

    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        User1 user1 = new User1();
        user1.setCalculator(calculator);
        user1.start();

        User2 user2 = new User2();
        user2.setCalculator(calculator);
        user2.start();
    }
}

결과

User1: 50
User2: 50

User1은 setMemory에서는 100을, User2 setMemory에서는 50을 설정하였습니다.
그러나 User1의 결과는 100이 아닌 50이 설정되었습니다.

그이유는 즉슨, User1과 User2 Calculator를 공유하여 사용하고 있습니다
다만, memory set후 바로 출력하는것이 아닌 2초간의 sleep time이 있기 때문입니다
따라서 원하는 값을 얻기 위해서는 공유 자원의 대한 제어가 필요합니다


동기화 블록 설정

스레드가 사용중인 객체를 다른 스레드가 변경할 수 없도록 하려면 스레드 작업이 끝날떄 까지
객체에 잠금을 걸어 다른 스레드가 사용할 수 없도록 해야합니다.


이처럼 멀티스레드 프로그램에서 단 하나의 스레드만 실행할 수 있는 코드 영역을 임계 영역(critical section)이라고 합니다.
자바는 임계 영역을 지정하기 위해 동기화 메서드블록을 제공합니다

동기화 메서드

public synchronized void setMemory(int memory) {
    this.memory = memory;

    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(Thread.currentThread().getName() + ": " + this.memory);
}

동기화 블록

public void setMemory(int memory) {
    synchronized (this) {
        this.memory = memory;

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ": " + this.memory);
    }
}

이외에도 'volatile' 이라는 키워드가 있습니다.
'volatile'은 변수에 적용하여 Cache memory가 아닌 main memory에서 동작하게 설정됩니다.
즉, 멀티스레드 환경에서 임계영역이 아닌 main memory에서 동작하기 때문에
여러 스레드가 공유 자원에 대해 동시에 '읽기-쓰기' 작업을 진행한다면 원자성을 보장해주지 않습니다.

따라서, 위와 같은 공유자원에 여러 스레드가 '읽기-쓰기' 작업을 수행한다면,
임계영역을 보장해주는 synchronized 키워드를 사용해야 합니다

참조

이것이 자바다 - 신용권님 저

블로그 이미지

사용자 yhmane

댓글을 달아 주세요

들어가며

우리는 하나의 프로그램, 프로세스로 두 가지 이상의 작업을 처리할 수 있다는 것을 알고 있습니다.
스레드를 이용하면 두가지 이상의 일을 동시에 할 수 있는데요,
이번 포스팅에선 Java를 이용해서 스레드를 생성하는 방법을 알아보도록 하겠습니다.


프로세스와 스레드

스레드를 다루기 전에 필수적인 용어만 짚고 넘어가도록 하겠습니다

  • 프로그램은 파일 시스템에 존재하는 실행파일 (*.exe)
  • 프로세스는 운영체제에서 실행중인 애플리케이션
  • 스레드는 프로세스 내에서 실행되는 여러 흐름의 단위

간단히 정리하면, 프로그램은 실행파일입니다.
이 실행파일은 메모리에 올린 것은 프로세스라고 하는데요
프로세스는 n개의 thread로 구성되어 있습니다.

운영체제의 대한 추가적인 설명은 생략하도록 하고, 자세히 설명되어 있는 링크를 달아두도록 하겠습니다.
스레드, 프로세스의 대한 개념 정리가 필요하시다면 한번씩 읽어보세요 😄
Process와 Thread 이야기. 프로세스(Process) | by Charlezz | Medium


Java Thread 생성

Java에서 Thread를 생성하는 방법은 두가지가 존재합니다

  1. Thread 클래스를 상속 받아 run 메서드를 오버라이딩 하는것
  2. Runnable 인터페이스를 Implements 하여 run 메서드를 정의하는것

먼저, Thread 상속

public class MyThread extends Thread {
    private int index;
    public MyThread(int index) {
        this.index = index;
    }

    @Override
    public void run() {
        System.out.println(this.index + " thread start");
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(this.index + " thread end");
    }
}

다음으로, Runnable 인터페이스 implements

public class MyRunnable implements Runnable {

    private int index;
    public MyRunnable(int index) {
        this.index = index;
    }

    @Override
    public void run() {
        System.out.println(this.index + " thread start");
        try {
            Thread.sleep(1000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(this.index + " thread end");
    }
}

실행

public class MyThreadMain {
    public static void main(String[] args) throws  {
          // thread
        for (int i = 0; i < 10; i++) {
            Thread myThread = new MyThread(i);
            myThread.start();
        }

          // runnable
        for (int i = 10; i < 20; i++) {
            Runnable runnable = new MyRunnable(i);
            Thread runnableThread = new Thread(runnable);
            runnableThread.start();
        }
    }
}

이외에도 단순한 Thread의 처리라면 익명클래스나 람다를 이용하는 방법도 있습니다.
Java 8버전 이상을 사용하신다면 익명클래스 보단 람다 사용을 권장합니다!!

new Thread() {
    @Override
    public void run() {
        System.out.println("anonymous class thread");
    }
}.start();

new Thread(() -> System.out.println("lambda thread")).start();

 


start vs run

우리는 run() 메서드를 재정의하였지만, 정작 thread 사용시에는 start() 메서드를 이용하였습니다.
그 이유는 jvm의 메모리 구조때문인데요, 아래의 사진을 잠시 살펴보도록 하겠습니다.

jvm은 ‘data, code, stack, heap’ 영역으로 구성되어 있습니다.
여기서 프로세스 내부의 thread들은 data, code, heap 영역을 공유하게 됩니다.

다만, run() 메서드는 메인쓰레드의 call-stack을 공유하여 작업을 하지만
start() 메서드는 각 쓰레드마다 call-stack을 새로 만들어 작업을 합니다.

run() 메서드는 thread가 순번을 기다리며 대기하고 있기에 하드웨어의 성능을 적절히 사용할 수 없습니다.
따라서 쓰레드의 작업을 적적히 분배하기 위해서는 start() 메서드를 이용해야 합니다


출처

블로그 이미지

사용자 yhmane

댓글을 달아 주세요

Spring CORS 설정하기

이번 포스팅에서는 CORS에 대해서 간단한 개념을 짚어보고 Spring을 통해서 CORS 정책 설정 방법을 알아보도록 하겠습니다
자세한 개념은 아래 페이지에 정리가 잘 되어 있으니 프론트/백엔드 상관없이 웹개발자분들은 한번씩 읽어 보시는 것을 추천드립니다 🤗
교차 출처 리소스 공유 (CORS) - HTTP | MDN


CORS란?

먼저, 아래 사진의 에러 내용을 살펴보도록 하겠습니다.

http://localhost:3000' -> ‘http:localhost:8080/todo’를 호출하였더니 위와 같은 메세지가 출력되었습니다. 빨간 네모박스 안에 메세지를 보면, CORS 정책에 의해 접근이 막혔다는 메시지가 나와 있습니다. CORS란 무엇일까요?

 

CORS란 ‘Cross-Origin Resource Sharing’의 약자로 ‘교차 출처 리소스 공유’로 번역됩니다. 웹브라우저 에서 다른 출처의 자원을 공유하는 방법입니다.

 


Origin 이란?

Origin은 출처라는 의미로 번역되는데 일반적으로 '동일/교차' 출처라는 의미로 사용됩니다.
여기서 말하는 Origin은 아래와 같이 표현됩니다
https://yhame.tistory.com

  • 프로토콜 : https
  • 호스트 : yhmane.tistory.com
  • 포트 : 443 (80, 443 포트는 생략이 가능합니다 😀)

즉, 여기서 말하는 Origin은 우리가 흔히 얘기하는 URL 구조에서 '프토토콜 + 호스트 + 포트'를 합친것과 같습니다

Same Origin / Cross Origin

위에서 알아본 동일/교차 출처에 대해서 url과 비교하여 동일출처인지 다른출처인지 비교해보도록 하겠습니다. 기준 url은 아래와 같습니다

# 기준 url
https://yhmane.tistory.com
URL 출처 내용
https://yhmane.tistory.com/category 동일출처  protocol, host, port 동일
https://yhmane.tistory.com/198?category=769262 동일출처 protocol, host, port 동일
http://yhmane.tistory.com/category 다른출처 protocal이 다름
https://yhmane.tistory.com:8081/category 다른출처 port가 다름
https://github.com/yhmane 다른출처 host가 다름

※ 동일출처의 경우 SOP (Same-Origin Policy)라 웹브라우저 상에서 보안상 이슈가 없지만, 교차출처의 경우 보안상 이슈로 인해 웹브라우저상에서 에러메시지가 처리됩니다. 결국, 이러한 문제를 해결하기 위해서는 서버(Spring, Node 등등)에서 CORS를 처리해주어야 합니다.


Spring CORS 설정하기

Spring에서는 크게 2가지 방식으로 CORS를 설정할 수 있습니다.

  1. @Configuration 설정 방법
  2. @CrossOrigin 설정 방법

일반적으로는 1번 @Configuration 설정 방법을 많이 사용합니다

@Configuration 이용 방법

먼저, @Configuration을 이용하여 전역으로 처리하는 방법을 알아보겠습니다.

* Java

@Configuration
public class CorsConfig implements WebMvcConfigurer {

	@Override
	public void addCorsMappings(CorsRegistry registry) {
		registry.addMapping("/**")
			.allowedOrigins("http://127.0.0.1:8080")
			.allowedMethods(
				HttpMethod.GET.name,
				HttpMethod.POST.name,
				HttpMethod.PUT.name,
				HttpMethod.DELETE.name
			)
	}
}

* Kotlin

@Configuration
class CorsConfig : WebMvcConfigurer {

	override
	fun addCorsMappings(registry: CorsRegistry) {
		registry.addMapping("/**")
			.allowedOrigins("http://localhost:3000")
			.allowedMethods(
				HttpMethod.GET.name,
				HttpMethod.POST.name,
				HttpMethod.PUT.name,
				HttpMethod.DELETE.name
			) 
	} 
}
  • addMapping
    CORS 적용할 Url 패턴을 정의합니다. 모든 url에 대한 접근을 허용할 경우 위와 같이 ‘/**’로 설정합니다.
  • allowedOrigins
    위에서 Origin은 (Protocol + host + port)의 조합이라고 들었습니다. 따라서, 허용하고자 하는 Origin을 적어주시면 됩니다. 마찬가지로 chaining도 가능합니다
  • allowedMethods
    허용하고자 하는 method를 적어주시면 됩니다.

@CrossOrigin 이용 방법

어노테이션 이용방법은 조금 더 간단합니다.
해당 클래스나 메서드위에 @CrossOrigin 어노테이션을 붙여주면 됩니다.

* Java

@CrossOrigin("http://localhost:3000")
@RestController
@RequestMapping("/todo")
public class TestController {
	
	//@CrossOrigin("http://localhost:3000")
	@GetMapping public String test() {
		// test ..
	}
}

* Kotlin

@CrossOrigin("http://localhost:3000")
@RestController
@RequestMapping("/todo")
class TestController {

	//@CrossOrigin("http://localhost:3000")
	@GetMapping fun test() {
		// test ..
	}
}

정리

  • 웹브라우저를 통해 동일출처가 아닌 교차출처를 이용해야 할 경우 CORS 설정을 해야 합니다
  • Origin은 (프로토콜 + 도메인 + 포트)로 구성됩니다
  • Spring에서 CORS 설정은 전역, @CrossOrigin을 이용해서 설정합니다
  • 이외에도 필터를 이용해 CORS 설정을 해줄수도 있습니다

출처

교차 출처 리소스 공유 (CORS) - HTTP | MDN
Cross Origin Resource Sharing - CORS - 뒤태지존의 끄적거림
baeldung

블로그 이미지

사용자 yhmane

댓글을 달아 주세요

요즘 코드를 짜는 시간보다 업무를 분석하는 시간이 많아졌다
정책은 뭐 이리 많은지 ㅠㅠ ... 히스토리가 끊긴 프로젝트를 이어 받아서 하는중인데
나도 모르게 한숨이 많아 졌나 보다. 주변에서 괜찮냐고 물어보는데 허허
알게 모르게 스트레스를 만땅으로 받고 있는거 같다

남는 시간에 공부를 했었는데, 최근엔 스트레스 해소를 위해 새로운 취미를 시작해볼까 한다
개발 공부도 중요하지만 업무로 인한 스트레스를 잘 날려버릴 수 있도록
대체제를 잘 찾아봐야 겠다


비공개로 글을 썼었는데, 한달만에 새로운 취미를 찾아서 기쁜 마음에 다시 글을 이어서 쓴다!!
내가 찾은 새로운 취미는 볼링이다!!!
이제 100정도 치는거 같다 ㅋ.ㅋ
그래서 그런지 출퇴근길 볼링 유튜브 보는게 요즘 낙인거 같다 ㅎㅎ

올해안에 에버레이지 150 만드는게 목표다!!!

블로그 이미지

사용자 yhmane

댓글을 달아 주세요

인덱스 생성

ElasticSearch는 REST를 지원하기 때문에 여러가지 방법을 이용하여 인덱스를 다룰 수 있습니다.
크게 다루는 방법은 3가지입니다

  • CURL
  • kibana
  • 언어의 라이브러르 이용

여기서는 kibana 쿼리를 이용하여 인덱스를 생성해 보도록 하겠습니다

PUT test_index
{
  "mappings": {
   "_doc": {
      "properties": {
        "testId": {
          "type": "long"
        },
        "testName": {
          "type": "keyword"
        }
      }
    }
  },
  "settings": {
    "index": {
      "refresh_interval": "1s",
      "number_of_shards": "1",
      "number_of_replicas": "1"
    }
  }
}

인덱스의 정보를 위와 같이 정의하였습니다. 사용될 문서의 정보와 샤드 레플리카 정보들로 간단히 설정하였습니다.


Document 생성

마찬가지로 인덱스와 같이 여러 방식을 이용할 수 있습니다.
여기서는 kibana 쿼리를 이용하였습니다

PUT test_index/_doc/1
{
  "testId" : 1,
  "testName" : "yunho"
}

참조

https://esbook.kimjmin.net/04-data/4.2-crud

블로그 이미지

사용자 yhmane

댓글을 달아 주세요