Java/Java의 정석

Chapter 13 스레드(Thread)

계란💕 2023. 3. 1. 20:25

1. 프로세스(Process)와 스레드(Thread)

  • Def) 프로세스(Process): 실행 중인 프로그램이다.
  • 프로그램을 실행하면 OS로부터 필요한 자원(메모리)을 할당 받아서 프로세스가 된다. 
  • 프로세스는 프로그램을 수행하기 위해 필요한 데이터, 메모리 등의 자원 그리고 스레드로 구성된다. 
    • Def) 여기서 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것을 스레드라고 한다. 
    • 다시 말해 스레드는 프로세스의 실행 단위를 말한다. 
    • 스레드를 경량 프로세스(LWP, Light Weight Process)라고도 부른다. 
  • 프로세스는 하나 이상의 스레드를 포함한다. 
    • 프로세스는 공장, 스레드는 작업을 처리하는 일꾼이라고 이해할 수 있다. 
  • 멀티스레드 프로세스(multi threaded process): 둘 이상의 스레드를 가진 프로세스를 말한다. 
  • 하나의 프로세스 내에서 만들 수 있는 스레드의 개수는 한정적일까?
    • 그렇지는 않다. 하지만 스레드가 작업을 수행하는데 개별적인 메모리 공간(호출스택)이 필요하다. 따라서 프로세스의 메모리 한계에 따라 생성할 수 있는 스레드의 개수가 결정된다. 
    • 실무에서는 프로세스의 메모리 한계에 다다를 만큼 스레드를 많이 만들 일이 없다. 

 


멀티태스킹(multi tasking, 다중 작업)과 멀티스레딩(multi programming)

  • 멀티태스킹
    • 여러 개의 프로세스를 동시에 실행한다 
    • 윈도우나 유닉스를 포함한 대부분의 OS는 멀티태스킹(다중 작업)을 지원하므로 여러 개의 프로세스를 동시에 실행할 수 있다.
  • 멀티스레딩
    • CPU의 코어(core)가 한 번에 단 하나의 작업만 수행할 수 있으므로 실제로 동시에 처리되는 작업의 개수코어 개수와 일치한다. 
    • 처리해야하는 스레드의 개수는 언제나 코어의 개수보다 훨씬 많기 때문에 각 코어가 아주 짧은 시간 동안 여러 작업을 번갈아가며 수행함으로써 여러 작업들이 모두 동시에 수행되는 것처럼 보이게 한다. 
  • 프로세스의 성능이 스레드의 개수에 비례하지는 않는다. 
  • ex) 싱글스레드로 서버 프로그램을 싱글 스레드로 작성한 경우, 사용자의 요청마다 새로운 프로세스를 생성해야한다. 이는 스레드를 새로 생성하는 것에 비해 더 많은 시간과 메모리공간이 필요하므로 많은 사용자의 요청을 서비스하기 어려워진다. 
  •  
  •  

 


멀티스레딩(multi programming) 장점과 문제점

  • 장점
    • CPU의 사용률 향상
    • 자원을 효율적으로 사용
    • 사용자에 대한 응답성 향상
    • 작업이 분리되어 코드가 간결해진다. 
    • Ex) 메신저로 채팅하면서 파일 다운, 음성 대화를 할 수 있다. 
  • 문제점
    • 동기화(synchronization) 문제: 여러 스레드가 같은 프로세스 내에서 자원을 공유하면서 작업을 하며 생기는 문제를 말한다. 
    • 교착 상태(deadlock): 두 스레드가 각각 자원을 점유하고 있을 때, 상대방의 자원을 사용하려고 무한 대기중인 상태를 말한다. 사다리 위, 아래에 있는 사람이 각각 상대방이 나오기만을 기다리는 상태와 유사하다. 

