Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[Java] 예제 소스 정리 - 스레드 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Java

[Java] 예제 소스 정리 - 스레드

끈기JK 2023. 2. 4. 10:19

com.eomcs.concurrent.ex1~7

 

 

예제 소스 정리

 

스레드

 

com.eomcs.concurrent

 

 

1) 스레드 사용 전
package com.eomcs.concurrent;

public class Exam0110 {

  public static void main(String[] args) {

    int count = 1000;

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

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

 

 

2) 스레드 사용 후 => 패키지 멤버 클래스로 스레드 구현하기
package com.eomcs.concurrent;

public class Exam0120 {

  public static void main(String[] args) {

    int count = 1000;

    MyThread t = new MyThread(count);
    t.start();

    for (int i = 0; i < count; i++) {
      System.out.println(">>> " + i);
    }
  }
}
package com.eomcs.concurrent;

public class MyThread extends Thread {

  int count;

  public MyThread(int count) {
    this.count = count;
  }

  @Override
  public void run() {
    for (int i = 0; i < count; i++) {
      System.out.println("==> " + i);
    }
  }
}

 

 

3) 인스턴스 생성 후 즉시 메서드 호출하기
package com.eomcs.concurrent;

public class Exam0130 {

  public static void main(String[] args) {

    int count = 1000;

    new MyThread(count).start();

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

 

 

4) 패키지 멤버를 스태틱 중첩 클래스로 만든다.
package com.eomcs.concurrent;

public class Exam0140 {

  public static void main(String[] args) {

    int count = 1000;

    new MyThread(count).start();

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

  static class MyThread extends Thread {

    int count;

    public MyThread(int count) {
      this.count = count;
    }

    @Override
    public void run() {
      for (int i = 0; i < count; i++) {
        System.out.println("==> " + i);
      }
    }
  }

}

 

 

5) 스태틱 중첩 클래스를 로컬 클래스로 만든다.
package com.eomcs.concurrent;

public class Exam0150 {

  public static void main(String[] args) {
    class MyThread extends Thread {

      int count;

      public MyThread(int count) {
        this.count = count;
      }

      @Override
      public void run() {
        for (int i = 0; i < count; i++) {
          System.out.println("==> " + i);
        }
      }
    }

    int count = 1000;

    new MyThread(count).start();

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

 

 

6) 로컬 클래스가 바깥 메서드의 변수를 사용할 때 로컬 클래스에서 그 변수의 값을 다룰 수 있도록 그와 관련된 인스턴스 필드와 생성자 파라미터를 컴파일러가 자동으로 만드는 기법을 활용한다.
package com.eomcs.concurrent;

public class Exam0160 {

  public static void main(String[] args) {

    int count = 1000;

    class MyThread extends Thread {

      // 컴파일러가 바깥 메서드의 count 변수 값을 담을 수 있도록 필드를 자동으로 생성한다.
      //
      //      int count; 

      // 컴파일러가 바깥 메서드의 count 변수를 값을 필드에 담을 수 있게 
      // 생성자에 파라미터를 추가하고 필드에 파라미터 값을 저장하는 코드를 자동 생성한다.
      // 
      //      public MyThread(int count) {
      //        this.count = count;
      //      }

      @Override
      public void run() {
        for (int i = 0; i < count; i++) {
          System.out.println("==> " + i);
        }
      }
    }

    // 컴파일러가 MyThread 생성자를 호출할 때 count 변수의 값을 넘기는 
    // 코드를 자동으로 생성할 것이니 개발자가 직접 넘길 필요가 없다.
    // 
    //    new MyThread(count).start();
    new MyThread().start(); 


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

 

 

7) 로컬 클래스를 익명 클래스로 만든다.
package com.eomcs.concurrent;

public class Exam0170 {

  public static void main(String[] args) {

    int count = 1000;

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

    t.start(); 

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

 

 

8) 익명 클래스의 인스턴스가 들어갈 자리에 익명 클래스 정의 코드를 직접 둔다.
package com.eomcs.concurrent;

public class Exam0180 {

  public static void main(String[] args) {

    int count = 1000;

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

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

 

 

1) 스레드 사용 전
package com.eomcs.concurrent;

public class Exam0210 {

  public static void main(String[] args) {

    int count = 1000;

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

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

 

 

2) 스레드 사용 후 : Runnable 구현체를 패키지 멤버로 만들어 Thread로 실행한다.
package com.eomcs.concurrent;

public class Exam0220 {

  public static void main(String[] args) {

    int count = 1000;

    //    MyRunnable r = new MyRunnable(count);
    //    Thread t = new Thread(r);
    //    t.start();

    //    Thread t = new Thread(new MyRunnable(count));
    //    t.start();

    new Thread(new MyRunnable(count)).start();

    for (int i = 0; i < count; i++) {
      System.out.println(">>> " + i);
    }
  }
}
package com.eomcs.concurrent;

public class MyRunnable implements Runnable {

  int count;

  public MyRunnable(int count) {
    this.count = count;
  }

  @Override
  public void run() {
    for (int i = 0; i < count; i++) {
      System.out.println("==> " + i);
    }
  }
}

 

 

  3) 패키지 멤버를 스태틱 중첩 클래스로 만든다.
package com.eomcs.concurrent;

public class Exam0230 {

  public static void main(String[] args) {

    int count = 1000;

    new Thread(new MyRunnable(count)).start();

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

  static class MyRunnable implements Runnable {

    int count;

    public MyRunnable(int count) {
      this.count = count;
    }

    @Override
    public void run() {
      for (int i = 0; i < count; i++) {
        System.out.println("==> " + i);
      }
    }
  }
}

 

 

4) 스태틱 중첩 클래스를 로컬 클래스로 만든다.
package com.eomcs.concurrent;

public class Exam0240 {

  public static void main(String[] args) {

    class MyRunnable implements Runnable {

      int count;

      public MyRunnable(int count) {
        this.count = count;
      }

      @Override
      public void run() {
        for (int i = 0; i < count; i++) {
          System.out.println("==> " + i);
        }
      }
    }

    int count = 1000;

    new Thread(new MyRunnable(count)).start();

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

 

 

5) 로컬 클래스의 특징을 활용하여 바깥 변수의 값을 받는 코드를 제거한다.
왜? 컴파일러가 자동으로 그런 일을 할 코드를 생성해주기 때문이다.
package com.eomcs.concurrent;

public class Exam0250 {

  public static void main(String[] args) {

    int count = 1000;

    class MyRunnable implements Runnable {
      @Override
      public void run() {
        for (int i = 0; i < count; i++) {
          System.out.println("==> " + i);
        }
      }
    }

    new Thread(new MyRunnable()).start();

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

 

 

6) 로컬 클래스를 익명 클래스로 만든다.
package com.eomcs.concurrent;

public class Exam0260 {

