FeGwan-Training / FeGwan

0 stars 0 forks source link

ETC ... Thread #13

Closed HoFe-U closed 1 year ago

HoFe-U commented 1 year ago

Discussed in https://github.com/FeGwan-Training/FeGwan/discussions/12

Originally posted by **HoFe-U** May 31, 2023

Thread 란 ??

일반적으로 Thread 를 알기위해서는 Process 를 알아야합니다. 그럼 우리가 생각하는 Process 란 뭘까요???

자 그럼 위의 Process 가 하나의 Thread 로 이루어진것을 알았으니 다음을 확인해봅시다.

Process 는 최소하나의 Task(작업) 으로 이루어져있습니다.

그럼 이런 작업을 실행하는 주체를 Thread 라고합니다.

스크린샷 2023-05-29 오후 4 07 18

모르는 내용이너무 많지않나요??? 조금만더 정리해봅시다

앞서 Process 가 뭐라고했는지 기억하죠??

→ 그럼 Thread 의 자원은 누가 할당해줄까요??? Process 에는 어떠한 정보들이 들어있을까요??

1. Process 의 자원할당

자원할당

  • Process 는 운영체제에 의해 CPU에 할당되어 실행됩니다

    —> 어떤방식으로???? 방식이 정해져있나???

  • 위와 같은 방식들을 처리해주는게 Scheduler 입니다. ~~이 부분을 이야기하면 너무 OS 쪽으로 깊게 해야해서 일단 배제하겠습니다.~~

    —> 그럼 Process 의 내부 자원은 어떻게 구성되어 있나??

    스크린샷 2023-05-29 오후 3 52 18

Process 에 있는 데이터 정보들은

  1. Stack
    • 함수 호출, 지역 변수, 매개 변수 등을 관리하기 위한 함수 호출 및 복귀 주소, 지역 변수 등을 저장
  2. Heap
    • 프로세스가 실행 중에 동적으로 메모리를 할당하고 해제하는 데 사용(우리가 생각하는 객체)
  3. Data
    • 프로세스는 프로그램의 전역 변수, 정적 변수, 상수 등의 데이터를 포함 (세그먼트에 저장)
  4. Text(Code)
    • 우리가 작성한 code 들이 기입(Read-only 로 구성)
  5. Register(정확히는 CPU 에 포함되어있다)
    • 프로세스는 CPU 내부의 레지스터를 사용하여 데이터를 저장하고 처리합니다. 이는 연산에 필요한 피연산자들이 저장되고, 연산 결과도 임시로 저장
  6. PCB(Process Control Block)
    • 프로세스 식별자(PID) : 프로세스의 PK 라고 보면됩니다

    • 프로세스 상태(Process Status) : 프로세스의 현재상태 표시

      스크린샷 2023-05-29 오후 4 12 44

    • 프로그램 카운터(Programe Counter) : 프로세스가 다음 실행할 명령어 주소를 가르키는 레지스터의 값

    • 레지스터 값: 프로세스의 레지스터 상태를 저장

    • 스케줄링 정보: 프로세스의 우선순위, 스케줄링 알고리즘에 필요한 정보

    • 할당된 자원 정보: 프로세스가 할당 받은 메모리, 입출력 장치, 파일 등의 자원 정보를 저장

    • 프로세스 계정 정보: 프로세스의 사용 시간, CPU 시간, 생성 시간 등의 계정 정보를 포함

마무리 정리

  1. Process 는 프로그램이 실행되는것을 Process 라고하고 좀더 명확하게 표현하면 Programe 의 instance 를 Process 고도 할 수있겠다
  2. Process 는 Register, Stack , Heap 등의 여러가지 구조로 이루어져있다
  3. 프로세스의 정보들은 PCB에 할당되어있다.

2. Thread 의 자원할당

자 이제 맨처음 나왔던 그림을 다시 보자 Thread 부분 말고는 왜 저런 그림으로 되어있는지 알수있을것이다.

그럼 이제 Thread 에 대해서 알아보자

  • Thread 란 뭐라고?(간략하게)

    프로세스의 최소단위의 작업(Task)를 실행하는것을 Thread 라고한다.

자원할당

Thread 는 앞서 말한것처럼 Process 와 관련이있기 때문에

공유자원과 각각 독립적인 자원이 존재할수 있슴다.

Thread 이미지 (왼쪽이 single 오른쪽이 mulitThread)