2. 스레드의 구현과 실행

  • 1) Thread 클래스를 상속: extend는 하나의 클래스만 상속 가능하므로 다른 클래스는 상속 불가능하다. 
  • 2) Runnable 인터페이스를 구현하는 방법: 위 방법의 단점 때문에 Runnable 인터페이스를 implements하는 방법이 일반적이다. 
    • run() 메서드만 정의되어 있다.
  • 스레드 명은 기본적으로 "Thread - 번호" 로 이름이 정해진다. 
  • 스레드를 생성하고나서 start()를 호출해야만 스레드가 실행된다.
    • start()가 호출된 후, 실행 대기 상태에 있다가 자신의 차례가 되어야 실행된다. 
    • 하나의 스레드에 대해서 start()는 딱 한 번만 호출 가능하다. 
    • 실행이 종료된 스레드는 다시 실행될 수 없다. 
    • 따라서, start()를 두 번이상 호출하려면 두 번째 호출 직전에 새롭게 스레드를 생성해줘야한다.  

 


  Ex) 스레드 생성 방법

  • 아래 두 가지의 방법에 따라서 인스턴스 생성 방법이 다르다. 
  • Thread.currentThread(): 현재 실행 중인 스레드를 반환한다.
  • getName(): 스레드 이름을 반환한다. 
  • 1번 예제의 경우에는 부모 클래스인 Thread 클래스의 메서드를 직접 호출 가능
  • 2번 예제의 경우에는  currentThread()를 호출해서 스레드에 대한 참조를 가져와야만 호출이 가능하다.
<hide/>
public class ThreadEx1 extends  Thread{
    @Override
    public void run() {
        for(int i = 0; i < 5; ++i){
            System.out.println(getName());  // 부모 클래스의 메서드 호출
        }
    }
}


public class ThreadEx2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; ++i) {
            System.out.println(Thread.currentThread().getName());
        }
}

2번 예제, 인터페이스를 구현한 Thread 클래스에 있는 static 메서드 currentThread()


3. start()와  run()

  • main() 안에서 run()을 호출하는 것은 스레드를 실행시키는 게 아니라 단순히 메서드를 호출하는 것이다. 
  • start(): 새로운 스레드가 작업을 실행하는데 필요한 호출 스택을 생성한 다음에 run()을 호출해서 생성된 호출 스택에 run()이  첫 번째로 올라가게 한다. 
    • 바로 실행 되는 게 아니라, 실행 대기 상태가 된다. 
  • 모든 스레드독립적인 작업을 수행하기 위해 자신만의 호출 스택을 필요로 한다. 따라서, 새로운 스레드를 생성하고 실행할 때마다 새로운 호출 스택이 생성되고 스레드가 종료되면 호출 스택은 소멸된다.

call stack (main메서드에서 run()을 호출했을 때의 호출 스택)

 


 

start()가 호출되면 새로운 호출 스택을 하나 만든다. 두 개의 바구

 

새로운 스레드를 생성하고 start()를 호출한 후 호출 스택의 변화

  1.  main 메서드에서 스레드의 start() 호출
  2. start() 는 새로운 스레드를 생성하고 스레드가 작업하는데 사용될 호출 스택을 생성한다. (두번째 사진의 오른쪽 바구니)
  3. 새로 생성된 호출스택에 run()이 호출되서 스레드가 독립된 공간에서 작업을 수행한다. 
    • start()를 실행하면 내부적으로 run()이 자동 호출된다.
  4. 호출 스택이 2개이므로 스케줄러가 정한 순서에 의해서 번갈아가며 작업이 실행된다.

 

  • 호출스택에서는 맨 위에 있는 메서드가 현재 실행중인 메서드이다. 
    • 나머지 메서드들은 대기 상태에 있다. 
    • 위 그림처럼 스레드가 두 개인 경우는 호출 스택의 맨 위 메서드라도 대기 상태일 수 있다. 
  • 각 스레드들은 작성된 스케줄에 따라 순서가 되면 자신의 작업을 수행한다. 
  • 작업을 마친 스레드는 호출스택이 비워지면서 스레드가 사용하던 호출 스택은 사라진다. 

 

 

main 스레드

  • main 메서드의 작업을 수행하는 것도 스레드이다. (main 스레드)
  • 만약, main 메서드가 수행을 마치더라도 다른 스레드가 아직 작업을 마치지 않은 상태라면 프로그램이 종료되지 않는다. 
  • 실행 중인 사용자 스레드가  하나도 없을 때, 프로그램이 종료된다. 

 

 


