개발자입니다
[Java] 예제 소스 정리 - 스레드 본문
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() 종료!");
}
}
'네이버클라우드 AIaaS 개발자 양성과정 1기 > Java' 카테고리의 다른 글
[Java] 예제 소스 정리 - 디자인 패턴 13가지 (0) | 2023.02.19 |
---|---|
[Java] 예제 소스 정리 - 네트워킹(HTTP, 연결 방식) (0) | 2023.02.03 |
[비트캠프] 63일차(13주차5일) - Java(네트워킹 연결방식, 스레드), myapp-33~35 (0) | 2023.02.03 |
[Java] 예제 소스 정리 - 네트워킹(Client/Server, 연결 방식) (0) | 2023.02.02 |
[비트캠프] 62일차(13주차4일) - Java(Client, Server 아키텍처), myapp-32-1~2 (0) | 2023.02.02 |