- 자바 프로그래밍
- 스레드 프로그래밍(com.eomcs.concurrent.ex7)
- 스레드풀 사용법
Executors 태스크 프레임워크 - 스레드풀 만들고 사용하기
Exam0110.java
// 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은 종료하지 않는다.
}
}
ㄴ 스레드 풀을 실행해놓고 메인 스레드가 종료됨
=> 스레드 풀에서 생성한 스레드가 요청한 작업을 마치더라도 다음 작업을 수행하기 위해 계속 실행된 채로 대기하고 있으므로 JVM 은 종료하지 않음
=>
ㄴ 익명 클래스 람다로 바꾸기
ㄴ 메소드 바깥 껍데기(new Runnable() { ... })를 제거
ㄴ 메서드 이름(run())을 제거
ㄴ 파라미터와 메서드 바디 사이에 -> 추가
ㄴ 파라미터 타입 삭제(위 코드에는 파라미터 없으므로 그대로)
ㄴ 파라미터가 없을 경우 괄호 생략 불가
ㄴ 문장이 하나 일 경우 {} 중괄호 제거 가능
=>
ㄴ 계속 실행된 채로 대기하고 있으므로 강제종료 필요
Executors 태스크 프레임워크 - 스레드풀 종료하기
Exam0120.java
// 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() 종료!");
}
}
ㄴ 스레드풀의 shutdown 은 지금 당장 종료하라는 것이 아님 => 종료 요청을 접수할 뿐
=> 스레드풀에 있는 모든 스레드들이 요청한 작업을 끝내면 종료하도록 함
ㄴ shutdown() 호출 이후에는 새 작업 요청을 받지 않음
ㄴ 유지하고 있는, 실행중인 스레드가 없다면 스레드 풀을 가동하지 말아라
=>
=>
ㄴ 10초 후 종료됨
Executors 태스크 프레임워크 - 스레드풀 만들기 : 고정크기 스레드풀
Exam0210.java
// 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.newFixedThreadPool(고정크기); => 스레드풀의 크기를 고정시킴
ㄴ 스레드풀의 크기를 초과해서 작업을 수행하면 다른 스레드의 작업이 끝날 떄까지 작업큐에 대기하고 있음
ㄴ 놀고 있는 스레드가 생기는 순간 해당 스레드가 작업을 진행함
=>
Executors 태스크 프레임워크 - 스레드풀 만들기 : 가변크기 스레드풀
Exam0220.java
// 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() 종료!");
}
}
ㄴ 스레드풀은 execute()를 호출한 순서대로 작업큐에 작업을 보관함
ㄴ Executors.newCachedThreadPool(); => 스레드의 수를 고정하지 않고 필요할 때마다 스레드를 생성하는 스레드풀
=> 작업을 끝낸 스레드는 다시 사용할 수 있도록 pool에 보관
=>
Executors 태스크 프레임워크 - 스레드풀 만들기 : 한 개의 스레드를 갖는 스레드풀
Exam0230.java
// 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.newSingleThreadExecutor(); => 한 개의 스레드만 갖는 스레드풀임을 알 수 있음
=>
=>
=>
=>
=>
=>
ㄴ 모든 작업을 하나의 스레드만을 이용해서 함
Executors 태스크 프레임워크 - 작업 실행 : execute()
Exam0310.java
// 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() 종료!");
}
}
ㄴ 수행한 작업의 종료 여부를 확인할 수 없음
=>
=>
ㄴ 6초 후 깨어남
Executors 태스크 프레임워크 - 작업 실행 : submit()
Exam0320.java
// 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() 종료!");
}
}
ㄴ Future.get() => 요청한 작업이 완료될 때 까지 기다리고, 요청한 작업이 완료되면 null 리턴
=>
=>
=>
=>
Executors 태스크 프레임워크 - 스레드풀 종료 : shutdown()
Exam0410.java
// 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() 종료!");
}
}
ㄴ 위 코드에서는 3개의 스레드가 작업을 함
ㄴ shutdown 을 할 경우 더이상 작업 요청을 받지 않고 이전에 요청한 작업(대기하고 있는 작업)들이 완료되면 스레드를 종료하도록 예약함
ㄴ shutdown 한 다음에는 스레드에 작업을 맡기면 작업 요청을 거절함 => 예외발생
=>
Executors 태스크 프레임워크 - 스레드풀 종료 : shutdownNow()
Exam0420.java
// 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 < 1_0000_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()과 차이점:
// - 만약 Running 상태의 스레드가 Not Runnable 상태(sleep()/wait())에 놓인다면,
// 바로 스레드를 멈출 기회라고 보고 스레드를 강제 종료할 것이다.
// - 즉 실행 중인 작업만 완료시키고, 대기 중인 작업은 취소시키는 효과가 있다.
// => Running 상태에 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() 종료!");
}
}
ㄴ shutdownNow() 메서드는 스레드 풀에 대기 중인 작업을 무시하고, 현재 실행 중인 작업을 중단시키며 스레드 풀을 종료함
=>
Executors 태스크 프레임워크 - 스레드풀 종료 대기 : awaitTermination()
Exam0510.java
// 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() 종료!");
}
}
ㄴ 10 => 값
ㄴ TimeUnit.SECONDS => 초
ㄴ 10초 이내라도 종료되는 순간 즉시 리턴
ㄴ 10초 지나도 종료가 안 되면 false 를 리턴함
=>
ㄴ 정상적으로 종료되었을 경우
=>
ㄴ 한 개의 작업을 15초로 설정할 경우 10초 지나도 종료가 안 된 것이 있음을 알 수 있음
Executors 태스크 프레임워크 - 스레드풀 종료 대기 : awaitTermination() 활용
Exam0520.java
// 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() 종료!");
}
}
ㄴ 만약 10초가 경과될 때까지 종료되지 않으면, 대기 중인 작업을 강제 종료하라고 지시함
ㄴ 강제 종료?
=> 일단 대기 중인 작업은 모두 취소함
=> 실행 중인 스레드 중 Not Runnable 상태에 있는 스레드는 강제 종료함
=> 그 외 running 상태의 스레드는 강제 종료할 수 없음
예) 입.출력 대기 상태는 running 상태이다. Not Runnable 상태가 아님
=>
'네이버클라우드 > JAVA 웹 프로그래밍' 카테고리의 다른 글
JAVA 39일차 (2023-07-17) 자바 프로그래밍_DBMS (0) | 2023.07.17 |
---|---|
JAVA 39일차 (2023-07-17) 자바 프로그래밍_45. 스레드 풀_개인프로젝트 - 마트 관리 시스템 (0) | 2023.07.17 |
JAVA 38일차 (2023-07-14) 자바 프로그래밍_스레드 재사용하기 : 스레드풀(thread pool) 구현_개인프로젝트 - 마트 관리 시스템 (0) | 2023.07.16 |
JAVA 38일차 (2023-07-14) 자바 프로그래밍_스레드, 임계영역(Critical Region, Critical Section) (0) | 2023.07.14 |
JAVA 37일차 (2023-07-13) 자바 프로그래밍_스레드와 멀티태스킹 (0) | 2023.07.13 |