4. 싱글 스레드와 멀티 스레드

  • 하나의 스레드로 두 개의 작업을 수행하는 시간과 두 개의 스레드로 두 개의 두개의 작업을 수행하는 시간은 거의 같다. 
  • 오히려 두 개의 스레드로 작업하면 시간이 좀 더 걸린다. 
    • 스레드간의 작업 전환(context switching, 컨텍스트 스위칭)에  시간이 걸리기 때문이다.  
    • 컨텍스트 스위칭할 때는 작업의 상태, 다음에 실행할 위치(PC, 프로그램 카운터) 등의 정보를 저장하고 읽어오는 시간이 소요된다. 
    • 컨텍스트 스위칭보다 프로세스의 스위칭에 시간이 더 많이 소요된다. 
    • 따라서, 싱글 코어에서 단순히 CPU만을 사용하는 계산 작업의 경우는 멀티 스레드보다는 싱글 스레드가 효율적이다. 

 

출처 - 「자바의 정석 -남궁성 」

 

 

cf) 병행과 병렬

  • 병행(concurrent): 여러 스레드 동시에 진행하는 것
  • 병렬(parallel): 하나의 작업 여러 스레드가 나눠서 처리하는 것

 

 

  Ex) 싱글 스레드

  • 수행 시간을 측정하기 쉽게 "-" 대신에 new String("-")을 이용해서 수행 속도를 늦춘다. 
<hide/>
public static void main(String[] args) {

    long startTime = System.currentTimeMillis();
    for (int i = 0; i < 300; ++i) {
        System.out.printf("%s", new String("|"));
    }
    System.out.println();
    System.out.println("소요 시간1: " + (System.currentTimeMillis() - startTime));

    for (int i = 0; i < 300; ++i) {
        System.out.printf("%s", new String("-"));
    }
    System.out.println();
    System.out.println("소요 시간1: " + (System.currentTimeMillis() - startTime));

}

  Note) 실행 결과

 

 

 

 

 cf) String 생성 시, 문자열 리터럴과 new String()의 차이점

문자열 리터럴 생성과&nbsp; new String()의 차이

 

 

 

 

  Ex) 멀티 스레드

  • 새로운 스레드를 하나 더 생성해서 두 개의 스레드가 작업을 하나씩 나눠서 수행한다. 
  • 싱글스레드와 다르게, 사진과 같이 두 개의 스레드가 두 개의 작업을 번갈아가면서 수행된다. 

 

  • ThreadTest5
<hide/>
public class ThreadTest5 {
    static long startTime = 0;

    public static void main(String[] args) {
        ThreadTest5_1 th1 = new ThreadTest5_1();
        th1.start();
        startTime = System.currentTimeMillis();

        for (int i = 0; i < 300; ++i) {
            System.out.print(new String("|"));
        }
        System.out.println();
        System.out.println("소요 시간1: " + (System.currentTimeMillis() - ThreadTest5.startTime));
    }
}

 

  • ThreadTest5_1
<hide/>
public class ThreadTest5_1 extends Thread{
    public void run() {
        for (int i = 0; i < 300; ++i) {
            System.out.print(new String("-"));
        }
        System.out.println();
        System.out.println("소요 시간2: " + (System.currentTimeMillis() - ThreadTest5.startTime));
    }
}

 

  Note) 실행 결과

 


5. 스레드의 우선 순위

  • 스레드는 우선 순위(priority)라는 속성(멤버 변수)을 가진다. (1 ~ 10), 숫자가 클수록 우선순위가 높다.
  • 우선 순위 값에 따라서 스레드가 얻는 실행 시간이 달라진다. 
  • 수행하는 작업의 중요도에 따라서 스레드의 우선 순위를 다르게 지정해서 특정 스레드더 많은 작업 시간을 갖도록 할 수 있다.  우선 순위가 높을수록 더 많은 실행시간이 주어진다. 
  • ex) 파일 전송 기능이 있는 메신저의 경우
    • 파일 다운로드를 처리하는 스레드보다 채팅 내용을 전송하는 스레드의 우선 순위가 더 높아야 사용자가 채팅하는데 불편함이 없을 것이다.
  • 관련 메서드
    • void setPriority(int newPriority): 스레드의 우선 순위를 지정한 값으로 변경한다. 
    • int getPriority(): 스레드의 우선 순위를 반환한다. 숫자가 클수록 우선 순위가 높다.
  • 스레드의 우선 순위스레드를 생성한 스레드로부터 상속 받는다. 

  Ex) 

 


6. 스레드 그룹(thread group)

  • 스레드 그룹은 서로 관련된 스레드그룹으로 다루기 위한 것이다. 
  • 스레드 그룹을 생성해서 스레드를 그룹으로 묶어서 관리할 수 있다. 
  • 폴더와 마찬가지로 스레드 그룹 안에 다른 스레드 그룹이 들어갈 수도 있다. 

  Ex) 

 

 


