◎위챗 : speedseoul
http://scarlett.tistory.com/entry/Thread-4%EA%B0%84%EB%8B%A8%ED%95%9C-Thread-%EA%B2%80%EC%83%89-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8
http://scarlett.tistory.com/entry/Thread-2Thread%EC%9D%98-%EC%82%AC%EC%9A%A9%EB%B0%A9%EB%B2%95
이제 Thread를 활용하는 간단한 프로그램을 소개해 보겠습니다.
상당히 많은 정보를 모아놓은 데이터소스를 생각해 보겠습니다.
대표적으로는 정보를 체계적으로 모아 놓은 데이터베이스를 생각할 수 도 있구요.
또는 디렉토리 서버에 저장되어 있는 정보를 생각할 수도 있습니다.
혹은 인터넷에 있는 모든 웹서버의 내용을 데이터소스로 생각해도 되겠습니다.
그리고 이런 정보를 모아놓은 데이터소스가 존재한다고 가정하고 그 데이터소스에 접근이
가능하다고 가정합니다. 이런 상황에서 여러분이 인기가수 장나라에 대한 정보를
검색하는 프로그램을 만든다고 해보겠습니다.
논의를 간단히 하기 위해 데이터 소스를 유명 연예신문이 운영하는 웹서버의 홈페이지
내용으로 제한하도록 하겠습니다. 즉, 연예신문 웹서버의 홈페이지에 “장나라”에 대한 기사
가 있는지 검색하고 싶은 것입니다.
물론 검색한 결과를 될 수 있다면 빨리 알고 싶은 것이 당연한 일일 것입니다.
자, 이제 이런 가정아래 프로그램을 설계합니다.
첫째, 연예신문 웹서버의 내용을 찾아 검색하는 일을 담당하는 객체가 필요 합니다.
둘째는 연예신문 웹서버가 하나가 아니고 여러 개이기 때문에 한개의 Thread가
웹서버를 순서대로 검색하는 것보다 여러 개의 Thread가 동시에 검색을 수행하는 것이
좋을 것입니다. 가장 간명하게는 하나의 웹 서버를 하나의 Thread가 검색을 담당하도록
한다면 간단해 질 것 같네요.
프로그램 설계내용을 좀 자세히 살펴보도록 하겠습니다.
프로그램 설계에 따르면 “장나라”등의 검색하고 싶은 대상을 검색을 하는 일을 담당하는
객체가 필요한데 , 그 객체는 반드시 Runnable객체로 구현해야 됩니다.
왜냐하면 Thread와 함께 사용되어서 동시에 어떤일(검색)을 하고자 하기 때문입니다.
검색하는 일을 담당하는 객체가 Runnable객체가 아니면 Main Thread에서 순차적으로
검색을 수행할 수 밖에 없기 때문입니다. 검색을 담당하는 객체는 Runnable객체로
만들어 져야합니다. 즉, Thread와 함께 사용되어져야 한다면 대부분 Runnable객체를
준비해야만 합니다.
그 다음 검색을 담당할 객체 즉, Runnable객체는 약간의 정보를 가지고 시작해야만
합니다. 일단 어디를 검색해야 하는지 알려 주어야하고, 그 다음은 어떤 것을 검색해야
하는 것을 알려 주어야 합니다. 그리고 어떤 방법으로 검색해야 하는지 알려주어야 할 것
입니다.
어디를 검색해야하는 문제는 연예정보를 제공하는 웹 서버의 위치인 URL로 정하겠습니다.
자바에는 URL 객체가 있어서 이 URL 객체로부터 InputStream을 얻을 수 있습니다.
일단 InputStream을 얻었다는 것은 자바에서 지원하는 I/O 클래스를 이용해서 정보를 읽을
수 있다는 것입니다. 즉, URL객체와 InputStream을 이용하여 디스크에 있는 파일을
읽어내듯이 웹서버의 정보를 자연스럽게 읽어낼 수 있습니다.
어떤 것을 검색해야하는 문제는 Runnable객체의 생성자에 검색할 대상의 정보를 인자로
넘겨 주면 되겠지요. 간명하게 해결할 수 있습니다.
남은 문제는 어떤 방법으로 검색해야 하는지입니다.
여기서는 간명하게 웹서버의 홈페이지를 한줄씩 읽어가면서 그 읽어낸 정보중에 검색대상이 포함되어 있으면 정보를 찾은 것으로 하겠습니다. 정보를 찾은 다음에는 그만 빠져나오도록 하겠습니다. 즉 "장나라"를 검색하는데 웹 서버 홈페이지에 "장나라" 텍스트가
포함되어 있다면 정보를 찾은 것으로 한다는 뜻입니다.
Runnable 검색기능 객체
SearchTask는 검색의 기능을 담당하는 Runnable객체입니다. Runnable객체로 설계한 것은
SearchTask객체를 Thread와 함께 사용하겠다는 뜻을 처음부터 갖고 있는 겁니다.
import java.net.*;
import java.io.*;
/**
* URL로 지정되는곳에서 TARGET을 찾습니다.
*/
public class SearchTask implements Runnable {
private String url;
private String target;
/**
* String url : 검색할 URL
* String target : 검색할 대상
*/
public SearchTask(String url,String target) {
this.url = url;
this.target = target;
}
public void run() {
BufferedReader br = null;
try {
URL u = new URL(url);
br = new BufferedReader(new InputStreamReader(u.openStream()));
String s = "";
while((s = br.readLine()) != null) {
if (s.indexOf(target) != -1) {
System.out.println(url + " , " + target + " 찾았습니다.");
return;
}
}
System.out.println(url + " , " + target + " 없습니다.");
}
catch(Exception e ) {
e.printStackTrace();
}
finally {
try {
if (br != null)
br.close();
}
catch(Exception ignore) { }
}
}
}
예제 7 - 13 SearchTask.java
예제 7 - 13의 SearchTask는 "implements Runnable"을 보면 알 수 있듯이 Runnable
클래스입니다. SearchTask객체자체가 Thread와 함께 사용될 것은 전제로 하기 때문에
Runnable로 설계하는 것이 옳습니다.
SearchTask의 생성자를 살펴보면 두개의 인자를 가지는데 그 첫번째가 검색해야할
웹서버의 URL의 주소를 나타냅니다. 그 두번째는 검색해야할 대상을 의미합니다.
SearchTask는 가장 일반적인 Runnable객체의 사용법을 보여주고 있는데, 그것은
다름 아니라 Thread가 수행해야할 내용은 public void run()에 적어두고,
public void run()에서 필요한 정보는 Runnable객체의 생성자를 통해서 전달받는 것입니다.
여기서 SearchTask는 검색할 url 과 검색할 대상을 생성자로부터 전달 받고 있습니다.
public void run() 메소드를 살펴보면, 이부분은 Thread가 각각 수행을 하는 부분인데
SearchTask 생성자에서 전달받은 정보인 url의 String을 이용해서 실제 URL객체를 만들고,
URL객체의 openStream()메소드를 호출해서 InputStream을 얻어내고 있습니다.
여기서 만들어진 InputStream으로 읽기를 하면 웹 서버의 URL의 정보를 읽는 셈이됩니다.
SearchTask에는 InputStream으로부터 정보를 읽으려고 BufferedReader 레퍼런스를 이용
했는데, 이 레퍼런스를 이용한 이유는 BufferedReader클래스에서 정의해 놓은 readLine()
메소드를 이용하고 싶기 때문입니다.
즉, 웹서버 홈페이지로부터 정보를 한줄씩 읽고, 읽은 정보중에 검색대상이 있는지
확인하고 싶기 때문입니다. 이 이유로 readLine()메소드를 사용하고 싶습니다.
예제 7 - 13의 SearchTask는 try - catch - finally 구문을 아주 적절하게 사용하고 있습니다.
BufferedReader레퍼런스를 try 블록 바깥에 선언해서 finally구문에서 BufferedReader
레퍼런스를 이용해서 열어놓은 스트림을 반드시 닫고자 하기 때문입니다.
finally 구문은 정상적인 흐름이든, 예외가 발생하든 반드시 실행이 되는
부분이므로 시스템 자원을 정리하는 곳으로는 그야 말로 안성 맞춤이기 때문입니다.
필자는 사용하는 시스템자원은 반드시 try - catch - finally 구문을 통해서 필요하면
사용하고 , 필요없어지면 정리합니다. 이런 규칙엔 예외가 없습니다.
SearchTask는 약간의 트릭도 사용하는데, 사용하고 싶은 스트림은 InputStream이 아니라
Reader, 정확하게는 BufferedReader입니다.
하지만 URL객체로부터 openStream()메소드 호출로 얻을 수 있는 것은 InputStream이므로
InputStream을 우선 InputStreamReader를 이용해서 Reader로 변경해야 겠습니다.
그리고 해당 Reader를 BufferedReader로 덮어씌워서 결국에는 사용하고자하는 궁극적인
스트림 BufferedReader를 이용할 수 있게 되었습니다.
늘 생각하는 것이지만 , 자바에서의 I/O의 설계는 참 멋있다는 생각이 듭니다.
BufferedReader에는 readLine()이라는 메소드가 있고, 한 줄씩 읽는 메소드입니다.
만약 스트림을 모두 읽어내게되면 null을 돌려주게됩니다. 그때는 읽기를 그만 두게
되면 되겠지요. while문은 스트림으로부터 한줄씩 읽어내고, 만약 끝까지 다 읽었다면
(null 을 돌려주었다면) 읽기를 중단하고 while문을 빠져나라가는 뜻입니다.
SearchTask의 검색대상을 찾았는지 찾지 못했는지 알게되는 여부는 while 문속에 있는
String클래스에 있는 indexOf(String s) 메소드를 이용해서 구현했습니다.
String클래스의 indexOf(String s)메소드는 String s가 포함되어 있다면 그 s가 시작되는 첫번째 위치의 인덱스를 돌려주고, 만약 s가 포함되지 않았다면 –1을 돌려주게 됩니다.
웹서버의 홈페이지에서 한줄을 읽고 ,읽은 정보중에 target이 포함되어 있다면
(즉 –1이 아니라면) 정보를 찾았다고 알리고 (화면에 출력하고) 정보 찾기를 중단해라
(public void run()을 빠져나가라)는 뜻입니다. 실행중인 Thread를 중지시키는 가장 좋은
방법은 public void run()을 마치는 일이고, return은 아주 좋은 선택입니다.
메소드에서 return하게되면 해당 메소드를 종료하는 셈이니까요.
Basic Thread 프로그램
SearchScript는 Thread와 SearchTask Runnable을 이용해서 Concurent 검색을 수행하고
있습니다.
public class BasicConcurrentSearch {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("java -classpath CLASSPATH
BasicConcurrentSearch person");
System.exit(1);
}
String target = args[0];
Runnable r1 = new SearchTask("http://www.sportsseoul.com",target);
new Thread(r1).start();
Runnable r2 = new SearchTask("http://www.sportschosun.com",target);
new Thread(r2).start();
Runnable r3 = new SearchTask("http://www.stoo.com",target);
new Thread(r3).start();
Runnable r4 = new SearchTask("http://www.goodday.com",target);
new Thread(r4).start();
}
}
예제 7 - 14 BasicConcurrentSearch.java