본문 바로가기
네이버클라우드/JAVA 웹 프로그래밍

JAVA 37일차 (2023-07-13) 자바 프로그래밍_스레드와 멀티태스킹

by prometedor 2023. 7. 13.
- 자바 프로그래밍
  - 스레드 프로그래밍(com.eomcs.concurrent.ex1 ~ ex4)
- 멀티태스킹의 메커니즘 이해
  - 프로세스 스케쥴링: Round Robin 방식, Priority + Aging 방식
  - 컨텍스트 스위칭 개념
  - 프로세스 복제(fork)방식과 스레드 방식 비교
- 스레드의 구동원리와 사용법
  - 스레드의 라이프사이클 이해
  - Thread 클래스와 Runnable 인터페이스 사용법
- 프로젝트 실습

 

리눅스 서버 접속 및 C 파일 테스트 해보기

# 리눅스 운영체제 설치

- 가상 머신 구축 시스템 설치: Virtualbox
- CLI 기반 가상 머신 관리 도구 설치: Vagrant
- 가상 머신 생성 및 설정

## 도구  준비

### Virtualbox 설치

- [Virtualbox 사이트](https://www.virtualbox.org/) 접속
- Downloads/호스트 OS에 맞는 패키지 다운로드
- Virtualbox 설치

### Vagrant 설치

- [Vagrant 사이트](https://www.vagrantup.com/) 접속
- Download 클릭
- 호스트 OS에 맞춰서 설치
  - 예) macOS: `brew install vagrant`
  - 예) Windows: 다운로드 후 설치
- 설치 확인

```bash
$ vagrant -v
```

## VirtualBox 가상 머신 관리

### 가상 머신 프로젝트 폴더 생성

```bash
~$ mkdir vagrant
~$ cd vagrant
~/vagrant$ mkdir ubuntu
~/vagrant$ cd ubuntu
~/vagrant/ubuntu$ 
```

### Variant Box(가상머신 이미지) 찾기