  public static void main(String[] args) {

    int count = 1000;

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

    new Thread(r).start();

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

 

 

  7) 익명 클래스 코드를 메서드로 파라미터에 직접 넣는다.
package com.eomcs.concurrent;

public class Exam0270 {

  public static void main(String[] args) {

    int count = 1000;

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

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

 

 

8) 익명 클래스를 람다 표현식으로 전환한다.
package com.eomcs.concurrent;

public class Exam0280 {

  public static void main(String[] args) {

    int count = 1000;

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

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

 

 

 

com.eomcs.concurrent.ex1

 

 

멀티 스레드 적용 전 - 멀티 태스킹을 적용하기 전
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 클래스를 정의할 때 분기해서 실행할 코드를 담으면 된다.
 그러면 두 개의 실행 흐름이 서로 왔다 갔다 하면서 실행된다.

 ## 멀티태스킹(multi-tasking)
 - 한 개의 CPU가 여러 코드를 동시(?)에 실행하는 것.
 - 실제는 일정한 시간을 쪼개 이 코드와 저 코드를 왔다갔다 하면서 실행한다.
 - 그럼에도 불구하고 외부에서 봤을 때는 명령어가 동시에 실행되는 것 처럼 보인다.
 - 왜? CPU 속도가 워낙 빠르기 때문이다.

 ## CPU의 실행 시간을 쪼개서 배분하는 정책 : CPU Scheduling 또는 프로세스 스케줄링
 - CPU의 실행 시간을 쪼개 코드를 실행하는 방법이다.
 1) Round-Robin 방식
 - Windows OS에서 사용하는 방식
 - CPU 실행 시간을 일정하게 쪼개서 각 프로세스에 분배하는 방식
 2) Priority 방식
 - Unix, Linux 에서 사용하는 방식
 - 우선 순위가 높은 프로세스에 더 많은 실행 시간을 배정하는 방식
 - 문제점:
   - 우선 순위가 낮은 프로그램인 경우 CPU 시간을 배정 받지 못하는 문제가 발생했다.
   - 그래서 몇 년이 지나도록 실행되지 않는 경우가 나타났다.
 - 해결책?
   - CPU 시간을 배정 받지 못할 때 마다 
     즉 다른 프로세스에 밀릴 대 마다 우선 순위를 높여서
     언젠가는 실행되게 만들었다.
   - 이런 방식을 "에이징(aging) 기법"이라 부른다.

 ## 멀티 태스킹을 구현하는 방법
 1) 멀티 프로세싱
 - 프로세스(실행 중인 프로그램)를 복제하여 분기한다.
 - 그리고 분기된 프로세스를 실행시켜서 작업을 동시에 진행하게 한다.
 - 장점:
   - 분기하기가 쉽다. fork() 호출.
   - 즉 구현(프로그래밍)하기가 쉽다.
 - 단점:
   - 프로세스를 그대로 복제하기 때문에 
     프로세스가 사용하는 메모리도 그대로 복제된다.
   - 메모리 낭비가 심하다.
   - 복제된 프로세스는 독립적이기 때문에 
     실행 종료할 때도 일일이 종료해야 한다.

 2) 멀티 스레딩
 - 특정 코드만 분리하여 실행한다.
 - 따라서 프로세스가 사용하는 메모리를 공유한다.
 - 장점:
   - 프로세스의 힙 메모리를 공유하기 때문에 메모리 낭비가 적다.
   - 모든 스레드는 프로세스에 종속되기 때문에 프로세스를 종료하면
     스레드도 자동 종료된다.
 - 단점:
   - 프로세스 복제 방식에 비해 코드 구현이 복잡하다.

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

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

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

 

 

 

com.eomcs.concurrent.ex2

 

 

현재의 실행 라인을 알아내기
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());

    // 실행 흐름을 전문적인 용어로 "Thread(실 타래)"라 부른다.
    // 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)
// => 다른 하위 그룹은 없다!

 

 

스레드 그룹의 부모 그룹
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" 스레드 그룹의 자식 그룹들
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" 스레드 그룹에 소속된 스레드들
package com.eomcs.concurrent.ex2;

public class Exam0170 {

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

    Thread[] arr = new Thread[100];
    int count = systemGroup.enumerate(arr, false);

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

// JVM의 스레드 계층도:
// system(TG)
// => Reference Handler(T)
// => Finalizer(T)
// => Signal Dispatcher(T)
// => Attach Listener(T)
// => main(TG)
// ...=> main(T) : main() 메서드를 호출한다.
// => InnocuousThreadGroup(TG)

 

 

JVM의 전체 스레드 계층도
package com.eomcs.concurrent.ex2;

public class Exam0180 {

  public static void main(String[] args) {
    // JVM의 최상위 스레드 그룹인 system의 계층도 출력하기
    Thread mainThread = Thread.currentThread();
    ThreadGroup mainGroup = mainThread.getThreadGroup();
    ThreadGroup systemGroup = mainGroup.getParent();

    printThreads(systemGroup, "");
  }

  static void printThreads(ThreadGroup tg, String indent) {
    System.out.println(indent + tg.getName() + "(TG)");

    // 현재 스레드 그룹에 소속된 스레드들 출력하기
    Thread[] threads = new Thread[10];
    int size = tg.enumerate(threads, false);
    for (int i = 0; i < size; i++) {
      System.out.println(indent + "  ==> " + threads[i].getName() + "(T)");
    }

    // 현재 스레드 그룹에 소속된 하위 스레드 그룹들 출력하기
    ThreadGroup[] groups = new ThreadGroup[10];
    size = tg.enumerate(groups, false);
    for (int i = 0; i < size; i++) {
      printThreads(groups[i], indent + "  ");
    }
  }
}

// JVM의 스레드 계층도: (openjdk 11 기준)
// system(TG)
//   ==> Reference Handler(T)
//   ==> Finalizer(T)
//   ==> Signal Dispatcher(T)
//   ==> Attach Listener(T)
//   ==> main(TG)
//         ==> main(T)
//   ==> InnocuousThreadGroup(TG)
//         ==> Common-Cleaner(T)

 

 

 

com.eomcs.concurrent.ex3

 

 

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

public class Exam0110 {

  public static void main(String[] args) {

    // 1) Thread 클래스를 상속 받아 정의하기
    // => 구현하기 편하다.
    // => 그런데 다중 상속이 불가능하기 때문에 다른 클래스를 상속 받을 수 없다.
    // => 즉 MyThread가 다른 클래스를 상속 받으면서 스레드가 될 순 없다.
    //
    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);
    }
  }
}

CPU 사용을 스레드에게 배분할 때, 스레드를 생성한 순서대로 배분하지는 않는다.
 OS의 CPU 스케줄링 정책에 따라 스레드가 실행된다.
 즉 JVM에서 스레드를 실행하는 것이 아니라 OS가 실행한다.
 결론!
 => 똑 같은 자바의 스레드 코드가 OS에 따라 실행 순서가 달라질 수 있다.