7. 데몬 스레드(deamon thread)

  • 데몬 스레드는 다른 일반 스레드(데몬 스레드가 아닌 스레드)의 작업을 돕는 보조 역할을 하는 스레드이다. 
  • 일반 스레드가 종료되면 데몬 스레드는 필요 없어지기 때문에 강제적으로 종료된다. 
  • 데몬 스레드의  예시) 가비지 컬렉터, 워드프로세서의 자동 저장, 화면 자동 갱신 등
    • 프로그램을 실행하면 JVM은 가비지컬렉션, 이벤트 처리, 그래픽 처리와 같이 프로그램이 실행되는 데 필요한 보조 작업을 수행하는 데몬 스레드들을 자동으로 생성해서 실행시킨다. 
    • 이들은 "system스레드 그룹" 또는 "main 스레드 그룹"에 속한다. 
  • 작성 방법: 무한 루프와 조건문을 이용해서 실행 후 대기하고 있다가 특정 조건을 만족하면 작업을 수행하고 다시 대기하도록 작성한다. 
  • 데몬스레드는 일반 스레드와 작성 방법과 실행 방법이 같은데 스레드는 생성한 다음 실행 전에 setDaemon(true)를 호출하기만 하면 된다. 
  • 데몬 스레드가 생성한 스레드는  자동적으로 데몬 스레드가 된다.
  • 관련 스레드
    • boolean isDaemon(): 스레드가 데몬 스레드인지 확인한다. 
    • void setDaemon(boolean on): 스레드를 데몬 스레드로 또는 사용자 스레드로 변경한다.  매개변수 on의 값을 true로 지정하면 데몬 스레드가 된다. 

  Ex) 

 


8. 스레드의 실행 제어

  • 동기화(synchronization)와 스케줄링(scheduling) 때문에 스레드 프로그래밍이 어려운 것이다. 
  • 효율적인 멀티스레드 프로그램을 만들기 위해서는 보다 정교한 스케줄링을 통해서 프로세스에게 주어진 자원과 시간을 여러 스레드가 낭비 없이 잘 사용하도록 프로그래밍해야한다. 

 


스레드 스케줄링 메서드

  • sleep(long millis): 일정 시간 동안 스레드를 멈추게 한다. 
    • 일시 정지 상태가 된 스레드는 지정 시간이 다 되거나 interrupt()가 호출되면 (InterruptedException 발생) 잠에서 깨어나 실행 대기 상태가 된다. 
  • interrupt(), interrupted(): 스레드 작업을 멈추라고 요청한다. 
    • 요청할 뿐이며 강제로 종료시키지는 못한다.  
    • 진행 중인 스레드 작업이 끝나기 전에 취소해야할 때 쓴다. 
    • ex) 큰 파일을 다운로드받을 때, 시간이 너무 오래 걸리면 중간에 다운로드를 취소할 수 있어야한다. 
    • 스레드가 sleep(), wait(), join() 에 의해서 일시 정지 상태에 있을 때, 해당 스레드에 대해서 interrupt()를 호출하면  sleep(), wait(), join()에서 InterruptedException이 발생하고 스레드는 실행 대기 상태(RUNNABLE)로 바뀐다. 멈춰있던 스레드를 깨워서 실행 가능 상태로 만드는 것이다. 
  • suspend(), resume(), stop()
    • suspend(): 스레드를 멈추게 한다.  (교착상태 문제가 있음)
    • resume(): suspend() 실행 후, resume()를 호출해야 다시 실행 대기 상태가 된다. 
    • stop(): 호출 즉시 스레드가 종료된다. (교착상태 문제가 있음)
    • 세 메서드는 스레드 실행을 제어하는 쉬운 방법이 지만 suspend(), stop()는 교착 상태(deadlock)을 일으키기 쉽게 작성되어 있으므로 사용이 권장되지 않는다.  Deprecated(전에는 사용됐으나 앞으로는 사용하지 않을 것을 권장한다.)
  • yield():  주어진 실행시간을 다른 스레드에게 양보한다.  
  • join(): 다른 스레드의 작업을 기다린다. 스레드 자신이 하던 작업을 멈추고 다른 스레드가 지정된 시간동안 작업을 수행하도록 할 때, join()을 사용한다. 
    • sleep()과 유사한 면이 많으나 join()은 현재 스레드가 아닌 특정 스레드에 대해 동작하므로 static 메서드가 아니다. 
  •  

  Ex) 

 