- [가상머신이미지 저장소 사이트](https://app.vagrantup.com/) 접속
- 'ubuntu' 검색
- 'ubuntu'의 'virtualbox' 링크 선택

### Vagrant 설정 파일(Vagrantfile) 준비

```bash
~/vagrant/ubuntu$ vagrant init ubuntu/trusty64
~/vm/centos$ cat Vagrantfile
Vagrant.configure("2") do |config|
  # ...
end
```

### Box 다운로드 및 VM 생성, 실행

```bash
~/vagrant/ubuntu$ vagrant up
```

### 가상 머신에 ssh 접속

```bash
~/vagrant/ubuntu$ vagrant ssh
```

### VM 정지

```bash
~/vagrant/ubuntu$ vagrant halt
```

### VM 삭제

```bash
~/vagrant/ubuntu$ vagrant destroy
```

## Vagrantfile

### 클라우드에서 가져올 box 이름 지정하기

```
# https://vagrantcloud.com/search 사이트에서 box를 검색 할 수 있다.
config.vm.box = "box 이름"

예) config.vm.box = "ubuntu/trusty64"
```


## Box 다루기

### Vagrant에 설치된 박스 목록 보기

```bash
$ vagrant box list
```


### Vagrant에 등록된 박스 제거하기

```bash
$ vagrant box remove "박스이름"
예) 
$ vagrant box remove "myUbuntu/trusty64"
```

 

네이버 클라우드 서버 접속 확인 및 update, upgrade 실행

ㄴ 네이버 클라우드 서버 접속해보기

    ㄴ ssh root@공인 IP 주소

 

ㄴ apt update, apt upgrade 해주기

 

Vagrant 설치

https://www.vagrantup.com/

 

Vagrant by HashiCorp

Vagrant enables users to create and configure lightweight, reproducible, and portable development environments.

www.vagrantup.com

ㄴ [Download] 선택

 

ㄴ 해당 코드 복사하여 실행

 

ㄴ 윈도우는 직접 다운로드

 

ㄴ Mac m1 에서 해당 코드 이용하여 다운로드

 

ㄴ 다운 받은 vagrant 의 버전 확인 가능

 

ㄴ 해당 폴더 생성

 

https://app.vagrantup.com/ubuntu/boxes/trusty64

 

ㄴ 해당 코드 이용

 

=>

=>

=>

ㄴ Mac 에서는 오류 발생

ㄴ Windows 에서는 VirtualBox 에 있는 서버 실행시킬 수 있음

 

C 파일 테스트

ㄴ nano 를 이용하여 코드 입력 후 command + O 로 a.c 라는 이름으로 저장해주고 command + x 로 나가기

 

ㄴ a.c 파일 컴파일 하기

 

ㄴ ./a 로 a.c 파일 실행

    => 현재 폴더에서 컴파일된 a 를 실행

 

ㄴ 실행 결과

 

ㄴ a.c 파일 변경

 

ㄴ 다시 컴파일 후 실행한 결과

 

ㄴ 새로운 파일 b.c 생성

 

ㄴ a.c 파일과 마찬가지로 b.c 라는 이름으로 저장하여 컴파일 한 후 실행한 결과

 

스레드

## 컨텍스트 스위칭(context switching)
 - CPU의 실행 시간을 쪼개 이 코드 저 코드를 실행할 때 마다
   실행 위치 및 정보(context)를 저장하고 로딩하는 과정이 필요하다.
 - 이 과정을 '컨텍스트 스위칭'이라 부른다.

 ## 스레드(thread)
 - '실'이라는 뜻을 갖고 있다.
 - 한 실행 흐름을 가리킨다.
 - 하나의 실은 끊기지 않은 하나의 실행 흐름을 의미한다.

 ## 스레드 생성
 - 새 실을 만든다는 것이다.
 - 즉 새 실행 흐름을 시작하겠다는 의미다.
 - CPU는 스레드를 프로세스와 마찬가지로 동일한 자격을 부여하여 스케줄링에 참여시킨다.
 - 즉 프로세스에 종속된 스레드라고 취급하여
   한 프로세스에 부여된 실행 시간을 다시 쪼개 스레드에 나눠주는 방식이 아니다.
 - 그냥 단독적인 프로세스처럼 동일한 실행 시간을 부여한다.

 

멀티 스레드 적용 전 - 멀티 태스킹을 적용하기 전

// 멀티 스레드 적용 전 - 멀티 태스킹을 적용하기 전
package com.eomcs.concurrent.ex1;

public class Exam0110 {

  public static void main(String[] args) {

    for (int i = 0; i < 1000; i++) {
      System.out.println("==> " + i);
    }

    for (int i = 0; i < 1000; i++) {
      System.out.println(">>> " + i);
    }
  }
}

ㄴ 일반적으로 코드는 위에서 아래로 순서대로 실행함
ㄴ 작업이 완료할 때까지 다음 줄로 가지 않음

ㄴ 자바는 main() 메서드를 실행하는 한 개의 "실행 흐름"이 있음
ㄴ 실행 흐름에 따라 순서대로 코드가 실행됨

 

멀티 스레드 적용 후

// 멀티 스레드 적용 후
package com.eomcs.concurrent.ex1;

public class Exam0120 {

  // CPU의 시간을 쪼개서 왔다갔다 하면서 동시에 실행하고픈 코드가 있다면,
  // 다음과 같이 Thread를 상속 받아 run() 메서드에 그 코드를 두어라!
  //
  static class MyThread extends Thread {
    @Override
    public void run() {
      // 기존 실행 흐름과 분리하여 따로 실행시킬 코드를
      // 이 메서드에 둔다.
      for (int i = 0; i < 1000; i++) {
        System.out.println("==> " + i);
      }
    }
  }

  public static void main(String[] args) {
    // => 동시에 실행할 코드를 담고 있는 Thread 객체를 생성한다.
    // => 그리고 현재 실행과 분리하여 작업을 시작시킨다.
    // => JVM은 이 스레드에 들어 있는 코드와 다음에 진행하는 코드를
    // 왔다갔다 하면서 처리할 것이다.
    new MyThread().start();

    for (int i = 0; i < 1000; i++) {
      System.out.println(">>> " + i);
    }
  }

}

ㄴ  main() 메서드를 실행하는 기본 실행 흐름에서 새로운 실행 흐름으로 분기하고 싶다면, Thread 클래스를 정의할 때 분기해서 실행할 코드를 담으면 됨

=> 그러면 두 개의 실행 흐름이 서로 왔다 갔다 하면서 실행됨

 

현재의 실행 라인을 알아내기

// 현재의 실행 라인을 알아내기
package com.eomcs.concurrent.ex2;

public class Exam0110 {

  public static void main(String[] args) {
    // JVM은 여러 개의 스레드를 실행한다.
    // main() 호출도 별도의 스레드가 실행한다.
    // 확인해보자!

    // 이 순간 실행 중인 흐름이 무엇인지 알고 싶다면?
    Thread t = Thread.currentThread();

    System.out.println("실행 흐름명 = " + t.getName());

    // JVM이 실행될 때 main() 메서드를 호출하는 실행 흐름(스레드)의 이름은 "main"이다.
  }
}
// JVM의 스레드 계층도:
// main(T)

=>

 

스레드 그룹

// 스레드 그룹
package com.eomcs.concurrent.ex2;

public class Exam0120 {

  public static void main(String[] args) {
    Thread main = Thread.currentThread();

    // 스레드는 그룹에 소속되기도 한다.
    // 현재 스레드의 소속 그룹을 알고 싶다면?
    ThreadGroup group = main.getThreadGroup();
    System.out.println("그룹명 = " + group.getName());

    // main() 메서드를 호출하는 스레드는 "main" 스레드이고,
    // "main" 스레드가 소속된 그룹은 "main" 그룹이다.
  }
}

// JVM의 스레드 계층도:
// main(TG)
// => main(T)

=>

 

스레드 그룹에 소속된 스레드들

// 스레드 그룹에 소속된 스레드들
package com.eomcs.concurrent.ex2;

public class Exam0130 {

  public static void main(String[] args) {
    Thread main = Thread.currentThread();
    ThreadGroup mainGroup = main.getThreadGroup();

    // 스레드 그룹에 소속된 스레드 목록을 알고 싶다면?
    Thread[] arr = new Thread[100];
    int count = mainGroup.enumerate(arr, false);
    // 두 번째 파라미터 값을 false로 지정하면,
    // 하위 그룹에 소속된 스레드들은 제외한다.
    // 즉, 현재 그룹에 소속된 스레드 목록만 가져오라는 뜻!

    System.out.println("main 그룹에 소속된 스레드들:");
    for (int i = 0; i < count; i++)
      System.out.println("   => " + arr[i].getName());
  }
}

// JVM의 스레드 계층도:
// main(TG)
// => main(T)
// => 다른 스레드는 없다.

=>

 

스레드 그룹에 소속된 하위 그룹들

// 스레드 그룹에 소속된 하위 그룹들
package com.eomcs.concurrent.ex2;

public class Exam0140 {

  public static void main(String[] args) {
    Thread main = Thread.currentThread();
    ThreadGroup mainGroup = main.getThreadGroup();

    // 스레드 그룹에 소속된 하위 그룹을 알고 싶다면?
    ThreadGroup[] groups = new ThreadGroup[100];
    int count = mainGroup.enumerate(groups, false);
    // 두 번째 파라미터 값을 false로 지정하면,
    // 하위 그룹에 소속된 그룹들은 제외한다.
    // 즉, 현재 그룹에 소속된 하위 그룹의 목록만 가져오라는 뜻!

    System.out.println("main 그룹에 소속된 하위 그룹들:");
    for (int i = 0; i < count; i++)
      System.out.println("   => " + groups[i].getName());
  }

}

// JVM의 스레드 계층도:
// main(TG)
// => main(T)
// => 다른 하위 그룹은 없다!

=>

ㄴ main 그룹에 소속된 다른 하위 그룹은 없으므로 아무것도 출력되지 않음

 

스레드 그룹의 부모 그룹

// 스레드 그룹의 부모 그룹
package com.eomcs.concurrent.ex2;

public class Exam0150 {

  public static void main(String[] args) {
    Thread main = Thread.currentThread();
    ThreadGroup mainGroup = main.getThreadGroup();

    // 스레드 그룹의 부모 그룹을 알고 싶다면?
    ThreadGroup parentGroup = mainGroup.getParent();
    System.out.printf("main 스레드 그룹의 부모: %s\n", parentGroup.getName());


    // "system" 그룹의 부모 그룹은?
    ThreadGroup grandparentGroup = parentGroup.getParent();
    if (grandparentGroup != null) {
      System.out.printf("%s 스레드 그룹의 부모: %s\n", 
          parentGroup.getName(), 
          grandparentGroup.getName());
    }
  }
}

// JVM의 스레드 계층도:
// system(TG)
// => main(TG)
// ...=> main(T)

=>

ㄴ "system" 그룹의 부모 그룹은 존재하지 않으므로 출력되지 않음 

 

"system" 스레드 그룹의 자식 그룹들

// "system" 스레드 그룹의 자식 그룹들
package com.eomcs.concurrent.ex2;

public class Exam0160 {

  public static void main(String[] args) {
    Thread main = Thread.currentThread();
    ThreadGroup mainGroup = main.getThreadGroup();
    ThreadGroup systemGroup = mainGroup.getParent();

    ThreadGroup[] groups = new ThreadGroup[100];
    int count = systemGroup.enumerate(groups, false);

    System.out.println("system 스레드 그룹의 자식 그룹들:");
    for (int i = 0; i < count; i++) {
      System.out.println("   =>" + groups[i].getName());
    }
  }
}

// JVM의 스레드 계층도:
// system(TG)
// => main(TG)
// ...=> main(T) : main() 메서드를 호출한다.
// => InnocuousThreadGroup(TG)

=>

ㄴ system 스레드 그룹의 자식 그룹 => main, InnocuousThreadGroup 이 존재

ㄴ main() 메서드는 main 스레드가 호출함

 

 

 

스레드 만들기 I - Thread를 상속 받기

// 스레드 만들기 I - Thread를 상속 받기
package com.eomcs.concurrent.ex3;

public class Exam0110 {

  public static void main(String[] args) {

    // 1) Thread 클래스를 상속 받아 정의하기
    class MyThread extends Thread {
      // 기존의 스레드에서 분리해서 새 스레드에서 실행하고픈 코드가 있다면,
      // run()을 재정의하여 그 메서드에 해당 코드를 두어라!
      @Override
      public void run() {
        // 별도로 분리해서 병행으로 실행할 코드를 두는 곳!
        for (int i = 0; i < 1000; i++) {
          System.out.println("===> " + i);
        }
      }
    }

    // 스레드 실행
    // => Thread의 서브 클래스는 그냥 인스턴스를 만들어 start()를 호출한다.
    MyThread t = new MyThread();
    t.start(); // 실행 흐름을 분리한 후 즉시 리턴한다. 비동기로 동작한다.

    // "main" 스레드는 MyThread와 상관없이 병행하여 실행한다.
    for (int i = 0; i < 1000; i++) {
      System.out.println(">>>> " + i);
    }
  }
}

=>

ㄴ 똑같은 자바의 스레드 코드라도 OS에 따라 실행 순서가 달라질 수 있음

ㄴ Windows OS의 경우 우선 순위(priority) 값이 실행 순서나 실행 회수에 큰 영향을 끼치지 않음

 

Thread를 상속 받기 - 익명 클래스로 구현하기

// Thread를 상속 받기 - 익명 클래스로 구현하기
package com.eomcs.concurrent.ex3;

public class Exam0120 {

  public static void main(String[] args) {
    new Thread() {
      @Override
      public void run() {
        for (int i = 0; i < 1000; i++) {
          System.out.println("===> " + i);
        }
      }
    }.start();

    for (int i = 0; i < 1000; i++) {
      System.out.println(">>>> " + i);
    }

  }

}

ㄴ 익명클래스로 구현하기

 

스레드 만들기 II - Runnable 인터페이스 구현 + Thread

// 스레드 만들기 II - Runnable 인터페이스 구현 + Thread
package com.eomcs.concurrent.ex3;

public class Exam0210 {

  public static void main(String[] args) {

    // 2) Runnable 인터페이스를 구현하기
    class MyRunnable implements Runnable {
      @Override
      public void run() {
        // 별도로 분리해서 병행으로 실행할 코드를 두는 곳!
        for (int i = 0; i < 1000; i++) {
          System.out.println("===> " + i);
        }
      }
    }

    // 스레드 실행하기
    // => Runnable 구현체를 Thread 객체에 실어서 실행한다.
    // => start()를 호출하여 기존 스레드에서 분리하여 스레드를 실행시킨다.
    Thread t = new Thread(new MyRunnable());
    t.start(); // 실행 흐름을 분리한 후 즉시 리턴한다.

    // "main" 스레드는 Thread와 상관없이 병행하여 실행한다.
    for (int i = 0; i < 1000; i++) {
      System.out.println(">>>> " + i);
    }

  }

}

ㄴ Runnable 인터페이스 구현

    => 인터페이스를 구현하는 것이기 때문에 다른 클래스를 상속 받을 수 있음

ㄴ 직접적으로 스레드가 아니기 때문에 실행할 때는 Thread의 도움을 받아야 함

 

unnable 인터페이스 구현 + Thread - 익명 클래스로 구현하기

// Runnable 인터페이스 구현 + Thread - 익명 클래스로 구현하기
package com.eomcs.concurrent.ex3;

public class Exam0220 {

  public static void main(String[] args) {

    new Thread(new Runnable() {
      @Override
      public void run() {
        for (int i = 0; i < 1000; i++) {
          System.out.println("===> " + i);
        }
      }
    }).start();

    for (int i = 0; i < 1000; i++) {
      System.out.println(">>>> " + i);
    }

  }

}

ㄴ 익명 클래스로 구현하기

=>

Runnable 인터페이스 구현 + Thread - 람다(lambda)로 구현하기

// Runnable 인터페이스 구현 + Thread - 람다(lambda)로 구현하기
package com.eomcs.concurrent.ex3;

public class Exam0230 {

  public static void main(String[] args) {
    new Thread(() -> {
      for (int i = 0; i < 1000; i++) {
        System.out.println("===> " + i);
      }
    }).start();

    for (int i = 0; i < 1000; i++) {
      System.out.println(">>>> " + i);
    }

  }

  static void m(Runnable obj) {

  }

}

ㄴ 람다로 구현하기

 

스레드와 프로그램 종료

// 스레드와 프로그램 종료
package com.eomcs.concurrent.ex3;

import java.util.Scanner;

public class Exam0310 {

  static class MyThread extends Thread {
    @Override
    public void run() {
      Scanner keyboard = new Scanner(System.in);
      System.out.print("입력하시오> ");
      String input = keyboard.nextLine();
      System.out.println("입력한 문자열 => " + input);
      keyboard.close();
    }
  }

  public static void main(String[] args) {

    // main 스레드에서 새 스레드 객체 생성하기
    // => 어떤 스레드에서 만든 스레드를 그 스레드의 자식 스레드라 부른다.
    // => 즉 다음 스레드는 main 스레드의 자식 스레드이다.
    // => 자식 스레드는 부모 스레드와 같은 우선 순위를 갖는다.
    MyThread t = new MyThread(); // 우선순위 5
    t.start();

    // 모든 스레드가 완료할 때까지 JVM은 종료되지 않는다.
    System.out.println("프로그램 종료?");
  }

}

=>

ㄴ 모든 스레드가 완료할 때까지 JVM은 종료되지 않음

 

스레드의 생명주기(lifecycle)

// 스레드의 생명주기(lifecycle)
package com.eomcs.concurrent.ex4;

public class Exam0110 {
  public static void main(String[] args) {
    System.out.println("스레드 실행 전");
    new Thread() {
      @Override
      public void run() {
        for (int i = 0; i < 1000; i++) {
          System.out.println("===> " + i);
        }
      }
    }.start();

    System.out.println("스레드 실행 후");
    // main() 메서드의 호출이 끝나더라도 다른 스레드의 실행이 종료될 때까지 
    // JVM은 종료하지 않는다.
  }

}

=>

     스레드의 생명주기
     new Thread()    start()              sleep()/wait()
         준비 -------------------> Running ---------------> Not Runnable
                                   ^  |    <---------------
                                   |  |    timeout/notify()
                                   X  |
                                   |  |  run() 메서드 종료
                                   |  V
                                   Dead
     Running 상태?
     - CPU를 받아서 실행 중이거나 CPU를 받을 수 있는 상태
    
     Not Runnable 상태?
     - CPU를 받지 않는 상태
     
     run() 메서드 종료 후 다시 running 상태로 돌아갈 수 없다. 
     => 새로 스레드를 만들어 실행하는 방법 밖에 없다!

 

스레드의 생명주기(lifecycle) - 죽은 스레드는 다시 살릴 수 없다.

// 스레드의 생명주기(lifecycle) - 죽은 스레드는 다시 살릴 수 없다.
package com.eomcs.concurrent.ex4;

import java.util.Scanner;

public class Exam0111 {
  public static void main(String[] args) {
    System.out.println("스레드 시작시킴.");

    Thread t = new Thread(() -> { // Runnable 구현체를 정의하고 생성한다.
      for (int i = 0; i < 1000; i++) {
        System.out.println("===> " + i);
      }
      System.out.println("스레드의 run() 실행 종료!");
    });

    t.start();

    Scanner keyboard = new Scanner(System.in);
    keyboard.nextLine(); // 스레드가 종료될 때까지 시간을 벌기 위함.
    keyboard.close();

    // 죽은 스레드 객체를 또 실행할 수 없다.
    t.start(); // 예외 발생! ==> IllegalThreadStateException

    System.out.println("main() 종료!");
  }

}

=>

...

=> enter 를 치는 순간 예외 발생

 

스레드의 생명주기(lifecycle) - join()

// 스레드의 생명주기(lifecycle) - join()
package com.eomcs.concurrent.ex4;

public class Exam0120 {
  public static void main(String[] args) throws Exception {
    System.out.println("스레드 실행 전");

    Thread t = new Thread(() -> {
      for (int i = 0; i < 1000; i++) {
        System.out.println("===> " + i);
      }
    });

    t.start(); // 스레드를 생성하고 시작시킨다.

    t.join(); // t 스레드가 종료될 때까지 "main" 스레드는 기다린다.

    // 즉 t 스레드가 종료된 후 다음 코드를 실행한다.
    System.out.println("스레드 종료 후");

    // 스레드 종료 후 다시 시작시킨다면?
    // => IllegalThreadStateException 발생!
    // => 즉 종료된 스레드는 다시 running 할 수 없다.
    // t.start();

  }

}

 

ㄴ 종료된 스레드는 다시 running 할 수 없음

=>

스레드의 생명주기(lifecycle) - sleep()

// 스레드의 생명주기(lifecycle) - sleep()
package com.eomcs.concurrent.ex4;

public class Exam0130 {
  public static void main(String[] args) throws Exception {
    System.out.println("스레드 실행 전");

    new Thread() {
      @Override
      public void run() {
        System.out.println("Hello!");
      }
    }.start();

    // 3초 동안 not runnable 상태로 만든다.
    // => 즉 3초 동안 CPU가 놀고 있더라도 CPU를 사용하지 않는다.
    // => 3초가 지나면(timeout) 다시 "main" 스레드는 CPU를 받아 실행할 수 있다.
    // => sleep()을 호출하면 그 순간에 실행하는 스레드를 잠들게 한다.
    Thread.sleep(3000); // milliseconds

    System.out.println("스레드 실행 후");
  }

}

ㄴ sleep 을 이용하여 not runnable 상태로 만들 수 있음

    ㄴ Thread.sleep(3000) => 3초 동안 not runnable 상태로 만들 수 있음

=>

=>

ㄴ 3초가 지나면(timeout) 다시 "main" 스레드는 CPU를 받아 실행할 수 있음
    => sleep()을 호출하면 그 순간에 실행하는 스레드를 잠들게 할 수 있음

 

스레드의 생명주기(lifecycle) - running 상태 : CPU 쟁탈전(racing)

// 스레드의 생명주기(lifecycle) - running 상태 : CPU 쟁탈전(racing)
package com.eomcs.concurrent.ex4;

public class Exam0140 {
  public static void main(String[] args) throws Exception {

    class MyThread extends Thread {
      public MyThread(String name) {
        super(name);
      }
      @Override
      public void run() {
        for (int i = 0; i < 1000; i++)
          System.out.printf("%s %d\n", this.getName(), i);
      }
    }

    MyThread t1 = new MyThread("홍길동 =====>");
    MyThread t2 = new MyThread("오호라 ------------>");
    MyThread t3 = new MyThread("우헤헤 ##");

    // 스레드를 시작시키는 순간 running 상태로 접어든다.
    t1.start();
    t2.start();
    t3.start();

    for (int i = 0; i < 1000; i++)
      System.out.printf("main 스레드: %d\n", i);
  }

}

=>

...

...

...

ㄴ 3개의 스레드 + 메인 스레드가 CPU 쟁탈전을 벌임

 

프로세스(스레드) 스케줄링
 => OS가 프로세스나 스레드에 CPU 사용을 배분하는 정책
 1) Round-Robin 방식
    - Windows 운영체제에서 사용하는 방식이다.
    - 우선 순위 보다는 일정 시간 위주로 프로세스나 스레드에게 CPU를 배분하는 방식이다.
 2) Priority + Aging 방식
    - Unix나 Linux 운영체제에서 사용하는 방식이다.
    - 우선 순위가 높은 프로세스나 스레드에게 CPU를 먼저 배분하는 방식이다.
    - 우선 순위 배분 방식에서는 우선 순위가 낮은 경우 실행에서 소외되는 문제가 발생하기 때문에
      우선 순위가 높은 프로세스나 스레드 때문에 실행 순서가 밀릴 때 마다 
      원래의 낮은 순위를 높임으로써(aging) 결국에는 모든 프로세스와 스레드의 
      실행을 완료할 수 있게 한다.

 "컨텍스트 스위칭(context switching)"
 - 동시에 여러 개의 프로세스나 스레드를 실행할 때 CPU 사용권을 뺏어 다른 프로세스나 스레드에게 주기 전에 
   현재까지 실행한 코드의 위치 정보를 저장해야 한다.
   또한 CPU 사용권을 주기 전에 그 프로세스나 스레드가 이전에 어디까지 실행했었는지
   이전 실행 위치 정보를 로딩해야 한다.
   즉 실행 위치에 대한 정보를 저장하고 로딩하는 것을 말한다.

 