 우선 순위로 조정하면 되지 않나요?
 => Windows OS의 경우 우선 순위(priority) 값이 실행 순서나 실행 회수에 큰 영향을 끼치지 않는다.
    그래서 우선 순위의 값을 조정하여 스레드의 실행 회수를 조정하려 해서는 안된다.
 => 왜? OS에 따라 실행 정책이 다르기 때문이다.
 => 그냥 특정 코드를 동시에 실행하고 싶을 때 스레드를 사용한다고 생각하라!

 

 

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
package com.eomcs.concurrent.ex3;

public class Exam0210 {

  public static void main(String[] args) {

    // 2) Runnable 인터페이스를 구현하기
    // => 실무에서 스레드를 만들 때 많이 사용한다.
    // => 인터페이스를 구현하는 것이기 때문에 다른 클래스를 상속 받을 수 있다.
    // => 직접적으로 스레드가 아니기 때문에 실행할 때는 Thread의 도움을 받아야 한다.
    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 - 익명 클래스로 구현하기
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)로 구현하기
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("프로그램 종료?");
  }
}

 

 

 

com.eomcs.concurrent.ex4

 

 

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

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

 

 

스레드의 생명주기(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() 종료!");
  }

}

 

 

스레드의 생명주기(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();

  }

}

 

 

스레드의 생명주기(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("스레드 실행 후");
  }

}

 

 

스레드의 생명주기(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 상태로 접어든다.
    // running 상태는 실행하고 있는 상태 뿐만 아니라,
    // CPU를 받을 수 있는 상태이기도 하다. 
    // => CPU는 OS의 관리 정책(CPU Scheduling)에 따라 스레드나 프로세스에 배분된다. 
    //    물론 OS가 CPU를 배분한 후 임의시간 후에 
    //    다시 회수하여 다른 스레드(현재 스레드 포함)나 프로세스에 배분한다.
    //    때에 따라서 같은 스레드가 연속해서 배분 받는 경우도 있을 것이다.
    // 
    t1.start();
    t2.start();
    t3.start();

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

}

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

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

 

 

스레드의 생명주기(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());

    // 우선 순위가 높으면 CPU 사용 배분을 좀 더 자주 받는다.
    // => 스레드는 JVM에서 관리하는 것이 아니라 OS가 관리한다.
    // => 즉 OS의 스레드를 이용하는 것이다.
    // => 따라서 우선 순위에 따라 실행 스케줄을 어떻게 관리할지는 OS에 따라 다르다.
    // => Windows OS는 우선 순위를 크게 고려하지 않는다.
    //    그래서 Windows에서 실행할 때는 우선 순위에 영향을 적게 받을 것이다.
    // => Unix, Linux 계열 OS는 우선 순위를 고려한다.
    //    그래서 이런 OS에서 실행할 때는 우선 순위에 영향을 받을 것이다.
    //
    // 주의!
    // => Java 의 캐치프레이즈가 "Write Once, Run Anywhere!" 이다.
    // => 즉 OS에 상관없이 동일하게 동작하게 만드는 것이 자바의 목적이다.
    // => 그런데 우선 순위에 따라 실행률이 달라지고,
    //    OS 마다 차이가 난다면, 자바의 목적에 부합하는 것이 아니다.
    // => 그래서 가능한 OS에 영향을 덜 받는 방식으로 코딩해야 한다.
    // => 이런 이유로 스레드를 다룰 때 우선 순위를 고려하는 방식으로
    //    프로그래밍을 하지 말라!

  }

}

 

 

스레드의 생명주기(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를 배분한다.
    // 그러나 Windows 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);
  }

}

 

 

 

com.eomcs.concurrent.ex5

 

 

멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 1
package com.eomcs.concurrent.ex5;

public class Exam0110 {

  static class MyList {
    int[] values = new int[100];
    int size;

    public void add(int value) {
      // 여러 스레드가 동시에 이 메서드에 진입하면 
      // 배열의 값을 덮어쓰는 문제가 발생한다.
      // 이렇게 여러 스레드가 동시에 접근했을 때 문제가 발생하는 코드 부분을
      // "Critical Section" 또는 "Critical Region" 이라 부른다.
      if (size >= values.length) {
        delay();
        return;
      }
      delay();
      values[size] = value;
      delay();
      size = size + 1;
      delay();
    }

    public void print() {
      for (int i = 0; i < size; i++) {
        System.out.printf("%d:  %d\n", i, values[i]);
      }
    }

    public void delay() {
      int count = (int)(Math.random() * 1000);
      for (int i = 0; i < count; i++) {
        Math.atan(34.1234);
      }
    }
  }

  static class Worker extends Thread {
    MyList list;
    int value;

    public Worker(MyList list, int value) {
      this.list = list;
      this.value =  value;
    }

    @Override
    public void run() {
      for (int i = 0; i < 20; i++) {
        list.add(value);
      }
    }
  }

  public static void main(String[] args) throws Exception {
    MyList list = new MyList();

    Worker w1 = new Worker(list, 111);
    Worker w2 = new Worker(list, 222);
    Worker w3 = new Worker(list, 333);

    w1.start();
    w2.start();
    w3.start();

    Thread.sleep(10000);

    list.print();
  }

}

 

 

멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 1의 문제 해결
package com.eomcs.concurrent.ex5;

public class Exam0120 {

  static class MyList {
    int[] values = new int[100];
    int size;

    // Critical Section에 오직 한 개의 스레드만 접근하게 하면 
    // 비동기로 인한 문제가 발생하지 않는다.
    // => 즉 동기화로 처리한다.
    // => 동기화? 
    //    - 여러 스레드가 동시에 실행하는 것이 아니고
    //      여러 스레드가 순차적으로 접근하는 것.
    //    - 단 순차적으로 실행한다는 것은 동시 실행의 이점을 버리는 것이기 때문에
    //      스레드를 사용하기 전의 상태와 같다.
    //      기존의 실행 방식 처럼 실행 시간이 많이 걸린다.
    // 
    // 다음 메서드를 동기화 처리해 보자.
    // => synchronized
    //    - 크리티컬 섹션 구간에 이 키워드를 붙이면 오직 한 번에 한 개의 스레드만이 접근할 수 있다.
    //    - 먼저 접근한 스레드가 나가야만 다음 스레드가 진입할 수 있다.
    //    - 즉 크리티컬 섹션을 뮤텍스 구간으로 설정한다.
    //
    synchronized public void add(int value) {
      if (size >= values.length) {
        delay();
        return;
      }
      delay();
      values[size] = value;
      delay();
      size = size + 1;
      delay();
    }

    public void print() {
      for (int i = 0; i < size; i++) {
        System.out.printf("%d:  %d\n", i, values[i]);
      }
    }

    public void delay() {
      int count = (int)(Math.random() * 1000);
      for (int i = 0; i < count; i++) {
        Math.atan(34.1234);
      }
    }
  }

