- 자바 프로그래밍
- 스레드 프로그래밍(com.eomcs.concurrent.ex5 ~ ex7)
- 멀티태스킹의 메커니즘 이해
- 임계영역(Critical Region, Critical Section): 세마포어(Semaphore)와 뮤텍스(Mutex)
멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 1
// 멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 1
package com.eomcs.concurrent.ex5;
public class Exam0110 {
static class MyList {
int[] values = new int[100];
int size;
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);
}
}
}
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();
}
}
=>
ㄴ 스레드가 동시에 이 메서드에 진입하면 배열의 값을 덮어쓰는 문제가 발생
ㄴ 이렇게 여러 스레드가 동시에 접근했을 때 문제가 발생하는 코드 부분을 "Critical Section" 또는 "Critical Region" 이라 부름
멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 1의 문제 해결
// 멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 1의 문제 해결
package com.eomcs.concurrent.ex5;
public class Exam0120 {
static class MyList {
int[] values = new int[100];
int size;
// 다음 메서드를 동기화 처리해 보자.
// => 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();
}
}
ㄴ Critical Section에 오직 한 개의 스레드만 접근하게 하면 비동기로 인한 문제가 발생하지 않음
=> 동기화로 처리함
=> 동기화?
ㄴ 여러 스레드가 동시에 실행하는 것이 아니고 여러 스레드가 순차적으로 접근하는 것
ㄴ 순차적으로 실행한다는 것은 동시 실행의 이점을 버리는 것이기 때문에 스레드를 사용하기 전의 상태와 같음
=> 기존의 실행 방식 처럼 실행 시간이 많이 걸림
ㄴ synchronized 를 이용해 동기화 처리
ㄴ 크리티컬 섹션 구간에 이 키워드를 붙이면 오직 한 번에 한 개의 스레드만이 접근할 수 있
=> 먼저 접근한 스레드가 나가야만 다음 스레드가 진입할 수 있음
=> 크리티컬 섹션을 뮤텍스 구간으로 설정
멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 2
// 멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 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) {
// 크리티컬 섹션
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();
}
}
=>
ㄴ 여러 스레드가 같은 메모리(balance 필드)의 값을 동시에 변경할 때 문제가 발생할 수 있는 코드를 "크리티컬 섹션(임계영역; critical section)" 또는 "크리티컬 리전(critical region)" 이라 부름
ㄴ 강남, 서초 등 여러 개의 스레드가 같은 객체에 대해 메서드를 호출하여 동시에 값을 변경하려 할 때 서로 그 메모리의 값을 덮어쓰는 문제가 발생함
=> 여러 스레드가 동시에 실행할 때 문제를 일으키는 코드를 "임계 구역(Critical Section; Critical Region)"이라 부름
ㄴ 위 코드에서는 여러 스레드가 동시에 호출하고, 같은 인스턴스의 변수 값을 변경하는 메서드인 "withdraw()"가 critical section임
임계 구역(critical section)
- 여러 스레드가 동시에 실행할 때 문제가 발생하는 코드 블록을 말한다.
- critical region 이라고도 부른다.
- 같은 메모리에 여러 스레드가 동시에 접근하여 값을 변경하려 할 때 문제가 발생하는 것이다.
즉 다른 스레드가 사용하는 변수의 값을 임의로 변경하면 그 스레드는 원래의 의도대로 동작하지 않을 것이다.
- "스레드 안전(thread safe)하지 않다"라고 말한다.
멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 2의 문제점 해결
// 멀티 스레딩(비동기 프로그래밍)의 문제점 - 사례 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 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();
// 비동기 문제 해결책?
// => 한 번에 한 스레드 만이 크리티컬 섹션을 실행하도록 접근을 제한하면 된다.
}
}
=>
ㄴ 한 번에 한 스레드 만이 호출하도록 접근을 제한하고 싶다면 메서드 전체를 동기화 블록으로 선언하라!
=> 메서드 앞에 synchronized를 붙이는 방법을 이용
ㄴ 한 번에 한 스레드 만이 크리티컬 섹션을 실행하도록 접근을 제한하혀 비동기 문제 해결 가능
크리티컬 섹션에 동시에 접근하지 못하게 하는 기법
=> "뮤텍스(mutex)" 또는 "세마포어(1)(semaphore)"라 부른다.
자바에서 뮤텍스를 구현하는 방법,
=> 크리티컬 섹션에 해당하는 메서드나 코드 블록에 sychronized 키워드를 붙여
한 번에 한 스레드만 진입할 수 있도록 lock을 건다.
참고!
=> 여러 스레드가 동시에 실행해도 문제가 없는 코드 블록을
"스레드 안전(thread safe)"라 부른다.
비동기 문제 해결책?
=> 한 번에 한 스레드 만이 크리티컬 섹션을 실행하도록 접근을 제한하면 된다.
주의!
=> 동시에 여러 스레드가 같은 메모리에 접근하더라도 값을 변경하는 것이 아니라 단순히 값을 조회하는 경우에는
멀티 스레드 문제가 발생하지 않는다.
synchronized - 메서드를 동기화시킴
// 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();
}
}
=>
...
...
...
...
----------------------------------------------------------------------
스레드 재사용 - 1단계) 스레드 재 사용전 - 매번 스레드 생성
// 스레드 재사용 - 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();
// 카운트 할 때 마다 매번 스레드를 생성한다.
// => 실행 완료된 스레드는 가비지가 된다.
// => 즉 스레드를 매번 생성하는 방식은 과다한 가비지를 생성하기 때문에 메모리 낭비를 일으킨다.
// => 스레드는 실제 OS가 생성한다.
// 즉 스레드를 생성하는데 시간이 소요된다는 것이다.
// 스레드를 자주 생성하면 결국 스레드 생성하는데 시간을 많이 소요하게 된다.
}
System.out.println("main 스레드 종료!");
keyScan.close();
}
}
=>
ㄴ 카운트 할 때 마다 매번 스레드를 생성하면 실행 완료된 스레드는 가비지가 되어 메모리 낭비가 발생함
스레드 재사용 - 1단계) 스레드를 재 사용하려 시도
// 스레드 재사용 - 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 상태의 스레드는 다시 시작시킬 수 없다.
}
System.out.println("main 스레드 종료!");
keyScan.close();
}
}
=>
ㄴ 위 코드에서 카운트를 수행할 스레드는 두 번째 호출 될 때 기존 스레드를 호출함
ㄴ 한 번 실행이 완료된 Dead 상태의 스레드는 다시 시작시킬 수 없음
스레드 재사용 - 2단계) sleep()/timeout 을 활용한 스레드 재사용
// 스레드 재사용 - 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); // 스레드의 카운트 값을 변경한다.
}
System.out.println("main 스레드 종료!");
keyScan.close();
}
}
ㄴ sleep()을 이용한 스레드 재활용 방식은 일정 시간이 지난 후 스레드가 작업하게 만드는 방식임
ㄴ 스레드가 잠든 사이에 작업할 내용을 설정해두면, 스레드가 깨어났을 때 변경 사항에 따라 작업을 수행함
=>이 방식으로 한 개의 스레드를 재활용하여 작업을 처리할 수 있지만,
문제는:
=> 스레드가 깨어날 때까지 작업이 바로 실행되지 않음
=> 작업을 시키고 싶지 않아도 깨어나면 무조건 작업할 것임
=>
ㄴ 값을 다시 세팅하지 않아도 10초마다 똑같은 값이 반복돼서 출력됨
스레드 재사용 - 3단계) sleep()/timeout 을 활용한 스레드 재사용 II
// 스레드 재사용 - 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);
}
System.out.println("main 스레드 종료!");
keyScan.close();
}
}
ㄴ 스레드가 작업을 완료하면 10초 동안 잠듦
ㄴ 10초 후에 깨어났을 때 카운트 값이 설정되어 있지 않으면 다시 잠듦
ㄴ 카운트 값이 설정되면서 enable이 활성화 상태라면 작업을 실행함
ㄴ 작업이 끝나면 enable을 비활성으로 만든 후 잠듦
=> 이전 버전에서는 깨어난 후 무조건 작업을 수행했지만,
=> 이 버전은 카운트 값이 설정될 때만 작업하도록 개선한 것
그러나 근본적인 문제는 해결되지 않음
=> 작업을 완료한 후 무조건 10초를 기다림
=> 스레드가 깨어난 후 작업이 없더라도 10초를 기다림
=>
ㄴ 실행 한 후 값을 주지 않으면 스레드를 10초 동안 잠들게 하고 값을 입력하면 카운트 값 출력
ㄴ 단점은 값을 줘도 10초를 기다려야함
스레드 재사용 - 4단계) wait()/notify() 사용
// 스레드 재사용 - 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()를 통해 알림이 올 때까지 스레드의 실행을 멈추게 한다.
// - 이 메서드는 동기화 블록
// (한 번에 한 스레드만이 진입하도록 설정된 블록)에서만 호출할 수 있다.
synchronized (valueBox) {
valueBox.wait();
// valueBox 객체에 대해 사용하라는 신호가 올 때까지 이 스레드에게 기다리라는 명령이다.
// 즉 wait()를 호출한 스레드는 Not Runnable 상태에 진입한다.
// => 실행을 멈추고 CPU 사용권을 받지 않는 상태가 된다.
}
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 스레드는 이 객체의 사용을 간절히 기다리는 다른 스레드에게
// 즉시 사용하라고 신호를 보낸다.
}
System.out.println("main 스레드 종료!");
keyScan.close();
}
}
ㄴ 문법 주의!
=> wait()/notify() 는 반드시 동기화 영역 안에서 호출해야 함
ㄴ 동기화 영역?
=> synchronized로 선언된 메서드
예) synchronized void m() {}
=> synchronized로 묶인 블록
예) synchronized(접근대상) {...}
ㄴ valueBox.wait();
ㄴ valueBox 객체에 대해 사용하라는 신호가 올 때까지 이 스레드에게 기다리라는 명령
=> 즉 wait()를 호출한 스레드는 Not Runnable 상태에 진입함
=> 실행을 멈추고 CPU 사용권을 받지 않는 상태가 됨
ㄴ 이 스레드를 잠에서 깨우는 방법?
=> 다른 스레드가 valueBox에 대해 notify()를 호출하면, wait() 로 신호를 기다리고 있는 스레드가 잠에서 깨어나 실행을 시작함
ㄴ 기다림을 끝내는 방법?
=> notify()를 통해 기다림이 끝났다는 것을 알림 받아야 함
=>
=>
ㄴ 값을 입력하면 바로 출력되고, 입력하지 않으면 바로 스레드 대기중이되고, 다시 값을 입력하면 notify 가 호출되어 깨워주고 값을 출력함
ㄴ 스레드는 종료되지 않음
멀티 스레드 재사용 - Pooling 기법을 이용하여 생성된 객체를 재활용하기
// 멀티 스레드 재사용 - 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();
}
}
ㄴ 3개의 스레드만 쓰도록 함
ㄴ 스레드를 다 쓴 후에는 다시 스레드 풀에 돌려줌
=>
ㄴ 3개의 스레드를 돌려 씀