Transcript Java Thread

프로그래머를 위한 Java 2
제12장 자바 쓰레드 프로그래밍
12.1 쓰레드 기초

프로세스(process)


Chap.12
2
실행중인 프로그램(program in execution)
쓰레드(thread)

"실행될 명령어들의 연속 (a sequence of instructions to be executed)”

단일 쓰레드 프로그램 (single-thread program)
 하나의 실로 꿸 수 있는 프로그램을

다중 쓰레드 프로그램 (multi-thread program)
 프로그램에서 여러 개의 함수들이 서로 독립적으로 수행
이런 경우에는 명령어들이 순차적으로 수행되지 않기 때문에
하나의 실로 꿸 수 없다.
12.1 쓰레드 기초
단일 쓰레드 프로그램
다중 쓰레드 프로그램
Chap.12
3
12.1 쓰레드 기초

실행되는 명령어들의 순서를 선으로 표현
단일 쓰레드 프로그램
다중 쓰레드 프로그램
Chap.12
4
12.1 쓰레드 기초

Chap.12
5
자바에서의 쓰레드

Thread 클래스로부터 상속받아서 사용

Runnable 인터페이스를 인플리멘츠해서 사용

두 가지 방법 중에서 프로그래머가 상황에 맞게 선택하여 사용

두 방법 모두 쓰레드를 사용하려면 run() 함수를 만들어야 한다.
 run() 함수에 쓰레드가 일할 작업의 내용을 기술
 쓰레드를 만든 다음에 쓰레드를 실행시키려면 start() 함수를 호출
 start() 함수를 호출하면 자동적으로 run() 함수가 수행된다.
12.1 쓰레드 기초
자바 쓰레드의 상태도
Chap.12
6
12.1 쓰레드 기초
Chap.12
7
12.1.2 Thread 상속


