。゚(*´□`)゚。

코딩의 즐거움과 도전, 그리고 일상의 소소한 순간들이 어우러진 블로그

[네이버클라우드] 클라우드 기반의 개발자 과정 7기/웹프로그래밍

[NC7기-57일차(7월14일)] - 웹프로그래밍 38일차

quarrrter 2023. 7. 14. 17:42

임계영역 


delay(); 

시간을 끄는거,, 끌다보면 다른 스레드가 가져갈거라 사용함.

 

T1 , T2, T3

스레드는 서로 간섭하지 않구 갈 길 감.

중간에 add()를 호출하면,,,

add 메서드안에서 cpu를 먼저 선점한 스레드가 시작하고 cpu  쟁탈전을 시작해서 작업시작. 

T1이 cpu 뺏겼다해서 T2와 T3만 경쟁하는게 아니라 T1까지 동등한 입장에서 경쟁 

 

Thread: 실행흐름

add: 코드, 메서드 호출될땐 stack에 로컬변수 생성됨. 

각 스레드 별로 stack를 갖고 있음. 자기 실행흐름 진행을 stack에 담고있음. 

로컬변수는 따로 관리함. 

 

인스턴스는 공유함. 

 


Critical Section = Critial Region (임계구역)

void add(int value) {
  if(size >= values.length) {
  	return;
   }
  values[size]  = value;
  size = size + 1;
 }
T1 (111) T2 (222) T3 (333)
JVM Stack JVM Stack JVM tack
value[111]    
Heap

value에 값 넣고 stack의 로컬변수 사라짐
T1이 진행하다가 중간에 끊겨서 T3로 넘어가면 ....
이미 111이 들어있지만 heap은 모든 메모리가 공유하기 때문에 코드에 따라 111에 333 덮어씀
아까 멈췄던 곳에서 다시 실행. values에 값을 넣지 않고 size +1
T2에게 뺏기고 size3에 t2 값 넣어짐.

여러 스레드가 같은 메모리에 접근하여 값을 변경할 때 문제가 발생할 수 있다. 

 = > 이렇게 여러 스레드가 동시에 실행할 때 문제가 발생할 수 있는 코드블럭을... "Critical Section" 이라 부른다. 

 

* 여러 스레드가 같은 메모리를 동시에 접근하더라도 단지 메모리의 값을 조회만 한다면 문제가 발생하지 않는다.

=> 이런 코드 블럭을 "Thread Safe" 스레드 안전이라 부른다.

 

해결책: synchronized 

한 번에 한 스레드만 진입할 수 있다. 

다른 스레드는 먼저 진입한 스레드가 메서드 호출을 마칠때까지 기다려야한다. 

코드 블럭의 진입 개수를 제어하는 것을 : Semaphore (n)

semaphore(1) = mutex 뮤텍스: mutual Exclusion(상호 배제) ex) 선풍기 바람세기. 라디오 채널 

자바는 뮤텍스만 지원한다. (semaphore(2이)가 안 된다는 뜻임 ; 

순차적으로 실행한다는 것은 동시 실행의 이점을 버리는 것이기 때문에 스레드를 사용하기 전의 상태와 같다.
 기존의 실행 방식 처럼 실행 시간이 많이 걸린다.

 

synchronized void add (int value) {
	if (size >= values.length) {
    	return;
     }
     values[size]= value;
     size = size +1;
   }

=> 실행 완료된 스레드는 가비지가 된다.

=> 가비지 컬렉터가 가비지가 된 스레드를 수집하여 해제시키기 전까지는

그 스레드를 위해 할당된 메모리를 사용할 수 없다.

=> 즉 스레드를 매번 생성하는 방식은

과다한 가비지를 생성하기 때문에 메모리 낭비를 일으킨다.

스레드는 실제 OS가 생성한다. 즉 스레드를 생성하는데에 시간이 소요된다.

스레드 재사용 - 작업이 끝나면 대기상태로 만들어서 안 끝나게 만들기 

wait() / notify()

-wait는 synchronized랑 같이 쓰여야함. 

run에는 synchronized 붙이지 말기 

 

wait()

- 해당 객체에서 notify() 통해 알림이 때까지 스레드의 실행을 멈추게 한다.

- 메서드는 동기화 블록

(한 번에 스레드만이 진입하도록 설정된 블록)에서만 호출할 있다.

 

문법 주의!

=> wait()/notify() 반드시 동기화 영역 안에서 호출해야 한다.

 

동기화 영역?

=> synchronized로 선언된 메서드             ) synchronized void m() {}

=> synchronized로 묶인 블록                     ) synchronized(접근대상) {...}

// 스레드 재사용 - 4단계) wait()/notify() 사용
package com.eomcs.concurrent.ex6;

import java.util.Scanner;

public class Exam0140 {

  public static void main(String[] args) {

    class ValueBox {
      int count;

      synchronized  public void setCount(int count) {
        this.count = count;

        // 이 객체의 사용을 기다리는 스레드에게 작업을 시작할 것을 알린다.
        //synchronized (this) {
        this.notify();
        //}
        // 문법 주의!
        // => notify()도 동기화 영역에서 호출해야 한다.
        // => 안그러면 IllegalMonitorStateException 예외가 발생한다.
      }
    }

    class MyThread extends Thread {
      ValueBox valueBox;

      public void setValueBox(ValueBox valueBox) {
        this.valueBox = valueBox;
      }

      @Override
      public void run() {

        System.out.println("스레드 시작했음!");
        try {
          while (true) {
            System.out.println("스레드 대기중...");


            synchronized (valueBox) {
              valueBox.wait();
            }
            System.out.println("카운트 시작!");
            for (int i = valueBox.count; i > 0; i--) {
              System.out.println("==> " + i);
            }
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }

    ValueBox valueBox = new ValueBox();

    MyThread t = new MyThread();
    t.setValueBox(valueBox);
    t.start(); // 이 스레드는 main 스레드가 실행하라고 신호를 줄 때까지 기다린다

    Scanner keyScan = new Scanner(System.in);

    while (true) {
      System.out.print("카운트? ");
      String str = keyScan.nextLine();
      if (str.equals("quit")) {
        break;
      }
      valueBox.setCount(Integer.parseInt(str));
      // setCount()
      // - 사용자가 입력한 카운트 값을 설정할 때
      // - main 스레드는 이 객체의 사용을 간절히 기다리는 다른 스레드에게 
      //   즉시 사용하라고 신호를 보낸다.
      // - setCount() 메서드의 코드를 확인해 보라!
    }

    System.out.println("main 스레드 종료!");
    keyScan.close();
  }
}

44. 스레드 재사용하기: 스레드풀(thread pool) 구현

스레드를 재사용하는 방법

GoF의 Flyweight 디자인 패턴을 적용하여 스레드풀을 구현하는 방법

 

1. 스레드풀 적용 전

클라이언트가 접속하면 서버앱에서 클라이언트 요청을 처리할 스레드를 생성한다.

클라이언트 응답을 완료하면 스레드는 가비지가 된다. 

클라이언트 요청 수 만큼 스레드 생성, 가비지 생김, 시간낭비, 메모리 낭비 

2. 스레드풀 적용 후 

클라이언트1가 접속하면 서버앱이 스레드풀에서 get thread하고, 스레드풀은 스레드1 생성 후 리턴함. 

클라이언트2가 접속하면 똑같이 작동 후 스레드2 리턴함. 

 

3. 스레드 반납 Flywieght 패턴

C1이 T1 에 요청, T1 응답 후 T1이 스스로 Threadpool에 return Thread()함.

C2가 요청하면 T1 리턴해서 내줌 

- 객체 사용 후 가비지를 버리지 않고 컬렉션에 보관.

- 객체가 필요할 때 컬렉션에 보관된 객체를 리턴함으로써 객체 재사용! => Flywieght 패턴- pooling 기법

=> 객체 생성 시간이 오래 걸리는 경우 (대표: 스레드 , 디비커넥션,)

=> 동일한 객체를 반복적으로 사용하는 경우  


프로젝트

server- util 추가 

RequestProcessJob은 이번 프로젝트 전용 ㅎㅎ 나머지는 다른 곳에서도 사용 가능.

다른 프로젝트에서도 사용하라고 껍데기들을 많이 만듬