스레드의 생명주기(lifecycle) - 우선 순위 조회

// 스레드의 생명주기(lifecycle) - 우선 순위 조회
package com.eomcs.concurrent.ex4;

public class Exam0210 {

  public static void main(String[] args) throws Exception {
    // 스레드 우선 순위 정보
    //
    // => 스레드의 우선 순위 범위
    System.out.printf("우선 순위 범위: %d ~ %d\n", //
        Thread.MIN_PRIORITY, //
        Thread.MAX_PRIORITY);

    // => 스레드의 기본 우선 순위
    System.out.printf("우선 순위 기본값: %d\n", Thread.NORM_PRIORITY);

    // => "main" 스레드의 우선 순위 조회
    System.out.printf("main 스레드 우선 순위: %d\n", //
        Thread.currentThread().getPriority());

    class MyThread extends Thread {
      public MyThread(String name) {
        super(name);
      }

      @Override
      public void run() {
        for (int i = 0; i < 1000; i++)
          System.out.printf("%s %d\n", this.getName(), i);
      }
    }


    MyThread t1 = new MyThread("t1");

    // "t1" 스레드의 우선 순위 조회
    // => "main" 스레드를 실행하는 동안 만든 스레드는 "main"의 자식 스레드라 부른다.
    // => 자식 스레드는 부모 스레드의 우선 순위와 같은 값을 갖는다.
    //    그래서 "t1" 스레드는 "main"의 우선 순위 값과 같다.
    System.out.printf("%s 스레드 우선 순위: %d\n", t1.getName(), t1.getPriority());
  }

}

 