Thread 클래스로부터 상속을 받아서 쓰레드를 만들어 사용하는 예제
예제 : ExtendedThreadTest.java
1 public class ExtendedThreadTest {
2
static public void main(String args[]) {
3
ExtendedThread thr_a, thr_b;
4
5
thr_a = new ExtendedThread();
6
thr_b = new ExtendedThread();
7
8
thr_a.start();
9
thr_b.start();
…………….
13 class ExtendedThread extends Thread {
14
public void run() {
15
System.out.println("Hello, I ("+getName()+") am working.");
16
}
…………
12.1 쓰레드 기초
Chap.12
8
12.1.3 Runnable 인터페이스 상속

Runnable 인터페이스를 임플리멘츠해서 쓰레드를 만드는 예제

쓰레드를 만들기 위해서는 쓰레드 생성자에 Runnable 인터페이스를
임플리멘츠하는 객체를 매개 변수로 전달한다.

객체의 특성을 유지하면서 쓰레드가 필요한 경우에 많이 사용
예제 : RunnableThread.java
1 public class RunnableThread implements Runnable {
2
private int a;
3
Thread thr_a;
4
5
public RunnableThread() {
6
a = 10;
7
}
8
9
public void doing() {
10
thr_a = new Thread(this);
11
thr_a.start();
12
}
12.1 쓰레드 기초
13
14
15
16
17
18
19
20
21
22
23
24 }
public void run() {
System.out.println("Hi! I (" +
Thread.currentThread().getName() + ") am working.");
System.out.println("I can access private var 'a' = " + a);
}
static public void main(String args[]) {
RunnableThread rt = new RunnableThread();
rt.doing();
}
Chap.12
9
12.1 쓰레드 기초

Chap.12
10
쓰레드 생성 방법 선택

두 방법은 서로 장단점이 있기 때문에 상황에 맞는 방법을 이용

Thread 클래스를 상속받아서 만드는 방법
 클래스와는 무관하게 독립된 작업을 하거나 여러 개의 쓰레드가 필요한
경우에 적합

Runnable 인터페이스를 임플리멘츠하는 방법
 클래스의 특성을 그대로 유지하면서 쓰레드를 하나 더 생성할 수 있음
Runnable을 임플리멘츠하는 경우
Thread 클래스를 상속받는 경우
12.1 쓰레드 기초
Chap.12
11
12.1.4 쓰레드 종료

Dead 상태
 run() 함수를 마치거나, stop() 함수가 호출되면 Dead 상태로 전이
 Dead 상태는 쓰레드의 모든 작업이 종료된 것을 의미한다.
 Dead 상태의 종료된 쓰레드는 start() 함수로 다시 실행시킬 수 없다.

JDK 1.2 이후 버전에서는 쓰레드의 stop(), resume(), suspend() 메소드가
deprecated 경고를 유발
 이러한 메소드들이 쓰레드에서 deadlock을 유발할 수 있기 때문.
 따라서, 복잡한 시스템이나 상업용 시스템을 개발하는 경우에는
이 메소드들의 사용을 자제해야 한다
12.1 쓰레드 기초

예제 : StopThreadTest.java
1 public class StopThreadTest {
2
static public void main(String s[]) {
3
StopThread thr_a = new StopThread();
4
System.out.println("Starting the thread...");
5
thr_a.start();
6
7
try {
8
Thread.sleep(2000);
9
}catch (InterruptedException e) {}
10
11
System.out.println("Stopping the thread...");
12
thr_a.stop();
13
14
try {
15
Thread.sleep(2000);
16
}catch (InterruptedException e) {}
Chap.12
12
12.1 쓰레드 기초
17
18
System.out.println("Trying to start the thread again...");
19
thr_a.start();
20
}
21 }
22
23 class StopThread extends Thread {
24
public void run() {
25
while (true) {
26
try {
27
sleep(500);
28
}catch (InterruptedException e) {}
29
System.out.println("Hello, I (" + getName() +
30
") slept, now I am working.");
31
}
32
}
33 }
Chap.12
13
12.1 쓰레드 기초
Chap.12
14
12.1.5 ThreadDeath 예외

stop() 메소드에 의해 쓰레드가 종료되면 ThreadDeath 예외가 발생

stop() 메소드에 의해 쓰레드가 종료하기 전에 쓰레드가 가지고 있는
락(lock)과 모니터(monitor)는 풀어주어야 한다.
 이런 경우에 쓰레드가 종료할 때 발생되는 ThreadDeath 예외를
잡아서 쓰레드 종료 직전에 필요한 작업들을 처리할 수 있다.
 모든 작업이 처리된 다음에는 ThreadDeath 예외를 다시
throw 해주어야 한다.
 stop() 메소드를 사용하지 않는 경우에는
ThreadDeath 예외를 잡아주지 않아도 된다.
12.1 쓰레드 기초

예제 : CatchDeathTest.java
1 public class CatchDeathTest {
2
static public void main(String args[]) {
3
KilledThread thr_a = new KilledThread("마당쇠 쓰레드");
4
5
System.out.println("Starting the thread...");
6
thr_a.start();
7
8
try {
9
Thread.sleep(2000);
10
}catch (InterruptedException e) {}
11
12
System.out.println("Stopping the thread...");
13
thr_a.stop();
14
}
15 }
16
Chap.12
15
12.1 쓰레드 기초
17 class KilledThread extends Thread {
18
KilledThread(String name) {
19
super(name);
20
}
21
22
public void run() {
23
try {
24
while (true) {
25
sleep(500);
26
System.out.println(getName() + ": is running");
27
}
28
}catch (ThreadDeath ouch) {
29
System.out.println("I (" + getName() +
30
") is being killed.");
31
throw(ouch);
32
}catch (InterruptedException e) {}
33
}
34 }
Chap.12
16
12.1 쓰레드 기초
Chap.12
17
12.1.6 suspend( ) / resume( )

suspend()와 resume() 메소드를 이용하는 예제 (SuspResu.java)
 JDK 1.2 이후 버전에서 stop() 메소드와 함께
suspend(), resume() 메소드는 deprecated 경고를 유발
 이 메소드들은 가급적 사용을 자제하기 바람

sleep() 메소드는 일정 시간 동안 쓰레드를 블락시키는데 사용,
suspend() 메소드는 얼마동안 블락되어야 하는지 모르는 경우에 사용

suspend() 메소드로 블락된 쓰레드는 resume() 메소드로
다시 Runnable 상태로 돌아올 수 있다.
12.1 쓰레드 기초

예제 : SuspResu.java
1 public class SuspResu {
2
static public void main(String s[]) {
3
SuspendedThread thr_a = new SuspendedThread("홍길동");
4
System.out.println("Starting the thread...");
5
thr_a.start();
6
7
try {
8
Thread.sleep(2000);
9
}catch (InterruptedException e) {}
10
11
System.out.println("Suspending the thread...");
12
thr_a.suspend();
13
14
try {
15
Thread.sleep(2000);
16
}catch (InterruptedException e) {}
Chap.12
18
12.1 쓰레드 기초
17
18
System.out.println("Resuming the thread...");
19
thr_a.resume();
20
}
21 }
22
23 class SuspendedThread extends Thread {
24
public SuspendedThread(String name){
25
super(name);
26
}
27
28
public void run() {
29
while (true) {
30
try {
31
sleep(500);
32
}catch (InterruptedException e) {}
33
System.out.println("I ("+ getName()+") am working.");
34
}
35
}
36 }
Chap.12
19
12.1 쓰레드 기초
Chap.12
20
12.1.7 스케쥴링

쓰레드의 스케쥴링은 자바 가상 머신에서 이루어진다.

자바 가상 머신은 우선 순위에 따라 선점 (preemtive) 방식으로 스케쥴링

선점 방식
 우선 순위가 높은 쓰레드가 등장하면 현재 실행중인 쓰레드는 쫓겨나고
우선순위가 높은 쓰레드가 실행되는 것을 의미
 우선 순위가 낮은 쓰레드는 우선 순위가 높은 쓰레드가 종료하거나
블락될 때까지 기다려야 한다
12.1 쓰레드 기초
예제 : Priority.java
1 class MyThread5 extends Thread {
2
protected boolean stop;
3
…………………..
21 public class Priority {
22
static public void main(String s[]) {
23
MyThread5 thr_a = new MyThread5("A");
24
MyThread5 thr_b = new MyThread5("B");
25
MyThread5 thr_c = new MyThread5("C");
26
27
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
28
29
System.out.println("Starting the threads...");
30
thr_a.start();
31
thr_b.start();
32
thr_c.start();
33
34
try {
35
Thread.sleep(1000);
36
} catch (InterruptedException e)
37
38
System.out.println("Setting Thread b to a higher priority...");
39
thr_b.setPriority(thr_b.getPriority() + 2);
…………………….
Chap.12
21
12.1 쓰레드 기초
41
try {
42
Thread.sleep(1000);
43
} catch (InterruptedException e) { }
44
45
System.out.println("Setting Thread b to a lower priority...");
46
thr_b.setPriority(thr_b.getPriority() - 4);
47
48
try {
49
Thread.sleep(1000);
50
} catch (InterruptedException e) { }
51
52
System.out.println("Setting Thread b to a equal priority...");
53
thr_b.setPriority(thr_b.getPriority() + 2);
54
55
try {
56
Thread.sleep(1000);
57
} catch (InterruptedException e) { }
58
59
System.out.println("Stopping the threads...");
60
thr_a.stop(true);
61
thr_b.stop(true);
62
thr_c.stop(true);
…………………………..
Chap.12
22
12.1 쓰레드 기초
Chap.12
23
12.1.8 데몬 쓰레드

다중 쓰레드로된 응용 프로그램은 모든 쓰레드가 종료되어야 프로세스가
종료

데몬 쓰레드
 쓰레드들 중에서 데몬 역할을 하는, 즉 다른 쓰레드에 서비스를
해주면서 다른 쓰레드가 모두 종료하면 자신도 종료하는 쓰레드
 데몬 쓰레드는 쓰레드를 생성한 다음에 setDaemon(true) 메소드를
이용해서 만들어줄 수 있다
12.1 쓰레드 기초
예제 : DaemonThread.java
1 public class DaemonThread {
2
static public void main(String s[]) {
3
MyThread6 thr_a = new MyThread6();
4
MyThread6 thr_b = new MyThread6();
5
6
System.out.println("Starting the threads...");
7
thr_a.start();
8
thr_b.setDaemon(true);
9
thr_b.start();
10
11
12
13
try {
Thread.sleep(2000);
} catch (InterruptedException e) { }
Chap.12
24
12.1 쓰레드 기초
Chap.12
25
15
System.out.println("Stopping the normal thread...");
16
thr_a.stop(true);
………………
20 class MyThread6 extends Thread {
21
protected boolean stop;
……………….
27
public void run() {
28
while (!stop) {
29
try {
30
sleep(500);
31
} catch (InterruptedException e) { }
32
if (isDaemon())
33
System.out.println(getName() + ": daemon thread");
34
else
35
System.out.println(getName() + ": regular thread");
…………………….
12.1 쓰레드 기초
Chap.12
26
12.1.9 동기화
a=10;
a = a + 1;
어셈블러
LOAD
a
ADD
1
STORE
a

쓰레드가 a를 사용할 때 다른 쓰레드는 작업이 끝날 때까지 사용하지
못하도록 락(lock)을 걸고 작업

자바에서는 synchronized라는 문장을 이용해서
원하는 메소드나 자료구조에 락(lock)거는 방법을 제공
12.1 쓰레드 기초

예제 : Monitors.java
1 public class Monitors implements Runnable {
2
Critical CritObj;
3
4
Monitors() {
5
CritObj = new Critical();
6
Thread thr_a = new Thread(this, "홍길동");
7
Thread thr_b = new Thread(this, "허접");
8
9
thr_a.start();
10
thr_b.start();
11
}
12
13
public void run() {
14
CritObj.a();
15
CritObj.b();
16
}
……………………….
25 class Critical {
26
public synchronized void a() {
Chap.12
27
12.1 쓰레드 기초
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 }
System.out.println(Thread.currentThread().getName() +
" is in a().");
try {
Thread.sleep((int)Math.round(Math.random() * 5000));
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() +
" is leaving a().");
}
public static synchronized void b() {
System.out.println(Thread.currentThread().getName() +
" is in b().");
try {
Thread.sleep((int)Math.round(Math.random() * 5000));
}catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() +
" is leaving b().");
}
Chap.12
28
12.1 쓰레드 기초

Chap.12
29
synchronized로 락(lock)이 걸린 메소드를 재귀적으로 호출


일반 락이라면 락이 걸린 메소드를 재귀적으로 호출하면 데드락(deadlock)이 발생된다. 그러나, synchronized 문장은 재귀적인 호출을
허용한다.
예제 : Reentrant.java
1 public class Reentrant {
2 public static void main(String args[])
3
TestClass Test = new TestClass();
4
Test.MyMethod();
5 }
6 }
7
8 class TestClass {
9 int times = 1;
10
11 public synchronized void MyMethod() {
12
int i = times++;
12.1 쓰레드 기초
13
14
15
System.out.println("MyMethod has started "
+ i + " time(s)");
while (times < 4)
16
MyMethod();
17
System.out.println("MyMethod has exited "
18
19
20 }
+ i + " time(s)");
}
Chap.12
30
12.1 쓰레드 기초
Chap.12
31
12.1.10 쓰레드 그룹

여러 개의 쓰레드를 동시에 처리해야 하는 경우가 발생

어떤 조건을 만족하는 경우에 모든 쓰레드를 종료시킨다든지,
블락시킨다든지 하는 작업이 필요하다.

여러 쓰레드는 동시에 제어하기 위해서 쓰레드 그룹을 사용
 쓰레드와 마찬가지로 쓰레드 그룹의 stop(), suspend(), resume()
메소드는 JDK 1.2 이후 버전에서 deprecated 경고를 발생시킨다.
12.1 쓰레드 기초
예제 : ThrGroup.java
1 class MyThread extends Thread {
2
MyThread(ThreadGroup tg, String name) {
3
super(tg, name);
4
}
………………….
16 public class ThrGroup {
17
static public void main(String s[]) {
18
ThreadGroup MyGroup = new ThreadGroup("My Group");
19
20
MyThread thr_a = new MyThread(MyGroup, "A");
21
MyThread thr_b = new MyThread(MyGroup, "B");
22
MyThread thr_c = new MyThread(MyGroup, "C");
23
24
System.out.println("Starting the threads...");
25
thr_a.start();
26
thr_b.start();
27
thr_c.start();
28
29
try {
30
Thread.sleep(2000);
31
} catch (InterruptedException e)
32
33
System.out.println("Stopping the thread group...");
34
MyGroup.stop();
……………………
Chap.12
32
12.2 쓰레드 응용
Chap.12
33
12.2.1 자바 쓰레드

자바의 쓰레드는 운영체제의 쓰레드로 매핑이 이루어져야
실제로 쓰레드로서 작업을 수행할 수 있다.

UNIX나 NT, Win95/98, OS/2 등이 쓰레드를 지원하는 방식이 다르므로
자바 쓰레드와 실제 운영체제의 쓰레드와 매핑이 달라질 수 있다.

실제 운영체제에서 쓰레드는 사용자 쓰레드와 커널 쓰레드로 구분
 사용자 쓰레드는 사용자가 라이브러리를 이용해서 만든 쓰레드
 커널 쓰레드는 실제 스케쥴링이 일어나는 스케쥴링 단위
 사용자 쓰레드와 커널 쓰레드는 M-1 관계, 1-1 관계, M-N 관계를 가짐
 NT 와 Win95는 1-1 관계의 대표적인 예이고,
M-N은 Solaris 쓰레드의 대표적인 예이다.
 자바 쓰레드는 일종의 사용자 쓰레드이다.
12.2 쓰레드 응용

Chap.12
34
M-1 매핑

자바 쓰레드는 사용자 쓰레드와 1-1 관계를 갖고,
사용자 쓰레드는 커널 쓰레드와 M-1 관계를 갖는다.

커널 쓰레드는 하나만 있기 때문에 다중 쓰레드의 장점을 살리기 어렵다

커널 쓰레드가 하나만 있으면 쓰레드를 이용할 때의 성능 향상은 어렵다

자바 쓰레드는 우선 순위에따라 커널 쓰레드에 스케쥴링되서 CPU에서
실행될 수 있다. 이러한 경우에 커널 쓰레드는 가상의 CPU(virtual CPU)와
같은 역할을 한다.

M-1 매핑의 대표적인 예는 green_threads이다.
 솔라리스 2.4, 2.5 운영체제에 제공되던 JDK 1.02 버전 및 JDK 1.1
버전은 모두 green_threads를 사용
 JDK 1.2 버전 이상에서는 JIT 컴파일러(Just In Time Compiler)를
이용한 M-N을 지원
12.2 쓰레드 응용
M-1 매핑
Chap.12
35
12.2 쓰레드 응용

Chap.12
36
1-1 매핑

1-1 매 핑 관 계 는 NT, Win95 에 서
사용되는 쓰레드

자바 쓰레드 1개에 커널 쓰레드가 1개
매핑되기 때문에 다중 쓰레드의 장점을
잘 살릴 수 있다

1-1 매 핑 에 서 도 CPU 에 서 스 케 쥴 링
단위는 커널 쓰레드이기 때문에
사용자 쓰레드가 많다는 것은
CPU에서 스케쥴링될 수 있는 커널
쓰레드가 많 다 는 것 을 의 미 하 기
때문에
자바 프로그램에서 성능
향상을 기대
1-1 매핑
12.2 쓰레드 응용

Chap.12
37
M-N 매핑

JDK 1.2 이상에서 지원되는 자바
native 쓰레드를 사용하는 경우에,
C 언어에서 쓰레드를 사용하는
것처럼 쓰레드를 자유롭게 사용할 수
있다.

NT의 1-1 매핑보다 프로그래머가
프로그램하는데 편리하면서,
다중 쓰레드의 장점을 최대한 활용할
수 있다.
M-N 매핑
12.2 쓰레드 응용

자바 쓰레드와 POSIX 쓰레드 비교
Chap.12
38
12.2 쓰레드 응용

각 운영체제별 쓰레드 환경
Chap.12
39
12.2 쓰레드 응용
Chap.12
40
12.2.2 생산자와 소비자

프로세스와 쓰레드에서 생산자와 소비자 문제는 대단히 유명
 생 산 자 는 알 파 벳 을 생 성 해 서 Monitor 에 저 장 하 고 , 소 비 자 는
저장된 알파벳을 가져간다.
 생 산 자 는 Monitor 에 꽉 차 면 블 락 되 고 , 소 비 자 는 Monitor 에
가져갈 알파벳이 없으면 블락된다.
 생산자가 블락된 경우 소비자는 Monitor에서 알파벳을 가져가면서
생산자에서 블락 생태를 벗어날 수 있도록 알려주고, 생산자도
소비자가 블락 상태에 있으면 알파벳을 저장하면서 소비자에게
알려야 한다.
 생 산 자 와 소 비 자 가 동 시 에 Monitor 에 접 근 해 서 작 업 하 지
못하도록 Monitor에는 락을 걸어주어야 한다.
12.2 쓰레드 응용

Chap.12
41
생산자와 소비자 관계
Monitor
Producer
add(char c)
eat( )
ADGHCE
Consumer
12.2 쓰레드 응용

예제 : Producer.java
1 class Producer extends Thread {
2
private Monitor monitor;
3
private String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
4
private int sleepTime = 200;
5
6
public Producer(Monitor s) {
7
monitor = s;
8
}
9
10
public void run() {
11
char c;
12
for (int i = 0; i < 20; i++) {
13
c = alphabet.charAt((int)(Math.random() * 25));
14
monitor.add(c);
15
System.out.println("ADD --> " + c);
16
try {
17
sleep((int)(Math.random() * sleepTime));
18
sleepTime = (int)(Math.random() * 1000);
19
} catch (InterruptedException e) {}
…………………..
Chap.12
42
12.2 쓰레드 응용
예제 : Consumer.java
1 class Consumer extends Thread {
2
private Monitor monitor;
3
private int sleepTime = 500;
4
5
public Consumer(Monitor s) {
6
monitor = s;
7
}
8
9
public void run() {
10
char c;
11
for (int i = 0; i < 20; i++) {
12
c = monitor.eat();
13
System.out.println("REMOVE <-- " + c);
14
try {
15
sleep((int)(Math.random() * sleepTime));
16
sleepTime = (int)(Math.random() * 1000);
17
}catch (InterruptedException e) {}
18
}
………………………………
Chap.12
43
12.2 쓰레드 응용

예제 : Monitor.java
1 class Monitor {
2
private char buffer[] = new char[6];
3
private int eatnext = 0;
4
private int addnext = 0;
5
private boolean isFull = false;
6
private boolean isEmpty = true;
7
8
public synchronized char eat() {
9
char toReturn;
10
while (isEmpty == true) {
11
try {
12
System.out.println("is empty.");
13
wait();
14
}catch (InterruptedException e) {}
15
}
16
toReturn = buffer[eatnext];
17
eatnext = (eatnext + 1) % 6;
18
if (eatnext == addnext ) {
19
isEmpty = true;
Chap.12
44
12.2 쓰레드 응용
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
}
isFull=false;
notify();
return(toReturn);
}
public synchronized void add(char c) {
while (isFull == true) {
try {
System.out.println("is full.");
wait();
}catch (InterruptedException e) {}
}
buffer[addnext] = c;
addnext = (addnext + 1) % 6;
if (addnext == eatnext) {
isFull = true;
}
isEmpty = false;
Chap.12
45
12.2 쓰레드 응용
39
40
notify();
}
41
42
public static void main(String args[]) {
43
Monitor m = new Monitor();
44
Producer p1 = new Producer(m);
45
Consumer c1 = new Consumer(m);
46
p1.start();
47
c1.start();
48
49 }
}
Chap.12
46
12.2 쓰레드 응용
Chap.12
47
12.2.4 파일에서 문자열 찾기: EzSearch

쓰레드를 이용해서 파일에서 원하는 문자열을 찾는 프로그램
예제이다.이것은 앞에서 다룬 생산자와 소비자 문제를 응용한
프로그램이다.

EzSearch에는 파일에서 원하는 문자열을 찾는 여러 개의 Search
쓰레드가 있고, IOC, EzDisplay 등의 클래스들이 있다.

Search 쓰레드들은 파일에서 찾은 내용을 IOC에 저장하고 EzDisplay는
IOC에 저장된 내용을 읽어간다. 즉, Search 쓰레드들은 생산자,
EzDisplay는 소비자이다.
12.2 쓰레드 응용

EzSearch의 생산자와 소비자
File
File
File
Search
IOC
putMessage( )
Message message
getMessage( )
............................
.............................
..................
.............
.................................
.........................
.....................
EzDisplay
Chap.12
48
12.2 쓰레드 응용

Chap.12
49
결과

EzSearch는 원하는 문자열을 원하는 파일들로부터 찾아서 프레임으로
결과를 보여준다.

C:\> java EzSearch String *.java