  static class Worker extends Thread {
    MyList list;
    int value;

    public Worker(MyList list, int value) {
      this.list = list;
      this.value =  value;
    }

    @Override
    public void run() {
      for (int i = 0; i < 20; i++) {
        list.add(value);

        // add() 호출 후 다른 스레드에게 CPU 사용권을 뺏길 기회를 만들자!
        int count = (int)(Math.random() * 1000);
        for (int x = 0; x < count; x++) {
          Math.atan(34.1234);
        }
      }
    }
  }

  public static void main(String[] args) throws Exception {
    MyList list = new MyList();

    Worker w1 = new Worker(list, 111);
    Worker w2 = new Worker(list, 222);
    Worker w3 = new Worker(list, 333);

    w1.start();
    w2.start();
    w3.start();

    Thread.sleep(10000);

    list.print();
  }

}

 

 

멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 2
package com.eomcs.concurrent.ex5;

public class Exam0210 {

  static class Account {
    String accountId;
    long balance;

    public Account(String accountId, long balance) {
      this.accountId = accountId;
      this.balance = balance;
    }

    public long withdraw(long money) {

      // 이 메서드처럼 여러 스레드가 같은 메모리(balance 필드)의 값을 
      // 동시에 변경할 때 문제가 발생할 수 있는 코드를 
      // "크리티컬 섹션(임계영역; critical section)" 
      // 또는 "크리티컬 리전(critical region)"
      // 이라 부른다.

      long b = this.balance;

      delay();  // CPU를 뺏길 기회를 제공

      b -= money;

      delay();  // CPU를 뺏길 기회를 제공

      if (b < 0)
        return 0;

      delay();  // CPU를 뺏길 기회를 제공

      this.balance = b;

      delay();  // CPU를 뺏길 기회를 제공

      return money;
    }

    private void delay() {
      int delayCount = (int)(Math.random() * 1000);
      for (int i = 0; i < delayCount; i++)
        Math.asin(45.765); // CPU를 뺏길 기회를 제공
    }
  }

  static class ATM extends Thread {
    Account account;

    public ATM(String name, Account account) {
      super(name);
      this.account = account;
    }

    @Override
    public void run() {
      long money = 0;
      long sum = 0;

      while (true) {
        money = account.withdraw(100);
        if (money <= 0)
          break;
        sum += money;
      }
      System.out.printf("%s에서 찾은 돈: %d원\n", this.getName(), sum);
    }
  }

  public static void main(String[] args) {
    Account account = new Account("111-11-1111-111", 100_0000);

    ATM 강남 = new ATM("강남", account);
    ATM 서초 = new ATM("서초", account);
    ATM 부산 = new ATM("부산", account);
    ATM 대전 = new ATM("대전", account);
    ATM 광주 = new ATM("광주", account);

    강남.start();
    서초.start();
    부산.start();
    대전.start();
    광주.start();

    // 강남, 서초 등 여러 개의 스레드가
    // 같은 객체에 대해 메서드를 호출하여 동시에 값을 변경하려 할 때
    // 서로 그 메모리의 값을 덮어쓰는 문제가 발생한다.
    // 이처럼 여러 스레드가 동시에 실행할 때 문제를 일으키는 코드를
    // "임계 구역(Critical Section; Critical Region)"이라 부른다.
    // 이 예제에서는 여러 스레드가 동시에 호출하고,
    // 같은 인스턴스의 변수 값을 변경하는 메서드인 
    // "withdraw()"가 critical section이다.
  }
}

 용어정리!
 
 임계 구역(critical section)
 - 여러 스레드가 동시에 실행할 때 문제가 발생하는 코드 블록을 말한다.
 - critical region 이라고도 부른다.
 - 같은 메모리에 여러 스레드가 동시에 접근하여 값을 변경하려 할 때 문제가 발생하는 것이다.
   즉 다른 스레드가 사용하는 변수의 값을 임의로 변경하면 그 스레드는 원래의 의도대로 
   동작하지 않을 것이다.
 - "스레드 안전(thread safe)하지 않다"라고 말한다.

 

 

멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 2의 문제점 해결
package com.eomcs.concurrent.ex5;

public class Exam0220 {

  static class Account {
    String accountId;
    long balance;

    public Account(String accountId, long balance) {
      this.accountId = accountId;
      this.balance = balance;
    }

    // 한 번에 한 스레드 만이 호출하도록 접근을 제한하고 싶다면
    // 메서드 전체를 동기화 블록으로 선언하라!
    // 어떻게? 
    // 메서드 앞에 synchronized를 붙인다.
    // => 여러 스레드가 동시에 접근했을 때 문제가 발생하지 않는 코드에 대해 
    //    synchronized 사용한다면 실행 속도가 떨어질 것이다.
    // => 왜? 
    //    한 번에 한 스레드만 순차적으로 접근하기 때문이다.
    //
    // 이렇게 크리티컬 섹션에 동시에 접근하지 못하게 하는 기법
    // => "뮤텍스(mutex)" 또는 "세마포어(1)(semaphore)"라 부른다.
    // 
    // 자바에서 뮤텍스를 구현하는 방법,
    // => 크리티컬 섹션에 해당하는 메서드나 코드 블록에 
    //    sychronized 키워드를 붙여
    //    한 번에 한 스레드만 진입할 수 있도록 lock을 건다.
    // 
    // 참고!
    // => 여러 스레드가 동시에 실행해도 문제가 없는 코드 블록을
    //    "스레드 안전(thread safe)"라 부른다.
    //
    //
    synchronized public long withdraw(long money) {
      long b = this.balance;

      delay();  // CPU를 뺏길 기회를 제공

      b -= money;

      delay();  // CPU를 뺏길 기회를 제공

      if (b < 0)
        return 0;

      delay();  // CPU를 뺏길 기회를 제공

      this.balance = b;

      delay();  // CPU를 뺏길 기회를 제공

      return money;
    }

    private void delay() {
      int delayCount = (int)(Math.random() * 1000);
      for (int i = 0; i < delayCount; i++)
        Math.asin(45.765); // CPU를 뺏길 기회를 제공
    }
  }

  static class ATM extends Thread {
    Account account;

    public ATM(String name, Account account) {
      super(name);
      this.account = account;
    }

    @Override
    public void run() {
      long money = 0;
      long sum = 0;

      while (true) {
        money = account.withdraw(100);
        if (money <= 0)
          break;
        sum += money;
      }
      System.out.printf("%s에서 찾은 돈: %d원\n", this.getName(), sum);
    }
  }