스크린샷 2023-05-29 오후 5 31 03

  • 공유자원
    1. 메모리
      • 프로세스의 메모리 공간을 공유합니다. 따라서, 스레드는 동일한 코드, 데이터, 힙을 공유하며, 이를 통해 변수, 배열, 객체 등의 데이터를 공유
    2. 파일 디스크립터
      • 여러 스레드는 동시에 파일에 대한 I/O 작업을 수행가능, 컴퓨터 시스템에서 파일이나 I/O 장치를 식별하고 조작하기 위해 사용되는 정수 값이라고 봐도 무방하다
    3. 시그널 핸들러
      • 프로세스의 시그널 핸들러를 공유합니다. 시그널은 프로세스에게 발생한 이벤트를 알리는 메커니즘이며, 스레드는 이를 처리하기 위해 동일한 시그널 핸들러를 사용가능
    4. 열린 소캣
      • 스레드는 프로세스의 열린 소켓을 공유합니다. 이는 네트워크 통신 작업을 스레드 간에 공유가능
  • 독립자원
    1. Thread Local 변수(이놈은 저희가 나중에 직접쓸수도 있습니다)

      • Thread Local은 Java에서 스레드 간에 데이터를 공유하지 않고, 각 스레드별로 독립적으로 유지하고자 할 때 사용되는 기능이고

      • Thread Local은 각 스레드마다 별도의 변수 인스턴스를 제공하며, 해당 변수는 스레드 내에서만 접근할 수 있습니다.

      • 각 스레드는 Thread Local 변수에 값을 설정하고 가져오는 메서드를 사용하여 독립적인 상태를 유지할 수 있습니다.

      • 예를 들어, 스레드 간에 로깅 정보, 트랜잭션 컨텍스트, 사용자 인증 정보 등을 공유하지 않고 스레드 내에서만 유지하고자 할 때 Thread Local을 사용할 수 있습니다

      • 이때 Thread Local 사용시 remove 를 하지 않는다면 어떻게 될까??

      • 코드

        public class ThreadLocalExample {
            private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
        
            public static void main(String[] args) {
                // 스레드 1
                Thread thread1 = new Thread(() -> {
                    threadLocal.set(1);
                    try {
                        TimeUnit.SECONDS.sleep(2); // 2초 대기
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread 1: " + threadLocal.get()); // 1 출력
                });
        
                // 스레드 2
                Thread thread2 = new Thread(() -> {
                    threadLocal.set(2);
                    System.out.println("Thread 2: " + threadLocal.get()); // 2 출력
                });
        
                // 스레드 3
                Thread thread3 = new Thread(() -> {
                    threadLocal.set(3);
                    System.out.println("Thread 3: " + threadLocal.get());
                });
        
                // 각 스레드 시작
                thread1.run();
                thread2.start();
                thread3.start();
        
                try {
                    thread1.join();
                    thread2.join();
                    thread3.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        
                // 메인 스레드에서의 값 출력
                System.out.println("Main Thread: " + threadLocal.get()); // 1 출력
        
        //        thread1.run(); // remove 해야하는 예시
        //        
            }
        }
        
    2. Thread Stack

      • Java에서 각 스레드는 독립적인 실행 흐름을 위해 별도의 스택(Stack)을 가지고 있습니다.

      • 스레드 스택은 함수 호출과 관련된 정보, 지역 변수, 매개 변수 등을 저장하는 데 사용됩니다.

      • 각 스레드의 스택은 스레드의 실행 흐름을 관리하고, 스레드마다 독립적인 함수 호출과 지역 변수의 스코프를 지원합니다.

      • 스레드 스택은 자동으로 생성되고 관리되며, 스레드의 수명 주기와 함께 할당 및 해제됩니다.

      • 스레드 스택은 JVM에 의해 관리되며, 스택 오버플로우와 같은 문제가 발생할 수 있습니다. JVM은 스택 오버플로우를 감지하고 예외를 발생시켜 프로그램의 안정성을 유지합니다.

      • 코드

        
        import java.util.concurrent.TimeUnit;
        
        public class ThreadLocalExample {
            private static ThreadLocal<Integer>threadLocal= new ThreadLocal<>();
        
            public static void main(String[] args) {
        //스레드 1
        Thread thread1 = new Thread(() -> {
        threadLocal.set(1);
                    try {
                        TimeUnit.SECONDS.sleep(2);// 2초 대기
        } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Thread 1: " +threadLocal.get());// 1출력
        });
        
        //스레드 2
        Thread thread2 = new Thread(() -> {
        threadLocal.set(2);
                    System.out.println("Thread 2: " +threadLocal.get());// 2출력
        });
        
        //스레드 3
        Thread thread3 = new Thread(() -> {
        threadLocal.set(3);
                    System.out.println("Thread 3: " +threadLocal.get());
                });
        
        //각 스레드 시작
        thread1.start();
                thread2.start();
                thread3.start();
        
                try {
                    thread1.join();
                    thread2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        
        //메인 스레드에서의 값 출력
        System.out.println("Main Thread: " +threadLocal.get());// null출력
        }
        }
        

        상단의 코드를 보면 threadLocal 이 다른 Thread 에 영향을 주지않는것을 볼 수 있다

    3. PC(Programe Counter) Register

      • PC는 다음에 실행할 명령어의 주소를 가리키는 역할을 하므로, 스레드마다 PC의 값은 서로 다를 수있음
      • 스레드는 자신만의 레지스터 세트를 가지며, 이를 통해 각각의 스레드는 자신의 데이터를 저장하고 연산을 수행합니다. 스레드 간에는 레지스터 값을 공유하지 않으며, 독립적으로 작업을 수행

마무리 정리

  • 왜??? Thread 가 공유자원과 독립자원으로 나누어져있을까??

    로컬 변수와 스레드 스택은 각각 스레드의 독립성을 보장하고, 동시에 실행되는 여러 스레드가 각자의 상태와 실행 흐름을 유지할 수 있도록 도와줍니다.

    이러한 독립적인 자원을 가지는 것은 스레드 간의 상호작용과 동시성 문제를 다루는 데 유용합니다

  • Thread 는 공유자원과 독립자원으로 나누어져있다

  • Thread 의 공유자원은 Process 의 자원들을 공유한다. 독립자원은 각각의 Thread 가 개별적으로 가지고있는 자원이다.


Java 에서 Thread 를 사용한는 방법

java 에서 Thread 를 사용하는 방법은 대표적으로 3가지? 정도가 있는것같다

더있는지는 잘..모르겠슴다.. ㅎㅎ😄

1. Thread 상속

  1. 첫번쨰로 Thread 클래스를 extends 받아서 처리하는 방법이 있다

    public class MyThread extends Thread {
        public void run() {
            // 스레드 동작을 구현
        }
    
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            myThread.start();
        }
    }
    

    위처럼 Thread 를 상속받아서 MyThread 클래스는 run() 메서드를 오버라이딩하여 스레드의 동작을 구현하고, start() 메서드를 호출하여 스레드를 실행

  2. Runnable 클래스 인터페이스를 구현한 클래스를 생성하여 동작을 정의하는 방법

    public class MyRunnable implements Runnable {
        public void run() {
    //스레드 동작을 구현
    }
    
        public static void main(String[] args) {
            MyRunnable myRunnable = new MyRunnable();
            Thread thread = new Thread(myRunnable);
            thread.start();
        }
    }
    
    

    위처럼 Runnable 을 구현하여 start 로 실행.

  3. Lambda 표현식으로 Runnable 구현

    public class LambdaThreadExample {
        public static void main(String[] args) {
            Runnable runnable = () -> {
                // 스레드 동작을 구현
            };
    
            Thread thread1 = new Thread(runnable);
            thread1.start();
    
            Thread thread2 = new Thread(()->{
                System.out.println(Thread.currentThread());
            });
            thread2.start();
        }
    }
    

    람다식으로 Runnable 을구현하여 실행하거나 파라미터로 바로 넣어서 실행( 동작 파라미터 기억하죠?)

위의 3가지 예제를 볼때 궁금증이 생겨야합니다

  1. 왜 Thread run을 상속받았는데 다 thread run 을하지않고 start 를 했나
  2. Runnable 과 Thread 를 상속받는거에 어떠한 차이점이 있나?

정도의 궁금증이 생겨나야합니다.

2. Thread. start() 와 Thread.run() 의 차이

결론부터 말씀드리면

<aside> ⚠️ start() 를 사용하면 새로운 Thread 가 생성되고 run() 이 실행된다. run() 을 사용할경우 기존의 Thread 를 생성하지않는다

</aside>

로 알수있겠습니다. 코드를 볼까요??

  1. start() 사용시

    class MyThread extends Thread {
       public void run()
       {
          System.out.println("Current thread name: "
                      + Thread.currentThread().getName());
          System.out.println("run() method called");
       }
    }
    
    class Main {
       public static void main(String[] args)
       {
          MyThread t = new MyThread();
          t.start();
       }
    }
    
    

    위와 같은 코드에서의 결과는

    Current thread name: Thread-0
    run() method called
    

    이라는 결과가 나옵니다. 즉 Thread-0 이라는 Thread 가 생성이 된다음 run() 메서드를 호출합니다.

  2. run() 사용시

    class MyThread2 extends Thread {
        public void run()
        {
            System.out.println("Current thread name: "
                + Thread.currentThread().getName());
    
            System.out.println("run() method called");
        }
    }
    
    class Main2 {
        public static void main(String[] args)
        {
            MyThread t = new MyThread();
            t.run();
        }
    }
    

    위의 코드에서 결과는

    Current thread name: main
    run() method called
    

    이라는 결과가 나오는데

    즉 main Thread 를 실행시켰고 새로운 Thread 를 생성하지 않았다는 결과가 나오는겁니다. 따라서 멀티 스레딩이 발생하지 않았다는것을 알수있습니다.

  3. start() 2번 호출시

    class MyThread3 extends Thread {
        public void run()
        {
            System.out.println("Current thread name: "
                + Thread.currentThread().getName());
    
            System.out.println("run() method called");
        }
    }
    
    class Main3 {
        public static void main(String[] args)
        {
            MyThread3 t = new MyThread3();
            t.start();
            t.start();
        }
    }
    

    위의 코드에서

    Exception in thread "main" java.lang.IllegalThreadStateException
        at java.base/java.lang.Thread.start(Thread.java:789)
        at javastudy.personalstudy.thread.different.Main3.main(MyThread3.java:25)
    Current thread name: Thread-0
    run() method called
    

    라는 결과가 나오는것을 알수있습니다. 이를 보면 start() 메서드는 2번호출 될수없다는것을 알수있습니다. 그렇기 때문에 start() 를 2번실행했을때 IllegalThreadStateException 가 발생한것을 알수있습니다.

  4. run() 2번 호출시

    class MyThread4 extends Thread {
        public void run()
        {
            System.out.println("Current thread name: "
                + Thread.currentThread().getName());
            System.out.println("run() method called");
        }
    }
    
    class Main4 {
        public static void main(String[] args)
        {
            MyThread t = new MyThread();
            t.run();
            t.run();
        }
    }
    

    위의 코드에서

    Current thread name: main
    run() method called
    Current thread name: main
    run() method called
    

    이라는 결과가 나오는것을 알수있습니다. run() 메서드를 두 번 호출해도 예외가 발생하지 않고 예상대로 두 번 실행되지만 메인 스레드 자체에서 실행되는것을 알수있습니다.

요약 정리

  | start() | run() -- | -- | -- Thread 생성 | 새로운 Thread 생성 | 새로운 Thread 를 생성하지 않는다. run() 을 호출한놈의 Thread 를 씀 |   |   호출 횟수 | 한번 이상의 호출할수 없다 | 여러번 호출가능 구현 위치 | Thread 에 정의되어있다 | Runnable 인터페이스에서 있는 메서드를 재정의 해야한다. 사용할때 | 멀티스래딩, 비동기처리 | 단일스레드, 테스트 및 디버깅

start() 가 좋냐? run() 이 좋냐? 그런거없슴다. 그냥 언제 어떻게 쓸지에따라서 다르게 쓰이는겁니다.


Mulit Thread . . .

멀티스레드란?

하나의 프로그램에 동시에 여러개의 일을 수행할수 있도록 해주는 것이라고 볼수있다

상단의 Thread 이미지를 다시 보고오자…

자 사실 멀티스레드의 개념은 생각보다 단순하다.

여러개의 Thread 가 하나의 Task 를 처리한다고 한다면 멀티스레딩이다.

사실 여기서 가장 중요한건 동기화 기법 을 어떻게 처리할지 이다.

Mulit Thread 동기화 기법..

  1. Mutex

    • 일단 뮤텍스는 상호배제를 위한 기법인데 하나의 Thread 만 임계영역 critical section 에 진입할수 있도록 도와줍니다. 하단의 코드를 보면서 이해를 해봅시다.

    • 코드

      package javastudy.personalstudy.thread.syncronizedways;
      
      import java.util.concurrent.locks.Lock;
      import java.util.concurrent.locks.ReentrantLock;
      
      public class MutexExample {
          private static int count = 0;
          private static Lock lock = new ReentrantLock();
      
          public static void main(String[] args) {
              // 앞서 설명했듯이 thread 에서 runnable 을 lambda 로 구현
              Thread thread1 = new Thread(() -> {
                  for (int i = 0; i < 1000; i++) {
                      lock.lock(); //<-- 해당자원이 임계영역에 진입하기전 lock 을검
                      try {
                          count++;
                      } finally {
                          lock.unlock(); // <-- 다 끝났을 경우 unlock 으로 해제
                      }
                  }
              });
      
              Thread thread2 = new Thread(() -> {
                  for (int i = 0; i < 1000; i++) {
                      lock.lock();
                      try {
                          count++;
                      } finally {
                          lock.unlock();
                      }
                  }
              });
      
              thread1.start();
              thread2.start();
      
              try {
                  thread1.join();
                  thread2.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              System.out.println("Count: " + count);
          }
      }
      
  2. Semaphore

    • 2가지 연산으로 구성 P (Wait) 연산과 V (Signal) 연산으로 구성

    • 카운터 값이 음수가 될 수 없고, 0 이상의 정수

    • 즉 P (Wait) 연산은 세마포어 카운터를 1 감소시키고, 만약 카운터가 0 이하가 되면 호출한 스레드는 블록됩니다.

      V (Signal) 연산은 세마포어 카운터를 1 증가시키고, 만약 카운터가 0 이상이 되면 대기 중인 스레드 중 하나를 깨웁니다.

    • 스레드 간의 안전한 공유 자원 액세스, 생산자-소비자 문제, 우선순위 스케줄링, 임계 영역 제한 등 다양한 상황에서 유용하게 활용 위에 말이 무슨말이야???? 괜찮습니다 다~~ 예제로 이해가 됩니다잉

    • 코드

      package javastudy.personalstudy.thread.syncronizedways.semaphore;
      
      import java.util.concurrent.Semaphore;
      
      public class SemaphoreExample {
          private static int count = 0;
          private static Semaphore semaphore = new Semaphore(1); // 동시에 접근할수 있는 Thread 개수를 1개로 선정한것
      
          public static void main(String[] args) {
              Thread thread1 = new Thread(() -> {
                  try {
                      semaphore.acquire(); // Semaphore 의 값을 1감소 처음에 1할당한것을 감소시키니까 0 이겠죠?
                      for (int i = 0; i < 1000; i++) {
                          count++;
                      }
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  } finally {
                      semaphore.release(); // 종료됬으니까 Semaphore 의 cnt 값을 증가
                  }
              });
      
              Thread thread2 = new Thread(() -> {
                  try {
                      semaphore.acquire();
                      for (int i = 0; i < 1000; i++) {
                          count++;
                      }
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  } finally {
                      semaphore.release();
                  }
              });
      
              thread1.start();
              thread2.start();
      
              try {
                  thread1.join();
                  thread2.join();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
      
              System.out.println("Count: " + count);
          }
      }
      
  3. Monitor

    • 하나의 객체(데이터) 마다 하나의 모니터가 될수있고 모니터는 객체(데이터) 가 두개이상의 접근할 수 없도록 막는 lock 을 제공하여 동기화를 하는 기법

    • 쉽게 생각하면 데이터(객체)를 모니터링 할수 있는 기법 이라고 볼수있다

    • 코드

      package javastudy.personalstudy.thread.syncronizedways.monitor;
      
      public class ThreadMonitorExample {
      
          private static intcount= 0;
      
          public static void main(String[] args) {
      //주요 스레드 생성
      Thread mainThread = new Thread(() -> {
                  System.out.println("주요 스레드 시작");
      
      //보조 스레드 생성
      for (int i = 1; i <= 5; i++) {
                      Thread subThread = new Thread(() -> {
                          for (int j = 0; j < 400; j++) {
      incrementCount();
                          }
                          System.out.println("보조 스레드 " + Thread.currentThread().getId() + " 종료");
                          System.out.println("보조 스레드 상태 :  " + Thread.currentThread().getState());
                      });
                      subThread.start();
                  }
      
      //보조 스레드들이 모두 종료될 때까지 대기
      while (count< 2000) {
                      try {
                          Thread.sleep(100);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
      
                  System.out.println("주요 스레드 종료");
              });
      
      //주요 스레드 시작
      mainThread.start();
      
      //주요 스레드와 보조 스레드 모니터링
      while (mainThread.isAlive()) {
                  System.out.println("주요 스레드 상태: " + mainThread.getState());
                  System.out.println("현재 count 값: " +count);
      
                  try {
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
      
              try {
                  System.out.println("프로그램 종료");
                  mainThread.join();
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }finally {
                  System.out.println("주요 스레드 상태 : "+ mainThread.getState());
              }
      
          }
      
          private static synchronized void incrementCount() {
      count++;
          }
      }
      
      

PS : 현재 Java 에서는 직접적인 동기화 기법인 synchronized 를 지양하고 Atomic Variable 혹은 conurrent Collection Framework 등을 알아 보면 좋을것같습니다.

## Thread 란 ?? > 일반적으로 Thread 를 알기위해서는 Process 를 알아야합니다. 그럼 우리가 생각하는 Process 란 뭘까요??? 자 그럼 위의 Process 가 하나의 Thread 로 이루어진것을 알았으니 다음을 확인해봅시다. Process 는 **최소하나의 Task(작업)** 으로 이루어져있습니다. 그럼 이런 **작업을 실행하는 주체를 Thread** 라고합니다. 스크린샷 2023-05-29 오후 4 07 18 모르는 내용이너무 많지않나요??? 조금만더 정리해봅시다 **앞서 Process 가 뭐라고했는지 기억하죠??** → 그럼 Thread 의 자원은 누가 할당해줄까요??? Process 에는 어떠한 정보들이 들어있을까요?? ### 1. Process 의 자원할당 **자원할당** - Process 는 운영체제에 의해 CPU에 할당되어 실행됩니다 —> 어떤방식으로???? 방식이 정해져있나??? - 위와 같은 방식들을 처리해주는게 Scheduler 입니다. `~~이 부분을 이야기하면 너무 OS 쪽으로 깊게 해야해서 일단 배제하겠습니다.~~` —> 그럼 Process 의 내부 자원은 어떻게 구성되어 있나?? 스크린샷 2023-05-29 오후 3 52 18 Process 에 있는 데이터 정보들은 1. Stack - 함수 호출, 지역 변수, 매개 변수 등을 관리하기 위한 함수 호출 및 복귀 주소, 지역 변수 등을 저장 2. Heap - 프로세스가 실행 중에 동적으로 메모리를 할당하고 해제하는 데 사용(우리가 생각하는 객체) 3. Data - 프로세스는 프로그램의 전역 변수, 정적 변수, 상수 등의 데이터를 포함 (세그먼트에 저장) 4. Text(Code) - 우리가 작성한 code 들이 기입(Read-only 로 구성) 5. Register(정확히는 CPU 에 포함되어있다) - 프로세스는 CPU 내부의 레지스터를 사용하여 데이터를 저장하고 처리합니다. 이는 연산에 필요한 피연산자들이 저장되고, 연산 결과도 임시로 저장 6. PCB(Process Control Block) - 프로세스 식별자(PID) : 프로세스의 PK 라고 보면됩니다 - 프로세스 상태(Process Status) : 프로세스의 **현재상태** 표시 스크린샷 2023-05-29 오후 4 12 44 - 프로그램 카운터(Programe Counter) : 프로세스가 다음 실행할 명령어 주소를 가르키는 레지스터의 값 - 레지스터 값: 프로세스의 레지스터 상태를 저장 - 스케줄링 정보: 프로세스의 우선순위, 스케줄링 알고리즘에 필요한 정보 - 할당된 자원 정보: 프로세스가 할당 받은 메모리, 입출력 장치, 파일 등의 자원 정보를 저장 - 프로세스 계정 정보: 프로세스의 사용 시간, CPU 시간, 생성 시간 등의 계정 정보를 포함 **마무리 정리** 1. Process 는 프로그램이 실행되는것을 Process 라고하고 좀더 명확하게 표현하면 Programe 의 instance 를 Process 고도 할 수있겠다 2. Process 는 Register, Stack , Heap 등의 여러가지 구조로 이루어져있다 3. 프로세스의 정보들은 PCB에 할당되어있다. ### 2. Thread 의 자원할당 > 자 이제 맨처음 나왔던 그림을 다시 보자 Thread 부분 말고는 왜 저런 그림으로 되어있는지 알수있을것이다. > > > 그럼 이제 Thread 에 대해서 알아보자 > - **Thread 란 뭐라고?(간략하게)** 프로세스의 최소단위의 작업(Task)를 실행하는것을 Thread 라고한다. **자원할당** Thread 는 앞서 말한것처럼 Process 와 관련이있기 때문에 **공유자원과 각각 독립적인 자원이 존재할수 있슴다.** Thread 이미지 (왼쪽이 single 오른쪽이 mulitThread) 스크린샷 2023-05-29 오후 5 31 03 - **공유자원** 1. 메모리 - 프로세스의 메모리 공간을 공유합니다. 따라서, 스레드는 동일한 코드, 데이터, 힙을 공유하며, 이를 통해 변수, 배열, 객체 등의 데이터를 공유 2. 파일 디스크립터 - 여러 스레드는 동시에 파일에 대한 I/O 작업을 수행가능, 컴퓨터 시스템에서 파일이나 I/O 장치를 식별하고 조작하기 위해 사용되는 정수 값이라고 봐도 무방하다 3. 시그널 핸들러 - 프로세스의 시그널 핸들러를 공유합니다. 시그널은 프로세스에게 발생한 이벤트를 알리는 메커니즘이며, 스레드는 이를 처리하기 위해 동일한 시그널 핸들러를 사용가능 4. 열린 소캣 - 스레드는 프로세스의 열린 소켓을 공유합니다. 이는 네트워크 통신 작업을 스레드 간에 공유가능 - **독립자원** 1. **Thread Local 변수(이놈은 저희가 나중에 직접쓸수도 있습니다)** - Thread Local은 Java에서 스레드 간에 데이터를 공유하지 않고, 각 스레드별로 독립적으로 유지하고자 할 때 사용되는 기능이고 - Thread Local은 각 스레드마다 별도의 변수 인스턴스를 제공하며, 해당 변수는 스레드 내에서만 접근할 수 있습니다. - 각 스레드는 Thread Local 변수에 값을 설정하고 가져오는 메서드를 사용하여 독립적인 상태를 유지할 수 있습니다. - 예를 들어, 스레드 간에 로깅 정보, 트랜잭션 컨텍스트, **사용자 인증 정보** 등을 공유하지 않고 스레드 내에서만 유지하고자 할 때 Thread Local을 사용할 수 있습니다 - **이때 Thread Local 사용시 remove 를 하지 않는다면 어떻게 될까??** - **코드** ```java public class ThreadLocalExample { private static ThreadLocal threadLocal = new ThreadLocal<>(); public static void main(String[] args) { // 스레드 1 Thread thread1 = new Thread(() -> { threadLocal.set(1); try { TimeUnit.SECONDS.sleep(2); // 2초 대기 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread 1: " + threadLocal.get()); // 1 출력 }); // 스레드 2 Thread thread2 = new Thread(() -> { threadLocal.set(2); System.out.println("Thread 2: " + threadLocal.get()); // 2 출력 }); // 스레드 3 Thread thread3 = new Thread(() -> { threadLocal.set(3); System.out.println("Thread 3: " + threadLocal.get()); }); // 각 스레드 시작 thread1.run(); thread2.start(); thread3.start(); try { thread1.join(); thread2.join(); thread3.join(); } catch (InterruptedException e) { e.printStackTrace(); } // 메인 스레드에서의 값 출력 System.out.println("Main Thread: " + threadLocal.get()); // 1 출력 // thread1.run(); // remove 해야하는 예시 // } } ``` 2. Thread Stack - Java에서 각 스레드는 독립적인 실행 흐름을 위해 별도의 스택(Stack)을 가지고 있습니다. - 스레드 스택은 함수 호출과 관련된 정보, 지역 변수, 매개 변수 등을 저장하는 데 사용됩니다. - 각 스레드의 스택은 스레드의 실행 흐름을 관리하고, 스레드마다 독립적인 함수 호출과 지역 변수의 스코프를 지원합니다. - 스레드 스택은 자동으로 생성되고 관리되며, 스레드의 수명 주기와 함께 할당 및 해제됩니다. - 스레드 스택은 JVM에 의해 관리되며, 스택 오버플로우와 같은 문제가 발생할 수 있습니다. JVM은 스택 오버플로우를 감지하고 예외를 발생시켜 프로그램의 안정성을 유지합니다. - **코드** ```java import java.util.concurrent.TimeUnit; public class ThreadLocalExample { private static ThreadLocalthreadLocal= new ThreadLocal<>(); public static void main(String[] args) { //스레드 1 Thread thread1 = new Thread(() -> { threadLocal.set(1); try { TimeUnit.SECONDS.sleep(2);// 2초 대기 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread 1: " +threadLocal.get());// 1출력 }); //스레드 2 Thread thread2 = new Thread(() -> { threadLocal.set(2); System.out.println("Thread 2: " +threadLocal.get());// 2출력 }); //스레드 3 Thread thread3 = new Thread(() -> { threadLocal.set(3); System.out.println("Thread 3: " +threadLocal.get()); }); //각 스레드 시작 thread1.start(); thread2.start(); thread3.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } //메인 스레드에서의 값 출력 System.out.println("Main Thread: " +threadLocal.get());// null출력 } } ``` 상단의 코드를 보면 threadLocal 이 다른 Thread 에 영향을 주지않는것을 볼 수 있다 3. PC(Programe Counter) Register - PC는 다음에 실행할 명령어의 주소를 가리키는 역할을 하므로, 스레드마다 PC의 값은 서로 다를 수있음 - 스레드는 자신만의 레지스터 세트를 가지며, 이를 통해 각각의 스레드는 자신의 데이터를 저장하고 연산을 수행합니다. 스레드 간에는 레지스터 값을 공유하지 않으며, 독립적으로 작업을 수행 **마무리 정리** - 왜??? Thread 가 공유자원과 독립자원으로 나누어져있을까?? 로컬 변수와 스레드 스택은 각각 스레드의 독립성을 보장하고, 동시에 실행되는 여러 스레드가 각자의 상태와 실행 흐름을 유지할 수 있도록 도와줍니다. 이러한 독립적인 자원을 가지는 것은 스레드 간의 **상호작용과 동시성 문제**를 다루는 데 유용합니다 - Thread 는 공유자원과 독립자원으로 나누어져있다 - Thread 의 공유자원은 Process 의 자원들을 공유한다. 독립자원은 각각의 Thread 가 개별적으로 가지고있는 자원이다. --- ## Java 에서 Thread 를 사용한는 방법 > java 에서 Thread 를 사용하는 방법은 대표적으로 3가지? 정도가 있는것같다 더있는지는 잘..모르겠슴다.. ㅎㅎ😄 > ### 1. Thread 상속 1. 첫번쨰로 Thread 클래스를 extends 받아서 처리하는 방법이 있다 ```java public class MyThread extends Thread { public void run() { // 스레드 동작을 구현 } public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); } } ``` 위처럼 Thread 를 상속받아서 MyThread 클래스는 run() 메서드를 오버라이딩하여 스레드의 동작을 구현하고, start() 메서드를 호출하여 스레드를 실행 1. Runnable 클래스 인터페이스를 구현한 클래스를 생성하여 동작을 정의하는 방법 ```java public class MyRunnable implements Runnable { public void run() { //스레드 동작을 구현 } public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); thread.start(); } } ``` 위처럼 Runnable 을 구현하여 start 로 실행. 2. Lambda 표현식으로 Runnable 구현 ```java public class LambdaThreadExample { public static void main(String[] args) { Runnable runnable = () -> { // 스레드 동작을 구현 }; Thread thread1 = new Thread(runnable); thread1.start(); Thread thread2 = new Thread(()->{ System.out.println(Thread.currentThread()); }); thread2.start(); } } ``` 람다식으로 Runnable 을구현하여 실행하거나 파라미터로 바로 넣어서 실행( 동작 파라미터 기억하죠?) **위의 3가지 예제를 볼때 궁금증이 생겨야합니다** 1. 왜 Thread run을 상속받았는데 다 thread run 을하지않고 start 를 했나 2. Runnable 과 Thread 를 상속받는거에 어떠한 차이점이 있나? 정도의 궁금증이 생겨나야합니다. ### 2. Thread. start() 와 Thread.run() 의 차이 결론부터 말씀드리면 로 알수있겠습니다. 코드를 볼까요?? 1. start() 사용시 ```java class MyThread extends Thread { public void run() { System.out.println("Current thread name: " + Thread.currentThread().getName()); System.out.println("run() method called"); } } class Main { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); } } ``` 위와 같은 코드에서의 결과는 ```java Current thread name: Thread-0 run() method called ``` 이라는 결과가 나옵니다. 즉 **Thread-0 이라는 Thread 가 생성**이 된다음 **run() 메서드를 호출**합니다. 2. run() 사용시 ```java class MyThread2 extends Thread { public void run() { System.out.println("Current thread name: " + Thread.currentThread().getName()); System.out.println("run() method called"); } } class Main2 { public static void main(String[] args) { MyThread t = new MyThread(); t.run(); } } ``` 위의 코드에서 결과는 ```java Current thread name: main run() method called ``` 이라는 결과가 나오는데 즉 main Thread 를 실행시켰고 새로운 Thread 를 생성하지 않았다는 결과가 나오는겁니다. 따라서 **멀티 스레딩이 발생하지 않았다**는것을 알수있습니다. 3. start() 2번 호출시 ```java class MyThread3 extends Thread { public void run() { System.out.println("Current thread name: " + Thread.currentThread().getName()); System.out.println("run() method called"); } } class Main3 { public static void main(String[] args) { MyThread3 t = new MyThread3(); t.start(); t.start(); } } ``` 위의 코드에서 ```java Exception in thread "main" java.lang.IllegalThreadStateException at java.base/java.lang.Thread.start(Thread.java:789) at javastudy.personalstudy.thread.different.Main3.main(MyThread3.java:25) Current thread name: Thread-0 run() method called ``` 라는 결과가 나오는것을 알수있습니다. 이를 보면 start() 메서드는 2번호출 될수없다는것을 알수있습니다. 그렇기 때문에 start() 를 2번실행했을때 `IllegalThreadStateException` 가 발생한것을 알수있습니다. 4. run() 2번 호출시 ```java class MyThread4 extends Thread { public void run() { System.out.println("Current thread name: " + Thread.currentThread().getName()); System.out.println("run() method called"); } } class Main4 { public static void main(String[] args) { MyThread t = new MyThread(); t.run(); t.run(); } } ``` 위의 코드에서 ```java Current thread name: main run() method called Current thread name: main run() method called ``` 이라는 결과가 나오는것을 알수있습니다. *`run()`* 메서드를 두 번 호출해도 예외가 발생하지 않고 예상대로 두 번 실행되지만 *메인* 스레드 자체에서 실행되는것을 알수있습니다. **요약 정리** | | start() | run() | | --- | --- | --- | | Thread 생성 | 새로운 Thread 생성 | 새로운 Thread 를 생성하지 않는다. run() 을 호출한놈의 Thread 를 씀 | | 호출 횟수 | 한번 이상의 호출할수 없다 | 여러번 호출가능 | | 구현 위치 | Thread 에 정의되어있다 | Runnable 인터페이스에서 있는 메서드를 재정의 해야한다. | | 사용할때 | 멀티스래딩, 비동기처리 | 단일스레드, 테스트 및 디버깅 | start() 가 좋냐? run() 이 좋냐? 그런거없슴다. 그냥 언제 어떻게 쓸지에따라서 다르게 쓰이는겁니다. --- ## Mulit Thread . . . > 멀티스레드란? 하나의 프로그램에 동시에 여러개의 일을 수행할수 있도록 해주는 것이라고 볼수있다 > 자 사실 멀티스레드의 개념은 생각보다 단순하다. 여러개의 Thread 가 하나의 Task 를 처리한다고 한다면 멀티스레딩이다. 사실 여기서 가장 중요한건 **동기화 기법** 을 어떻게 처리할지 이다. ### Mulit Thread 동기화 기법.. 1. Mutex - 일단 뮤텍스는 상호배제를 위한 기법인데 하나의 Thread 만 임계영역 critical section 에 진입할수 있도록 도와줍니다. 하단의 코드를 보면서 이해를 해봅시다. - 코드 ```java package javastudy.personalstudy.thread.syncronizedways; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class MutexExample { private static int count = 0; private static Lock lock = new ReentrantLock(); public static void main(String[] args) { // 앞서 설명했듯이 thread 에서 runnable 을 lambda 로 구현 Thread thread1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { lock.lock(); //<-- 해당자원이 임계영역에 진입하기전 lock 을검 try { count++; } finally { lock.unlock(); // <-- 다 끝났을 경우 unlock 으로 해제 } } }); Thread thread2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { lock.lock(); try { count++; } finally { lock.unlock(); } } }); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Count: " + count); } } ``` 2. Semaphore - 2가지 연산으로 구성 P (Wait) 연산과 V (Signal) 연산으로 구성 - 카운터 값이 음수가 될 수 없고, 0 이상의 정수 - 즉 P (Wait) 연산은 세마포어 카운터를 1 감소시키고, 만약 카운터가 0 이하가 되면 호출한 스레드는 블록됩니다. V (Signal) 연산은 세마포어 카운터를 1 증가시키고, 만약 카운터가 0 이상이 되면 대기 중인 스레드 중 하나를 깨웁니다. - 스레드 간의 안전한 공유 자원 액세스, 생산자-소비자 문제, 우선순위 스케줄링, 임계 영역 제한 등 다양한 상황에서 유용하게 활용 **위에 말이 무슨말이야???? 괜찮습니다 다~~ 예제로 이해가 됩니다잉** - 코드 ```java package javastudy.personalstudy.thread.syncronizedways.semaphore; import java.util.concurrent.Semaphore; public class SemaphoreExample { private static int count = 0; private static Semaphore semaphore = new Semaphore(1); // 동시에 접근할수 있는 Thread 개수를 1개로 선정한것 public static void main(String[] args) { Thread thread1 = new Thread(() -> { try { semaphore.acquire(); // Semaphore 의 값을 1감소 처음에 1할당한것을 감소시키니까 0 이겠죠? for (int i = 0; i < 1000; i++) { count++; } } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); // 종료됬으니까 Semaphore 의 cnt 값을 증가 } }); Thread thread2 = new Thread(() -> { try { semaphore.acquire(); for (int i = 0; i < 1000; i++) { count++; } } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); } }); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Count: " + count); } } ``` 3. Monitor - 하나의 객체(데이터) 마다 하나의 모니터가 될수있고 모니터는 객체(데이터) 가 두개이상의 접근할 수 없도록 막는 lock 을 제공하여 동기화를 하는 기법 - 쉽게 생각하면 **데이터(객체)를 모니터링 할수 있는 기법** 이라고 볼수있다 - 코드 ```java package javastudy.personalstudy.thread.syncronizedways.monitor; public class ThreadMonitorExample { private static intcount= 0; public static void main(String[] args) { //주요 스레드 생성 Thread mainThread = new Thread(() -> { System.out.println("주요 스레드 시작"); //보조 스레드 생성 for (int i = 1; i <= 5; i++) { Thread subThread = new Thread(() -> { for (int j = 0; j < 400; j++) { incrementCount(); } System.out.println("보조 스레드 " + Thread.currentThread().getId() + " 종료"); System.out.println("보조 스레드 상태 : " + Thread.currentThread().getState()); }); subThread.start(); } //보조 스레드들이 모두 종료될 때까지 대기 while (count< 2000) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("주요 스레드 종료"); }); //주요 스레드 시작 mainThread.start(); //주요 스레드와 보조 스레드 모니터링 while (mainThread.isAlive()) { System.out.println("주요 스레드 상태: " + mainThread.getState()); System.out.println("현재 count 값: " +count); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } try { System.out.println("프로그램 종료"); mainThread.join(); } catch (InterruptedException e) { throw new RuntimeException(e); }finally { System.out.println("주요 스레드 상태 : "+ mainThread.getState()); } } private static synchronized void incrementCount() { count++; } } ``` **PS : 현재 Java 에서는 직접적인 동기화 기법인 synchronized 를 지양하고 Atomic Variable 혹은 conurrent Collection Framework 등을 알아 보면 좋을것같습니다.**
MyeoungDev commented 1 year ago

위쪽에 이미지가 깨지는것 같네요! 밑에 Multi Thread 동기화 기법 예제는 너무 좋네요! 고생하셨습니다 👍