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