9. 스레드의 동기화

  • 멀티스레드 프로세스의 경우, 여러 스레드가 같은 프로세스 내의 자원을 공유해서 작업하므로 서로의 작업에 영향을 준다. 
  • 이와 관련 문제점을 막기 위해서 한 스레드가 특정 작업을 끝마치기 전까지다른 스레드에 의해 방해받지 않도록 하는 게 필요하다. 
    • 그래서 임계 영역(critical section)과 잠금(lock)이다. 
    • 임계 영역(critical section): 공유 데이터(객체)를 사용하는 코드 영역
    • 잠금(lock, 락): 열쇠 같은 개념.
      • 한 스레드가 특정 작업을 마칠 때까지 방해받지 않도록 lock획득한다. 
      • 해당 스레드가 임계 영역 내의 모든 코드를 수행하고 벗어나서 lock반납해야만 다른 스레드가 lock을 획득해서 임계 영역의 코드를 수행할 수 있게 된다. 
  • Def) 스레드 동기화(synchronization): 한 스레드가 진행 중인 작업을 다른 스레드 간섭하지 못하도록 막는 것을 말한다.  
    • java.util.concurrent.locks
    • java.util.concurrent.atomic
    • 위 두 패키지를 통해 다양한 방식으로 동기화를 구현할 수 있도록 지원하고 있다.

 

 


9.1 synchronized를 이용한 동기화

  1. 메서드 전체를 임계 영역으로 지정
  2. 특정한 영역을 임계 영역으로 지정
    •  두 방법 모두 lock의 획득반납이 자동으로 이뤄진다. 

 

 

  Ex 1) 메서드 전체를 임계 영역으로 지정한다

  • 메서드 앞에 synchronized를 붙이면 메서드 전체가 임계 영역으로 설정된다. 
  • 메서드가 호출된 시점부터 해당 메서드가 포함된 객체의 lock을 얻어서 작업을 하다가 메서드가 종료되면 lock을 반환한다. 
<hide/>
public synchronized void calcSum(){

}

 

 

  Ex 2) 특정한 영역을 임계 영역으로 지정

  • 메서드 내의 코드 일부를 {}로 감싸고 앞에 synchronized(참조 변수)를 붙인다. 
  • 블럭 안으로 들어갈 때부터 스레드는 지정된 객체의 lock을 얻게 되고 블럭을 벗어나면 lock을 반납한다. 
<hide/>
synchronized(객체의 참조 변수){

}

 


9.2 wait() 과 notify()

  • 동기화해서 공유 데이터를 보호하면 좋지만 특정 스레드가 객체의을 가진 상태로 오랜 시간을 보내지 않도록 해야한다. 
  • 이런 상황을 개선하기 위해 나온 것 wait(), notify()이다.
  • wait(): 동기화된 임계 영역의 코드를 수행하다가 작업을 더이상 진행할 상황이 아니면 wait()를 호출해서 스레드가 락을 반납하고 기다리도록 한다. 
  • 그러면 다른 스레드가 락을 얻어서 해당 객체에 대한 작업을 수행할 수 있다. 
  • notify(): 나중에 작업을 진행할 수 있는 상황이 되면 notify()를 호출해서 작업 중단했던 스레드가 다시을 얻어서 작업을 진행할 수 있도록 한다. 
    • 대기중인 스레드가 여러 개인 경우, 그 중 임의의 한 스레드에만 통보한다. 
  • notifyAll(): 대기 중인 모든 스레드에게 통보한다. 기다리는 스레드 중 하나만 락을 얻을 수 있다. 
    • notifyAll()을 이용하면 스레드의 기아 현상을 막을 수 있지만 스레드를 구분해서 각 스레드에게 다르게 통지하는 것이 필요하다. wait, notify 로는 불가능하지만 뒤이어 나오는 Lock, Condition을 이용하면 선별적 통지가 가능하다. 

 

cf) 기아 현상과 경쟁 상태

  • 기아 현상
  • 경쟁 상태: 여러 스레드가 하나의 자원을 두고 lock을 얻기 위해 경쟁하는 상태를 말한다. 

 