=>

ㄴ 자식 스레드는 부모 스레드의 우선 순위 값과 같음

ㄴ t1 스레드는 main 스레드가 만든 스레드이므로 t1 스레드와 main 스레드는 우선순위가 같음

 

스레드의 생명주기(lifecycle) - 우선 순위 설정

// 스레드의 생명주기(lifecycle) - 우선 순위 설정
package com.eomcs.concurrent.ex4;

public class Exam0220 {
  public static void main(String[] args) throws Exception {
    class MyThread extends Thread {
      public MyThread(String name) {
        super(name);
      }

      @Override
      public void run() {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++)
          Math.asin(38.567); // 시간 끌기 용. 왜? 부동소수점 연산은 시간을 많이 소요.
        long endTime = System.currentTimeMillis();
        System.out.printf("MyThread = %d\n", endTime - startTime);
      }
    }

    // main 스레드의 우선 순위를 가장 작은 1로 설정한다.
    Thread.currentThread().setPriority(1);

    MyThread t1 = new MyThread("t1");
    t1.setPriority(10);
    // 유닉스 계열의 OS는 스케줄링에서 우선 순위를 고려하여 CPU를 배분한다.
    // 따라서 프로그램을 짤 때 스레드의 우선 순위를 조정하는 방법에 의존하지 말라!

    System.out.printf("main 스레드 우선 순위: %d\n", Thread.currentThread().getPriority());

    System.out.printf("%s 스레드 우선 순위: %d\n", t1.getName(), t1.getPriority());


    // t1 스레드 작업 시작
    t1.start();

    // main 스레드 작업 시작
    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 100000000; i++)
      Math.asin(38.567); // 부동 소수점 연산을 수행하는 코드를 넣어서 실행 시간을 약간 지연시킨다.
    long endTime = System.currentTimeMillis();
    System.out.printf("main = %d\n", endTime - startTime);
  }

}

ㄴ 유닉스 계열의 OS는 스케줄링에서 우선 순위를 고려하여 CPU를 배분함
     => 그러나 Windows OS는 우선 순위를 덜 고려하여 CPU를 배분함
ㄴ 우선 순위를 조정하여 작업을 처리하도록 프로그램을 짜게 되면, 유닉스 계열에서 실행할 때는 의도한 대로 동작할지 모르지만,
     윈도우에서는 의도대로 동작하지 않을 것임
      => 따라서 프로그램을 짤 때 스레드의 우선 순위를 조정하는 방법에 의존하지 말자