  public static void main(String[] args) {
    Account account = new Account("111-11-1111-111", 100_0000);

    ATM 강남 = new ATM("강남", account);
    ATM 서초 = new ATM("서초", account);
    ATM 부산 = new ATM("부산", account);
    ATM 대전 = new ATM("대전", account);
    ATM 광주 = new ATM("광주", account);

    강남.start();
    서초.start();
    부산.start();
    대전.start();
    광주.start();

    // 비동기 문제 해결책?
    // => 한 번에 한 스레드 만이 크리티컬 섹션을 실행하도록 접근을 제한하면 된다.
    // 
    // 주의!
    // => 동시에 여러 스레드가 같은 메모리에 접근하더라도 
    //    값을 변경하는 것이 아니라 단순히 값을 조회하는 경우에는 
    //    멀티 스레드 문제가 발생하지 않는다.
    //
    // 세마포어(n); semaphore
    // => 크리티컬 섹션에 진입할 수 있는 스레드의 수를 지정한다.
    // => 자바에서는 세마포어를 지원하지 않는다.
    // => 개발자가 직접 구현해야 한다.
    // => 생활 예)
    //    - 화장실에 들어가는 사람들의 수
    //    - 영화 관람권 예매
    //    - 기차 좌석 예매
    //
    // 뮤텍스; mutex(mutual exclusion, 상호배제)
    // => 한 번에 오직 한 개의 스레드만이 크리티컬 섹션에 접근할 수 있다.
    // => 생활 예) 
    //    - 선풍기 풍량세기 선택
    //    - 라디오 채널 및 TV 채널 선택
    // => semaphore(1)과 같다.
    // => 자바는 synchronized 키워드를 통해 뮤텍스를 사용할 수 있다.
  }
}

용어정리!
 스레드 안전(thread safe)
 - 여러 스레드가 동시에 실행하더라도 아무런 문제가 되지 않는 코드를 말한다.
 - 여러 스레드가 같은 메모리에 접근하더라도 
   읽기만 한다면 아무런 문제가 되지 않는다.

 세마포어(semaphore)
 - critical section에 접근하는 스레드의 수를 제어하는 기법
 - 보통 다음의 형식으로 표시한다.
          semaphore(n)
   n은 개수를 의미한다.
   예) semaphore(3) : 동시에 3개의 스레드가 접근할 수 있다는 의미다.
 - 자바는 2개 이상의 접근을 허용하지 않는다. 오직 한 개만 가능하다.

 뮤텍스(mutual exclusion; MUTEX)
 - critical section에 오직 한 개의 스레드만이 접근하는 것.
 - semaphore(1) 과 같다.
 - 자바에서 synchronized 는 해당 블록(critical section)을 
   뮤텍스로 선언한다.

 

 

synchronized - 메서드를 동기화시킴
package com.eomcs.concurrent.ex5;

public class Exam0310 {
  static class Counter {
    long value;

    public Counter(long value) {
      this.value = value;
    }
  }

  // 비동기 실행
  // - 여러 스레드가 동시에 진입 가능!
  static void print1(String threadName, Counter counter) {
    System.out.printf("[%s] 출력 시작 ----------\n", threadName);

    for (int i= 0; i < counter.value; i++) {
      System.out.printf("%s ==> %d\n", threadName, i);
    }

    System.out.printf("---------- [%s] 출력 끝\n", threadName);
  }

  // 동기 실행
  // - 한 번에 한 스레드만 진입 가능!
  synchronized static void print2(String threadName, Counter counter) {
    System.out.printf("[%s] 출력 시작 ----------\n", threadName);

    for (int i  = 0; i < counter.value; i++) {
      System.out.printf("%s ==> %d\n", threadName, i);
    }

    System.out.printf("---------- [%s] 출력 끝\n", threadName);
  }

  // 동기화 블록
  // - 한 번에 한 스레드만 진입 가능!
  static void print3(String threadName, Counter counter) {
    System.out.printf("[%s] 출력 시작 ----------\n", threadName);

    synchronized (counter) {
      System.out.printf("[%s] $$$$$$$$$$$$$$$$$$$$$$\n", threadName);
      for (int i  = 0; i < 1000; i++) {
        System.out.printf("%s ==> %d\n", threadName, i);
      }
    }

    System.out.printf("---------- [%s] 출력 끝\n", threadName);
  }

  static class Worker extends Thread {
    Counter counter;

    public Worker(String name, Counter counter) {
      super(name);
      this.counter = counter;
    }

    @Override
    public void run() {
      print3(this.getName(), counter);
    }
  }

  public static void main(String[] args) throws Exception {
    Counter counter = new Counter(1000);

    Worker w1 = new Worker("**홍길동", counter);
    Worker w2 = new Worker("임꺽정---->", counter);
    Worker w3 = new Worker("유%관%순", counter);

    w1.start();
    w2.start();
    w3.start();
  }
}

 

 

 

com.eomcs.concurrent.ex6

 

 

스레드 재사용 - 1단계) 스레드 재 사용전 - 매번 스레드 생성
package com.eomcs.concurrent.ex6;

import java.util.Scanner;

public class Exam0110 {

  public static void main(String[] args) {

    class MyThread extends Thread {
      int count;

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

      @Override
      public void run() {
        for (int i = count; i > 0; i--) {
          System.out.println("==> " + i);
        }
      }
    }

    Scanner keyScan = new Scanner(System.in);

    while (true) {
      System.out.print("카운트? ");
      String str = keyScan.nextLine();
      if (str.equals("quit")) {
        break;
      }

      int count = Integer.parseInt(str);

      MyThread t = new MyThread();
      t.setCount(count);
      t.start();
      // 카운트 할 때 마다 매번 스레드를 생성한다.
      // => 실행 완료된 스레드는 가비지가 된다.
      // => 가비지 컬렉터가 가비지가 된 스레드를 수집하여 해제시키기 전까지는
      //    그 스레드를 위해 할당된 메모리를 사용할 수 없다.
      // => 즉 스레드를 매번 생성하는 방식은 
      //    과다한 가비지를 생성하기 때문에 메모리 낭비를 일으킨다.
    }

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

 

 

스레드 재사용 - 1단계) 스레드를 재 사용하려 시도
package com.eomcs.concurrent.ex6;

import java.util.Scanner;

public class Exam0111 {

  public static void main(String[] args) {

    class MyThread extends Thread {
      int count;

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

      @Override
      public void run() {
        for (int i = count; i > 0; i--) {
          System.out.println("==> " + i);
        }
      }
    }

    // 카운트를 수행할 스레드를 미리 만든다.
    MyThread t = new MyThread();

    Scanner keyScan = new Scanner(System.in);

    while (true) {
      System.out.print("카운트? ");
      String str = keyScan.nextLine();
      if (str.equals("quit")) {
        break;
      }

      // 사용자가 카운트 값을 입력하면, 
      int count = Integer.parseInt(str);

      // 기존에 생성한 스레드에 카운트 값을 설정한 후 실행을 시작시킨다.
      t.setCount(count);
      t.start();
      // 문제점?
      // - 한 번 실행이 완료된 Dead 상태의 스레드는 다시 시작시킬 수 없다.
      // - 다시 시작하려고 하면 예외가 발생한다.
      // - 즉 run() 메서드 호출이 끝나, Dead 상태가 된 스레드는 다시 start() 할 수 없다!
    }

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

 

 

스레드 재사용 - 2단계) sleep()/timeout 을 활용한 스레드 재사용
package com.eomcs.concurrent.ex6;

import java.util.Scanner;

public class Exam0120 {