9.3 lock과 condition을 이용한 동기화

 

  • synchronized 키워드와 lock(java.util의 locks 패키지) 을 이용해서 동기화할 수 있다. 
  • synchronized 키워드를 이용하면 lock이 자동으로 잠기고 풀린다. 
  • synchronized 블럭 내에서 예외가 발생하면 lock은 자동으로 풀린다.

 

 

동시성 제어에 관한) 락 분류

  • 배타적인 락 (exclusive lock)
    • 스레드가 자원에 접근할 때 락을 획득하고 다른 스레드는 해당 자원에 접근할 때까지 기다리는 방식
    • 락 획득
    • 다른 트랜잭션 차단
    • 트랜잭션 수행
    • 락 해제
    • 배타적인 락은 여러 트랜잭션이 동시에 여러 트랜잭션이 충돌없이 안전하게 데이터에 접근해야하는 경우 사용하며, 낙관적 락충돌이 적은 상황에서 성능을 향상시킨다.
  • 낙관적인 락(optimistic lock)
    • 스레드가 자원에 접근할 때, 락을 획득하지 않고 작업을 수행한 후, 변경이 충돌하지 않았는지 확인하는 방식
    • 락 획득 없이 읽기
    • 트랜잭션 수행
    • 락 얻기 시도
    • 충돌 확인
    • 트랜잭션 완료 또는 롤백하거나 다시 시도

9.4 volatile

  • 변수의 값을 메인 메모리에 즉시 반영해서 스레드간의 가시성 문제를 해결하는데 사용된다
  • volatile 키워드를 사용해서 변수를 선언하면 메인 메모리에서 직접 읽고 쓰기를 하게 된다.
  • 멀티 코어 프로세스는 각 코어 별로 별도의 캐시가 존재한다.
  • 코어는 메모리에서 읽어온 값을 코어 내부의 캐시에 저장하고 캐시에서 값을 읽어서 작업한다. 다시 같은 값을 읽을때에는 캐시에 값이 있는지 확인하고 없을 때에만 메모리에서 읽어온다.
  • 그러다 보니 메모리에 저장된 값이 변경되었는데 캐시에 갱신되지 은 경우 문제가 발생한다.
  • 이럴 때 volatile을 붙이면 데이터를 캐시가 아닌 메모리에서 읽어오므로 캐시 ↔ 메모리 간의 데이터 불일치가 해결도
  • volatile을 변수 앞에 선언하는 대신 (변수를 읽고쓰는 영역에)synchronized 를 사용해소 같은 효과를 얻는다.
    • 블럭으로 들어갈 때, 나올 때, 캐시와 메모리 간의 동기화가 이뤄지기 때문에 값의 불일치가 해소된다.

9.5 fork & join

  • fork(): 작업을 스레드의 작업 큐에 넣는다.  비동기 메서드
  • join(): 해당 작업의 수행이 끝날 때까지 기다렸다가 수행이 끝나면 결과를 반환한다. 동기 메서드
  • 비동기 메서드는 메서드를 호출하기만 할 뿐, 결과를 기다리지 않는다. 
    • 내부적으로는 다른 스레드에게 작업을 수행하도록 지시하고 결과를 기다리지 않고 돌아온다. 

 

TITLE

  • CON
  • CON

 


모르는 부분 및 기타

  • 호출스택(call stack, 콜스택): 컴퓨터 프로그램에서 현재 실행 중서브 루틴에 관한 정보를 저장하는 스택 자료구조이다. 먼저 호출되는 메서드가 콜 스택에 먼저 쌓이는 구조이다. 
  • DOS(Disk Operating System, 도스, 디스크 운영체제): UNIX와 더붙어 한 시대를 풍미했던 텍스트 기반의 대표적인 운영체제를 말한다. 멀티태스킹 불가능하다. 
  • 스케줄러(scheduler): 실행 대기 중인 스레드들의 우선순위를 고려해서 실행 순서와 실행 시간을 결정한다. 
  • 코어(core): CPU의 안에서 시스템의 모든 연산(데이터 처리)을 수행하는 부위이다.
  • Java는 OS에 대해  독립적인 언어라고 하지만  종속전인 부분이 몇 가지 있다. 스레드도 이에 해당한다.

 

 

출처 - 「Java의 정석 2 - 남궁성」