-> 프로그램을 실행하면 OS로부터 실행에 필요한 자원(메모리)를 할당받아 프로세스가 된다.
-> 하나의 새로운 프로세스를 생성하는 것보다 하나의 새로운 쓰레드를 생성하는 것이 더 적은 비용이 든다.
<예제 13-1 >
✍️ 입력
class Ex13_1 {
public static void main(String args[]) {
ThreadEx1_1 t1 = new ThreadEx1_1();
Runnable r = new ThreadEx1_2();
Thread t2 = new Thread(r); // 생성자 Thread(Runnable target)
t1.start();
t2.start();
}
}
class ThreadEx1_1 extends Thread {
public void run() {
for(int i=0; i < 5; i++) {
System.out.println(getName()); // 조상인 Thread의 getName()을 호출
}
}
}
class ThreadEx1_2 implements Runnable {
public void run() {
for(int i=0; i < 5; i++) {
// Thread.currentThread() - 현재 실행중인 Thread를 반환한다.
System.out.println(Thread.currentThread().getName());
}
}
}
💻 출력
Thread-0
Thread-0
Thread-0
Thread-1
Thread-1
Thread-1
Thread-1
Thread-1
Thread-0
Thread-0
cf) 쓰레드의 실행순서는 OS의 스케줄러가 작성한 스케줄에 의해 결정된다.
멀티쓰레드가 오히려 시간이 더 걸린다.
-> why? : 쓰레드간의 작업 전환(context switching)에 시간이 걸리기 때문이다.
작업 전환을 할 때는 현재 진행 중인 작업의 상태, 예를 들면 다음에 실행해야할 위치(PC, 프로그램 카운터) 등의 정보를 저장하고 읽어 오는 시간이 소요된다.
싱글 코어에서 단순히 CPU만을 사용하는 계산작업이라면 오히려 멀티쓰레드보다 싱글쓰레드로 프로그래밍하는 것이 더 효율적이다.
<예제 13-2 >
✍️ 입력
class Ex13_2 {
public static void main(String args[]) {
long startTime = System.currentTimeMillis();
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("-"));
System.out.print("소요시간1:" +(System.currentTimeMillis()- startTime));
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("|"));
System.out.print("소요시간2:"+(System.currentTimeMillis() - startTime));
}
}
💻 출력
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------소요시간1:28||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||소요시간2:32
<예제 13-3 >
✍️ 입력
class Ex13_3 {
static long startTime = 0;
public static void main(String args[]) {
ThreadEx3_1 th1 = new ThreadEx3_1();
th1.start();
startTime = System.currentTimeMillis();
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("-"));
System.out.print("소요시간1:" + (System.currentTimeMillis() - Ex13_3.startTime));
}
}
class ThreadEx3_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++)
System.out.printf("%s", new String("|"));
System.out.print("소요시간2:" + (System.currentTimeMillis() - Ex13_3.startTime));
}
}
💻 출력
-----------------------------------------------||||||--------|||||||||||---|||---|||||||||||||||||||||||||||||||||||||||||||||-----------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||---------------------------------||||||||||||||||||-------||||||||||||--------||||||||||||||||||||||||------------------------------------------------||||---------------------------------|||||||||||||||||||---------------------------------------------------------------------------------------------------||||||||||||||||||||||||||||||||||소요시간1:32||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||소요시간2:34
실행할 때마다 다른 결과를 얻을 수 있다.
-> OS의 프로세스 스케줄러의 영향을 받기 때문이다.
-> 프로세스 스케줄러에 의해서 실행순서와 실행시간이 결정되기 때문에 매 순간 상황에 따라 실행시간이 일정하지 않고 쓰레드에게 할당되는 시간 역시 일정하지 않다.
-> 쓰레드가 이러한 불확실성을 가지고 있다는 것을 염두해 두어야 한다.
자바가 OS 독립적이라고 하지만 실제로 OS종속적이 부분이 몇가지 있는데 쓰레드가 그 중 하나이다.
-> 두 개의 쓰레드로 처리한다면 사용자의 입력을 기다리는 동안 다른 쓰레드가 작업을 처리할 수 있기 때문에 보다 효율적인 CPU의 사용이 가능하다.
<예제 13-4 >
✍️ 입력
import javax.swing.JOptionPane;
class Ex13_4 {
public static void main(String[] args) throws Exception {
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
for(int i=10; i > 0; i--) {
System.out.println(i);
try {
Thread.sleep(1000); // 1초간 시간을 지연한다.
} catch(Exception e ) {}
}
}
}
💻 출력
입력하신 값은 fdfdf입니다.
10
9
8
7
6
5
4
3
2
1
<예제 13-5 >
✍️ 입력
import javax.swing.JOptionPane;
class Ex13_5 {
public static void main(String[] args) throws Exception {
ThreadEx5_1 th1 = new ThreadEx5_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
}
}
class ThreadEx5_1 extends Thread {
public void run() {
for(int i=10; i > 0; i--) {
System.out.println(i);
try {
sleep(1000);
} catch(Exception e ) {}
}
} // run()
}
💻 출력
10
9
8
7
입력하신 값은 dfdfdfdf입니다.
6
5
4
3
2
1
<예제 13-6 >
✍️ 입력
class Ex13_6 {
public static void main(String args[]) {
ThreadEx6_1 th1 = new ThreadEx6_1();
ThreadEx6_2 th2 = new ThreadEx6_2();
th2.setPriority(7);
System.out.println("Priority of th1(-) : " + th1.getPriority());
System.out.println("Priority of th2(|) : " + th2.getPriority());
th1.start();
th2.start();
}
}
class ThreadEx6_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++) {
System.out.print("-");
for(int x=0; x < 10000000; x++);
}
}
}
class ThreadEx6_2 extends Thread {
public void run() {
for(int i=0; i < 300; i++) {
System.out.print("|");
for(int x=0; x < 10000000; x++);
}
}
}
💻 출력
Priority of th1(-) : 5
Priority of th2(|) : 7
-|-|--------------------------------------------------------------||||||||||||||||||||----------------------------------------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||----|----------||||||||||||||||||||||||||||||||||-|||||||||||||||||||||||||||||||||||||||||||||||||||||-------------------|||||||||||||||||-------------|||||||||||||||||||||||||||||||||||||||||||||||-----------------------------------------------------------------------------------------------------------------------------------------------------
-> th1은 main메서드에서 생성하였기 때문에 우선순위는 5이다(main메서드 우선순위가 5이다.)
-> OS에 종속적이기 때문에 우선순위를 넣는다고 해도 예측만 가능할 뿐 정확히 알 수는 없다.
<예제 13-7 >
✍️ 입력
class Ex13_7 implements Runnable {
static boolean autoSave = false;
public static void main(String[] args) {
Thread t = new Thread(new Ex13_7());
t.setDaemon(true); // 이 부분이 없으면 종료되지 않는다.
t.start();
for(int i=1; i <= 10; i++) {
try{
Thread.sleep(1000);
} catch(InterruptedException e) {}
System.out.println(i);
if(i==5) autoSave = true;
}
System.out.println("프로그램을 종료합니다.");
}
public void run() {
while(true) {
try {
Thread.sleep(3 * 1000); // 3초마다
} catch(InterruptedException e) {}
// autoSave의 값이 true이면 autoSave()를 호출한다.
if(autoSave) autoSave();
}
}
public void autoSave() {
System.out.println("작업파일이 자동저장되었습니다.");
}
}
💻 출력
1
2
3
4
5
작업파일이 자동저장되었습니다.
6
7
8
작업파일이 자동저장되었습니다.
9
10
프로그램을 종료합니다.
쓰레드를 생성하고 start()를 호출하면 바로 실행되는 것이 아니라 실행대기열에 저장되어 자신의 차례가 될 때까지 기다려야 한다. 실행대기열은 큐(queue)와 같은 구조로 먼저 실행대기열에 들어온 쓰레드가 먼저 실행된다.
실행대기상태에 있다가 자신의 차례가 되면 실행상태가 된다.
주어진 실행시간이 다되거나 yield()를 만나면 다시 실행대기상태가 되고 다음 차례의 쓰레드가 실행상태가 된다.
실행 중에 suspend(), sleep(), wait(), join(), I/O block에 의해 일시정지상태가 될 수 있다. I/O block은 입출력작업에서 발생하는 지연상태를 말한다. 사용자의 입력을 기다리는 경우를 예로 들 수 있는데, 이런 경우 일시정지 상태에 있다가 사용자가 입력을 마치면 다시 실행대기상태가 된다.
지정된 일시정지시간이 다되거나(time-out), notify(), resume(), interrupt()가 호출되면 일시정지상태를 벗어나 다시 실행대기열에 저장되어 자신의 차례를 기다리게 된다.
실행을 모두 마치거나 stop()이 호출되면 쓰레드는 소멸된다.
static void sleep(long millis)
<예제 13-8 >
✍️ 입력
class Ex13_8 {
public static void main(String args[]) {
ThreadEx8_1 th1 = new ThreadEx8_1();
ThreadEx8_2 th2 = new ThreadEx8_2();
th1.start(); th2.start();
try {
th1.sleep(2000);
} catch(InterruptedException e) {}
System.out.print("<<main 종료>>");
} // main
}
class ThreadEx8_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++) System.out.print("-");
System.out.print("<<th1 종료>>");
} // run()
}
class ThreadEx8_2 extends Thread {
public void run() {
for(int i=0; i < 300; i++) System.out.print("|");
System.out.print("<<th2 종료>>");
} // run()
}
💻 출력
--------||||||||--------------------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||------|||||||--||------||||--||||||||||||||||||||||||||||||-------|||--------||||||||||||||||||||||||-||||||||||||||------------------------------------------------------------||||||||||||||||||-----|||||||||||||||||||||||||||||||||||||||||||||||------------------------------------|||||||||||||||||||||||||----------------------------------||||||||||||||||-------------------------------------------------<<th2 종료>>--------------------------------------------------------<<th1 종료>><<main 종료>>
-> sleep()는 (static메서드) 항상 현재 실행 중인 쓰레드에 대해 작동하기 때문에 th1.sleep()를 호출해도 sleep()를 호출한 쓰레드(main)에 적용이 된다.
void interrupt() 쓰레드의 interrupted상태를 false에서 true로 변경
boolean isInterrupted() 쓰레드의 interrupted상태를 반환
static boolean interrupted() 현재 쓰레드의 interrupted상태를 반환 후, false로 변경
cf) static이 붙으면 호출한 쓰레드를 기준으로 메서드를 적용한다.
<예제 13-9 >
✍️ 입력
import javax.swing.JOptionPane;
class Ex13_9 {
public static void main(String[] args) throws Exception {
ThreadEx9_1 th1 = new ThreadEx9_1();
th1.start();
String input = JOptionPane.showInputDialog("아무 값이나 입력하세요.");
System.out.println("입력하신 값은 " + input + "입니다.");
th1.interrupt(); // interrupt()를 호출하면, interrupted상태가 true가 된다.
System.out.println("isInterrupted():"+ th1.isInterrupted()); // true
}
}
class ThreadEx9_1 extends Thread {
public void run() {
int i = 10;
while(i!=0 && !isInterrupted()) {
System.out.println(i--);
for(long x=0;x<2500000000L;x++); // 시간 지연
}
System.out.println("카운트가 종료되었습니다.");
}
}
💻 출력
10
9
8
7
입력하신 값은 gdgdd입니다.
isInterrupted():true
카운트가 종료되었습니다.
<예제 13-10 >
✍️ 입력
class Ex13_10 {
public static void main(String args[]) {
RunImplEx10 r = new RunImplEx10();
Thread th1 = new Thread(r, "*");
Thread th2 = new Thread(r, "**");
Thread th3 = new Thread(r, "***");
th1.start();
th2.start();
th3.start();
try {
Thread.sleep(2000);
th1.suspend(); // 쓰레드 th1을 잠시 중단시킨다.
Thread.sleep(2000);
th2.suspend();
Thread.sleep(3000);
th1.resume(); // 쓰레드 th1이 다시 동작하도록 한다.
Thread.sleep(3000);
th1.stop(); // 쓰레드 th1을 강제종료시킨다.
th2.stop();
Thread.sleep(2000);
th3.stop();
} catch (InterruptedException e) {}
} // main
}
class RunImplEx10 implements Runnable {
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch(InterruptedException e) {}
}
} // run()
}
💻 출력
**
**
쓰레드 자신이 하던 작업을 잠시 멈추고 다른 쓰레드가 지정된 시간동안 작업을 수행하도록 할 때 join()을 사용한다.
sleep()처럼 interrupt()에 의해 대기상태에서 벗어날 수 있으며, join()이 호출되는 부분을 try - catch문으로 감싸야 한다.
sleep() vs join()
-> join()은 현재 쓰레드가 아닌 특정 쓰레드에 대해 작동하므로 static메서드가 아니다.
쓰레드의 실행제어 예제
-> if문 안에 gc를 깨우고(interrupt) join()을 사용해서 gc가 메모리를 확보할 시간을 주어야 한다.
try{
gc.join(100);
} catch(InterruptedException e) {}
<예제 13-11 >
✍️ 입력
class Ex13_11 {
static long startTime = 0;
public static void main(String args[]) {
ThreadEx11_1 th1 = new ThreadEx11_1();
ThreadEx11_2 th2 = new ThreadEx11_2();
th1.start();
th2.start();
startTime = System.currentTimeMillis();
try {
th1.join(); // main쓰레드가 th1의 작업이 끝날 때까지 기다린다.
th2.join(); // main쓰레드가 th2의 작업이 끝날 때까지 기다린다.
} catch(InterruptedException e) {}
System.out.print("소요시간:" + (System.currentTimeMillis() - Ex13_11.startTime));
} // main
}
class ThreadEx11_1 extends Thread {
public void run() {
for(int i=0; i < 300; i++) {
System.out.print(new String("-"));
}
} // run()
}
class ThreadEx11_2 extends Thread {
public void run() {
for(int i=0; i < 300; i++) {
System.out.print(new String("|"));
}
} // run()
}
💻 출력
------------------------------|||||--------|||||||||-------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||-----------------------------|||||||||||||||||||||||||||||||||||||||||||||||||||||||-----------------------------------||||||||-----------|||||||||||||||||----------------------------------------------------------------------------------------------------------||||||||||||-----------|||||||||||||||||||------------------|||||||||||||||||||||||---------||||||||||||||---------------------||||-----||||||||||----------||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||소요시간:5
-> th1과 th2의 작업을 마칠 때까지 main쓰레드가 기다리도록 했다.
<예제 13-12 >
✍️ 입력
class Ex13_12 {
public static void main(String args[]) {
Runnable r = new RunnableEx12();
new Thread(r).start(); // ThreadGroup에 의해 참조되므로 gc대상이 아니다.
new Thread(r).start(); // ThreadGroup에 의해 참조되므로 gc대상이 아니다.
}
}
class Account {
private int balance = 1000;
public int getBalance() {
return balance;
}
public void withdraw(int money){
if(balance >= money) {
try { Thread.sleep(1000);} catch(InterruptedException e) {}
balance -= money;
}
} // withdraw
}
class RunnableEx12 implements Runnable {
Account acc = new Account();
public void run() {
while(acc.getBalance() > 0) {
// 100, 200, 300중의 한 값을 임으로 선택해서 출금(withdraw)
int money = (int)(Math.random() * 3 + 1) * 100;
acc.withdraw(money);
System.out.println("balance:"+acc.getBalance());
}
} // run()
}
💻 출력
balance:800
balance:800
balance:500
balance:200
balance:200
balance:200
balance:0
balance:-100
<예제 13-13 >
✍️ 입력
class Ex13_13 {
public static void main(String args[]) {
Runnable r = new RunnableEx13();
new Thread(r).start();
new Thread(r).start();
}
}
class Account2 {
private int balance = 1000; // private으로 해야 동기화가 의미가 있다.
public int getBalance() {
return balance;
}
public synchronized void withdraw(int money){ // synchronized로 메서드를 동기화
if(balance >= money) {
try { Thread.sleep(1000);} catch(InterruptedException e) {}
balance -= money;
}
} // withdraw
}
class RunnableEx13 implements Runnable {
Account2 acc = new Account2();
public void run() {
while(acc.getBalance() > 0) {
// 100, 200, 300중의 한 값을 임으로 선택해서 출금(withdraw)
int money = (int)(Math.random() * 3 + 1) * 100;
acc.withdraw(money);
System.out.println("balance:"+acc.getBalance());
}
} // run()
}
💻 출력
balance:800
balance:500
balance:200
balance:200
balance:200
balance:200
balance:0
balance:0
<예제 13-14 >
✍️ 입력
import java.util.ArrayList;
class Customer implements Runnable {
private Table table;
private String food;
Customer(Table table, String food) {
this.table = table;
this.food = food;
}
public void run() {
while(true) {
try { Thread.sleep(10);} catch(InterruptedException e) {}
String name = Thread.currentThread().getName();
if(eatFood())
System.out.println(name + " ate a " + food);
else
System.out.println(name + " failed to eat. :(");
} // while
}
boolean eatFood() { return table.remove(food); }
}
class Cook implements Runnable {
private Table table;
Cook(Table table) { this.table = table; }
public void run() {
while(true) {
int idx = (int)(Math.random()*table.dishNum());
table.add(table.dishNames[idx]);
try { Thread.sleep(100);} catch(InterruptedException e) {}
} // while
}
}
class Table {
String[] dishNames = { "donut","donut","burger" };
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<>();
public synchronized void add(String dish) { // synchronized를 추가
if(dishes.size() >= MAX_FOOD)
return;
dishes.add(dish);
System.out.println("Dishes:" + dishes.toString());
}
public boolean remove(String dishName) {
synchronized(this) {
while(dishes.size()==0) {
String name = Thread.currentThread().getName();
System.out.println(name+" is waiting.");
try { Thread.sleep(500);} catch(InterruptedException e) {}
}
for(int i=0; i<dishes.size();i++)
if(dishName.equals(dishes.get(i))) {
dishes.remove(i);
return true;
}
} // synchronized
return false;
}
public int dishNum() { return dishNames.length; }
}
class Ex13_14 {
public static void main(String[] args) throws Exception {
Table table = new Table(); // 여러 쓰레드가 공유하는 객체
new Thread(new Cook(table), "COOK").start();
new Thread(new Customer(table, "donut"), "CUST1").start();
new Thread(new Customer(table, "burger"), "CUST2").start();
Thread.sleep(5000);
System.exit(0);
}
}
💻 출력
Dishes:[donut]
CUST2 failed to eat. :(
CUST1 ate a donut
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
CUST1 is waiting.
<예제 13-15 >
✍️ 입력
import java.util.ArrayList;
class Customer2 implements Runnable {
private Table2 table;
private String food;
Customer2(Table2 table, String food) {
this.table = table;
this.food = food;
}
public void run() {
while(true) {
try { Thread.sleep(100);} catch(InterruptedException e) {}
String name = Thread.currentThread().getName();
table.remove(food);
System.out.println(name + " ate a " + food);
} // while
}
}
class Cook2 implements Runnable {
private Table2 table;
Cook2(Table2 table) { this.table = table; }
public void run() {
while(true) {
int idx = (int)(Math.random()*table.dishNum());
table.add(table.dishNames[idx]);
try { Thread.sleep(10);} catch(InterruptedException e) {}
} // while
}
}
class Table2 {
String[] dishNames = { "donut","donut","burger" }; // donut의 확률을 높인다.
final int MAX_FOOD = 6;
private ArrayList<String> dishes = new ArrayList<>();
public synchronized void add(String dish) {
while(dishes.size() >= MAX_FOOD) {
String name = Thread.currentThread().getName();
System.out.println(name+" is waiting.");
try {
wait(); // COOK쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
dishes.add(dish);
notify(); // 기다리고 있는 CUST를 깨우기 위함.
System.out.println("Dishes:" + dishes.toString());
}
public void remove(String dishName) {
synchronized(this) {
String name = Thread.currentThread().getName();
while(dishes.size()==0) {
System.out.println(name+" is waiting.");
try {
wait(); // CUST쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
}
while(true) {
for(int i=0; i<dishes.size();i++) {
if(dishName.equals(dishes.get(i))) {
dishes.remove(i);
notify(); // 잠자고 있는 COOK을 깨우기 위함
return;
}
} // for문의 끝
try {
System.out.println(name+" is waiting.");
wait(); // 원하는 음식이 없는 CUST쓰레드를 기다리게 한다.
Thread.sleep(500);
} catch(InterruptedException e) {}
} // while(true)
} // synchronized
}
public int dishNum() { return dishNames.length; }
}
class Ex13_15 {
public static void main(String[] args) throws Exception {
Table2 table = new Table2();
new Thread(new Cook2(table), "COOK").start();
new Thread(new Customer2(table, "donut"), "CUST1").start();
new Thread(new Customer2(table, "burger"), "CUST2").start();
Thread.sleep(2000);
System.exit(0);
}
}
💻 출력
Dishes:[burger]
Dishes:[burger, burger]
Dishes:[burger, burger, donut]
Dishes:[burger, burger, donut, burger]
Dishes:[burger, burger, donut, burger, donut]
Dishes:[burger, burger, donut, burger, donut, donut]
COOK is waiting.
CUST2 ate a burger
CUST1 ate a donut
Dishes:[burger, burger, donut, donut, donut]
CUST1 ate a donut
CUST2 ate a burger
Dishes:[burger, donut, donut, donut]
Dishes:[burger, donut, donut, donut, donut]
Dishes:[burger, donut, donut, donut, donut, burger]
COOK is waiting.
CUST2 ate a burger
CUST1 ate a donut
Dishes:[donut, donut, donut, burger, donut]
CUST1 ate a donut
CUST2 ate a burger
Dishes:[donut, donut, donut, donut]
Dishes:[donut, donut, donut, donut, donut]
Dishes:[donut, donut, donut, donut, donut, donut]
COOK is waiting.
CUST1 ate a donut
Dishes:[donut, donut, donut, donut, donut, donut]
CUST1 ate a donut
CUST2 is waiting.
Dishes:[donut, donut, donut, donut, donut, donut]
[출처] 자바의 정석 <기초편> (남궁 성 지음)