  public static void main(String[] args) {

    class MyThread extends Thread {
      int count;

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

      @Override
      public void run() {
        System.out.println("스레드 시작했음!");
        try {
          // 스레드를 재사용하려면 다음과 같이 run() 메서드가 종료되지 않게 해야 한다.
          while (true) {
            // 사용자가 카운트 값을 입력할 시간을 주기 위해
            // 10초 정도 스레드를 멈춘다.
            Thread.sleep(10000);

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

    MyThread t = new MyThread();

    // 미리 스레드를 시작시켜 놓는다.
    t.start();

    Scanner keyScan = new Scanner(System.in);

    while (true) {
      System.out.print("카운트? ");
      String str = keyScan.nextLine();

      if (str.equals("quit")) {
        break;
      }

      int count = Integer.parseInt(str);
      t.setCount(count); // 스레드의 카운트 값을 변경한다.
      // sleep()을 이용한 스레드 재활용 방식은
      // 일정 시간이 지난 후 스레드가 작업하게 만드는 방식이다.
      // 스레드가 잠든 사이에 작업할 내용을 설정해두면,
      // 스레드가 깨어났을 때 변경 사항에 따라 작업을 수행한다.
      // 이 방식으로 한 개의 스레드를 재활용하여 작업을 처리할 수 있지만, 
      // 문제는:
      // => 스레드가 깨어날 때까지 작업이 바로 실행되지 않는다.
      // => 작업을 시키고 싶지 않아도 깨어나면 무조건 작업할 것이다.
      //
    }

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

 

 

스레드 재사용 - 3단계) sleep()/timeout 을 활용한 스레드 재사용 II
package com.eomcs.concurrent.ex6;

import java.util.Scanner;

public class Exam0130 {

  public static void main(String[] args) {

    class MyThread extends Thread {
      boolean enable;
      int count;

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

        // 카운트 값을 설정할 때 작업을 활성화시킨다.
        this.enable = true;
      }

      @Override
      public void run() {
        System.out.println("스레드 시작했음!");
        try {
          while (true) {
            System.out.println("스레드를 10초 동안 잠들게 한다!");
            Thread.sleep(10000);

            // 무조건 작업하지 말고,
            // enable이 true일 때만 작업하게 하자!
            if (!enable) {
              continue;
            }

            System.out.println("카운트 시작!");
            for (int i = count; i > 0; i--) {
              System.out.println("==> " + i);
            }
            // 스레드에게 맡겨진 작업이 끝나면 비활성 상태로 설정한다.
            enable = false;
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }

    MyThread t = new MyThread();
    t.start();

    Scanner keyScan = new Scanner(System.in);

    while (true) {
      System.out.print("카운트? ");
      String str = keyScan.nextLine();
      if (str.equals("quit")) {
        break;
      }

      int count = Integer.parseInt(str);
      t.setCount(count);
      // 이 버전은 다음과 같이 동작한다.
      // => 스레드가 작업을 완료하면 10초 동안 잠든다.
      // => 10초 후에 깨어났을 때 카운트 값이 설정되어 있지 않으면 다시 잠든다.
      // => 카운트 값이 설정되면서 enable이 활성화 상태라면 작업을 실행한다.
      // => 작업이 끝나면 enable을 비활성으로 만든 후 잠든다.
      // 이전 버전에서는 깨어난 후 무조건 작업을 수행했지만,
      // 이 버전은 카운트 값이 설정될 때만 작업하도록 개선하였다.
      // 그러나 근본적인 문제는 해결되지 않았다.
      // => 작업을 완료한 후 무조건 10초를 기다린다.
      // => 스레드가 깨어난 후 작업이 없더라도 10초를 기다린다.
      //
    }

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

 

 

스레드 재사용 - 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("스레드 대기중...");

            // wait()
            // - 해당 객체에서 notify()를 통해 알림이 올 때까지 스레드의 실행을 멈추게 한다.
            // - 이 메서드는 동기화 블록
            //   (한 번에 한 스레드만이 진입하도록 설정된 블록)에서만 호출할 수 있다.
            // 
            // 문법 주의!
            // => wait()/notify() 는 반드시 동기화 영역 안에서 호출해야 한다.
            // 
            // 동기화 영역?
            // => synchronized로 선언된 메서드
            //    예) synchronized void m() {}
            // => synchronized로 묶인 블록
            //    예) synchronized(접근대상) {...}
            //
            synchronized (valueBox) {
              valueBox.wait();
              // valueBox 객체에 대해 사용하라는 신호가 올 때까지 이 스레드에게 기다리라는 명령이다.
              // 즉 wait()를 호출한 스레드는 Not Runnable 상태에 진입한다.
              // => 실행을 멈추고 CPU 사용권을 받지 않는 상태가 된다.
              // 
              // 이 스레드를 잠에서 깨우는 방법?
              // => 다른 스레드가 valueBox에 대해 notify()를 호출하면,
              //    wait() 로 신호를 기다리고 있는 스레드가 잠에서 깨어나 
              //    실행을 시작한다.
              //
              // 기다림을 끝내는 방법?
              // => 즉 notify()를 통해 기다림이 끝났다는 것을 알림 받아야 한다.
              //
            }
            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();
  }
}

 

 

멀티 스레드 재사용 - Pooling 기법을 이용하여 생성된 객체를 재활용하기
package com.eomcs.concurrent.ex6;

import java.util.ArrayList;
import java.util.Scanner;

public class Exam0210 {

  static class MyThread extends Thread {
    ThreadPool pool;
    int count;

    public MyThread(String name, ThreadPool pool) {
      super(name);
      this.pool = pool;
    }

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

    @Override
    public void run() {
      synchronized (this) {
        try {
          while (true) {
            // 작업하라는 알림이 올 때까지 기다린다.
            wait();

            // 알림이 오면 작업을 실행한다.
            for (int i = count; i > 0; i--) {
              System.out.printf("[%s] %d\n", getName(), i);
              Thread.sleep(2000);
            }

            // 작업이 끝났으면 스레드풀로 돌아간다.
            pool.add(this);
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }
  }

  // MyThreadPool 과 MyThread 상호간에 참조를 피하기 위해
  // 인터페이스를 준비했다.
  interface ThreadPool {
    Thread get();
    void add(Thread obj);
  }

  static class MyThreadPool implements ThreadPool {
    ArrayList<MyThread> list = new ArrayList<>();

    public MyThreadPool() {
      // 사용할 스레드 객체를 미리 생성한다.
      // - 나중에 MyThread가 Pool로 다시 리턴될 수 있도록
      //   스레드 객체를 생성할 때 Pool의 주소를 알려준다.
      MyThread t1 = new MyThread("1번 스레드=>", this);
      MyThread t2 = new MyThread("2번 스레드***>", this);
      MyThread t3 = new MyThread("3번 스레드-->", this);

      // 생성된 스레드를 컬렉션에 보관한다.
      list.add(t1);
      list.add(t2);
      list.add(t3);

      // 일단 무조건 스레드를 미리 실행해 놓는다.
      t1.start();
      t2.start();
      t3.start();
    }

    // 스레드 풀에서 한 개의 스레드를 꺼낸다.
    @Override
    public MyThread get() {
      if (list.size() > 0) { // 컬렉션에 남아 있는 스레드가 있다면,
        return list.remove(0);
      }
      return null; // 없으면, null을 리턴한다.
      // 현재 이 예제에서는 오직 3개의 스레드만 쓰도록 하였다.
    }

    // 스레드를 다 쓴 후에는 다시 스레드 풀에 돌려준다.
    @Override
    public void add(Thread t) {
      list.add((MyThread) t);
    }
  }

  public static void main(String[] args) {

    // 스레드풀 준비!
    MyThreadPool threadPool = new MyThreadPool();

    Scanner keyScan = new Scanner(System.in);

    while (true) {
      System.out.print("카운트? ");
      String str = keyScan.nextLine();
      if (str.equals("quit")) {
        break;
      }

      int count = Integer.parseInt(str);

      // 스레드풀에서 스레드를 한 개 꺼낸다.
      MyThread t = threadPool.get();
      if (t == null) {
        System.out.println("남는 스레드가 없습니다!");
        continue;
      }

      // 스레드의 카운트를 설정한다. 그러면 카운트를 시작할 것이다.
      t.setCount(count);
    }

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

 

 

 

com.eomcs.concurrent.ex7

 

 

Executors 태스크 프레임워크 - 스레드풀 만들고 사용하기
package com.eomcs.concurrent.ex7;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Exam0110 {

  public static void main(String[] args) {
    // 스레드풀을 생성한다.
    // - 최대 3개의 스레드를 생성한다.
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    // 스레드풀에 작업 수행을 요청한다.
    // - 작업은 Runnable 구현체로 작성하여 넘겨준다.
    // - 스레드풀은 스레드를 생성하여 작업을 수행시킨다.
    executorService.execute(
        () -> System.out.printf("%s - Hello!\n", Thread.currentThread().getName()));

    System.out.println("main() 종료!");
    // JVM은 main 스레드를 종료하더라도 나머지 스레드가 종료할 때까지 기다린다.
    // 스레드풀에서 생성한 스레드가 요청한 작업을 마치더라도
    // 다음 작업을 수행하기 위해 계속 실행된 채로 대기하고 있기 때문에
    // JVM은 종료하지 않는다.
  }
}

 

 

Executors 태스크 프레임워크 - 스레드풀 종료하기
package com.eomcs.concurrent.ex7;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Exam0120 {

  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);
    executorService.execute( 
        () -> {
          System.out.printf("%s - Hello!\n", Thread.currentThread().getName());

          try { 
            Thread.sleep(10000); // 현재 스레드를 10초 동안 멈춘다.
          } catch (Exception e) {}

          System.out.printf("%s 스레드 종료!\n", Thread.currentThread().getName());
        });

    // 스레드풀에 있는 모든 스레드들이 요청한 작업을 끝내면
    // 종료하도록 지시한다.
    // 모든 스레드가 종료될 때까지 기다리지 않고 바로 리턴한다.
    // shutdown() 호출 이후에는 새 작업 요청은 받지 않는다.
    // 즉 execute()를 호출하면 예외가 발생한다.
    executorService.shutdown();

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

 

 

Executors 태스크 프레임워크 - 스레드풀 만들기 : 고정크기 스레드풀
package com.eomcs.concurrent.ex7;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Exam0210 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("[%s] - 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("[%s] - 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("[%s] 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    // 일단 스레드풀의 크기(3 개)만큼 작업 수행을 요청한다.
    // - 작업은 큐에 등록된 순서대로 보관된다.
    // - 스레드풀은 큐에서 작업을 꺼내 스레드에게 일을 시킨다.
    //
    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(3000));
    executorService.execute(new MyRunnable(9000));

    // 스레드풀의 크기를 초과해서 작업 수행을 요청한다면?
    // - 놀고 있는 스레드가 없을 경우,
    //   다른 스레드의 작업이 끝날 때까지 작업큐에 대기하고 있는다.
    // - 작업을 끝낸 스레드가 생기면 큐에서 작업을 꺼내 실행한다.
    //
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(4000));

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

 

 

Executors 태스크 프레임워크 - 스레드풀 만들기 : 가변크기 스레드풀
package com.eomcs.concurrent.ex7;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Exam0220 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) throws Exception {

    // 스레드의 수를 고정하지 않고 필요할 때마다 스레드를 생성하는 스레드풀이다.
    // 물론 작업을 끝낸 스레드는 다시 사용할 수 있도록 pool에 보관한다.
    ExecutorService executorService = Executors.newCachedThreadPool();

    // 놀고 있는 스레드가 없으면 새 스레드를 생성한다.
    //
    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(9000));
    executorService.execute(new MyRunnable(1000));

    // 작업을 끝낸 스레드가 생길 때까지 일부러 기다린다.
    //
    Thread.sleep(3000);

    // 그러면 새 스레드를 생성하지 않고
    // 작업을 끝낸 스레드가 요청한 작업을 처리한다.
    //
    executorService.execute(new MyRunnable(4000));

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

 

 

Executors 태스크 프레임워크 - 스레드풀 만들기 : 한 개의 스레드를 갖는 스레드풀
package com.eomcs.concurrent.ex7;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Exam0230 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) throws Exception {

    // 한 개의 스레드만 갖는 스레드풀이다.
    ExecutorService executorService = Executors.newSingleThreadExecutor();

    // 스레드가 한 개이기 때문에 순차적으로 실행한다.
    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(3000));
    executorService.execute(new MyRunnable(9000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(4000));

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

 

 

Executors 태스크 프레임워크 - 작업 실행 : execute()
package com.eomcs.concurrent.ex7;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Exam0310 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    // 스레드풀에 수행할 작업을 등록한다.
    // 스레드풀은 execute()를 호출한 순서대로 작업큐에 작업을 보관한다.
    // 그리고 놀고 있는 스레드가 있다면, 작업큐에서 작업을 꺼내 수행시킨다.
    // 놀고 있는 스레드가 없으면, 새로 스레드를 생성한다.
    // 스레드가 최대 개수라면 기존 스레드가 작업을 끝낼 때까지 기다린다.
    // => 수행한 작업의 종료 여부를 확인할 수 없다.
    executorService.execute(new MyRunnable(6000));

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

 

 

Executors 태스크 프레임워크 - 작업 실행 : submit()
package com.eomcs.concurrent.ex7;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Exam0320 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    // execute()와 같다.
    // => 단 작업의 종료 상태를 확인할 수 있는 Future 객체를 리턴한다.
    //
    Future<?> future1 = executorService.submit(new MyRunnable(2000));
    Future<?> future2 = executorService.submit(new MyRunnable(4000));

    // Future.get()
    // => 요청한 작업이 완료될 때 까지 기다린다.(pending)
    // => 요청한 작업이 완료되면 null을 리턴한다.
    //
    future2.get();
    System.out.println("두 번째 작업이 끝났음");

    future1.get();
    System.out.println("첫 번째 작업이 끝났음");

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

 

 

Executors 태스크 프레임워크 - 스레드풀 종료 : shutdown()
package com.eomcs.concurrent.ex7;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Exam0410 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("[%d] %s 스레드 실행 중...\n",
            this.millisec, Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("[%d] %s 스레드 종료!\n",
            this.millisec, Thread.currentThread().getName());

      } catch (Exception e) {
        System.out.printf("[%d] %s 스레드 실행 중 오류 발생!\n", 
            this.millisec, Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(4000));
    executorService.execute(new MyRunnable(4000));
    executorService.execute(new MyRunnable(4000));
    executorService.execute(new MyRunnable(4000));

    // => 더이상 작업 요청을 받지 말고
    //    이전에 요청한 작업(대기하고 있는 작업)들이 완료되면
    //    스레드를 종료하도록 예약한다.
    // => 작업 중인 스레드가 Not Runnable 상태가 아니라면 
    //    작업이 끝날 때까지 기다린다.
    executorService.shutdown();

    // 작업 요청을 거절한다.
    // => 예외 발생!
    executorService.execute(new MyRunnable(4000));

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

 

 

Executors 태스크 프레임워크 - 스레드풀 종료 : shutdownNow()
package com.eomcs.concurrent.ex7;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Exam0420 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("[%d] %s 스레드 실행 중...\n",
            this.millisec, Thread.currentThread().getName());

        Thread.sleep(millisec);
        //        for (long i = 0; i < 10000_0000; i++) {
        //          double r = Math.tan(3456.77889) / Math.random();
        //
        //        }

        System.out.printf("[%d] %s 스레드 종료!\n",
            this.millisec, Thread.currentThread().getName());

      } catch (Exception e) {
        System.out.printf("[%d] %s 스레드 실행 중 오류 발생!\n", 
            this.millisec, Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    executorService.execute(new MyRunnable(1000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(3000));
    executorService.execute(new MyRunnable(7000));
    executorService.execute(new MyRunnable(8000));
    executorService.execute(new MyRunnable(9000));

    // 가능한 현재 수행 중인 작업들을 모두 멈추도록 지시한다.
    // => shutdown()과 차이점:
    //    - 만약 작업을 실행 중인 스레드가 잠시 Not Runnable 상태에 놓인다면,
    //      바로 스레드를 멈출 기회라고 보고 스레드 종료를 시도할 것이다.
    //    - 이런 이유로 shutdown() 보다는 스레드 종료를 좀 더 강제하는 효과가 있다.
    // => 작업 중인 스레드가 Not Runnable 상태가 아니라면 작업이 끝날 때까지 기다린다.
    // => 다만 작업을 마치고 대기 중인 스레드는 즉시 취소한다.
    // => 그리고 취소한 대기 작업 목록을 리턴해준다.
    // 
    List<Runnable> tasks = executorService.shutdownNow();
    System.out.println("실행 취소된 작업들:");
    System.out.println("--------------------------------");
    for (Runnable task : tasks) {
      System.out.println(((MyRunnable) task).millisec);
    }
    System.out.println("--------------------------------");

    // 물론 새 작업 요청도 거절한다.
    // => 예외 발생!
    executorService.execute(new MyRunnable(4000));

    // shutdown() vs shutdownNow();
    // - shutdown()
    //   진행 중인 작업을 완료하고 대기 중인 작업도 완료한 다음 종료.
    // - shutdownNow()
    //   진행 중인 작업을 즉시 종료하고, 대기 중인 작업 목록은 리턴한다.
    System.out.println("main() 종료!");
  }
}

 

 

Executors 태스크 프레임워크 - 스레드풀 종료 대기 : awaitTermination()
package com.eomcs.concurrent.ex7;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Exam0510 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(4000));
    executorService.execute(new MyRunnable(5000));

    executorService.shutdown();

    // 스레드풀의 모든 스레드가 종료되면 즉시 true를 리턴한다.
    // 만약 지정된 시간(예: 10초)이 경과할 때까지 종료되지 않았다면 false를 리턴한다.
    //
    if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
      System.out.println("아직 종료 안된 작업이 있다.");
    } else {
      System.out.println("모든 작업을 종료하였다.");
    }

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

 

 

Executors 태스크 프레임워크 - 스레드풀 종료 대기 : awaitTermination() 활용
package com.eomcs.concurrent.ex7;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Exam0520 {

  static class MyRunnable implements Runnable {
    int millisec;

    public MyRunnable(int millisec) {
      this.millisec = millisec;
    }

    @Override
    public void run() {
      try {
        System.out.printf("%s 스레드 실행 중...\n",
            Thread.currentThread().getName());

        Thread.sleep(millisec);

        System.out.printf("%s 스레드 종료!\n",
            Thread.currentThread().getName());
      } catch (Exception e) {
        System.out.printf("%s 스레드 실행 중 오류 발생!\n", Thread.currentThread().getName());
      }
    }
  }
  public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(3);

    executorService.execute(new MyRunnable(6000));
    executorService.execute(new MyRunnable(2000));
    executorService.execute(new MyRunnable(4000));
    executorService.execute(new MyRunnable(20000));

    executorService.shutdown();

    // 스레드풀의 모든 스레드가 종료될 때까지 기다린다.
    if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
      System.out.println("아직 종료 안된 작업이 있다.");
      System.out.println("남아 있는 작업의 강제 종료를 시도하겠다.");

      // => 만약 10초가 경과될 때까지 종료되지 않으면,
      //    수행 중인 작업을 강제 종료하라고 지시한다.

      // 강제 종료?
      // => 일단 대기 중인 작업은 모두 취소한다.
      // => 실행 중인 스레드 중 Not Runnable 상태에 있는 스레드는 강제 종료한다.
      // => 그 외 running 상태의 스레드는 강제 종료할 수 없다.
      //    예) 입.출력 대기 상태는 running 상태이다. Not Runnable 상태가 아니다.
      executorService.shutdownNow();

      // 그리고 다시 작업이 종료될 때까지 기다린다.
      if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
        System.out.println("스레드풀의 강제 종료를 완료하지 못했다.");
      } else {
        System.out.println("모든 작업을 강제 종료했다.");
      }

    }

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