Thread Pool을 만들어 최적의 프로그램을 만들어 보자... |
들어가기 전에 이 내용과 소스는 오직 www.okjsp.pe.kr과 저자의 허락을 받은 곳에서만 사용할 수 있음을 알립니다. 만일 이 글을 읽기 힘드신 분은 첨부 파일에 이 글 내용과 똑같은 ThreadPool 강좌.txt 파일이 있습니다.. 이것을 통해 읽기 바랍니다. Thread Pool 사용하기.. Thread란 자원은 시스템의 리소스를 많이 차지하는 자원이다. 이번 컬럼에서는 Thread Pool에 대한 것이기에 Thread의 자세한 내용은 언급하지 않겠다.. 위에서 언급했듯이 Thread를 효율적으로 사용한다면 시스템에 많은 도움을 줄수 있다..
이러한 Thread Pool은 어떨때 쓰일까 하는 궁금증이 혹실 있는 사람을 위해서 대표적인 예로 우리가 지금 공부하는 서블릿 엔진들이 내부적으로는 Thread Pool을 쓸것이다. 참고로 많은 분들이 사용하시는 DB Pooling 또한 이와 유사한 것이라 생각하면 된다. 이러한 연결을 미리 여러개를 접속하여 필요할때 마다 사용하고 다 쓰면 반납하고 하여 효율적인 DBMS의 사용을 위한 것이 DB Pooling이라 할 수 있다.. 또 한가지 Pool을 썼을 경우 자기 방어적인 프로그램을 만들수 있다. Pool의 최대값은 이론적 근거와 충분한 테스트후 설정해 주면 안정적인 서비스를 제공할 수 있다. 필자가 예제를 만든것은 채팅을 위한 것이었지만 불필요한 군더더기는 모두 빼구 개념을 이해하기 위한 아주 간략한 부분만을 간추렸다. 아직 필자 또한 상당한 실력이 아니기에 콘크리트 코딩이 들어 간것을 양해 하기 바란다. 먼저 Thread Pool을 관리할 Manager를 만드는 것이다. 필요한 역활을 생각하면 설정값을 얻어 이에 따른 초기화 작업을 해 주어야 할것이다. 사용자의 요구에 따른 Thread 자원의 대여와 반납을 처리 해 주어야 한다. (필수) 위의 요구 사항을 보면 별것이 아닐것이다. 1개의 Thread 자원이 있을때 동시에 여러 대여 요청이 왔다.. 동기화 문제는 많은 추가적인 보살핌이 필요하기에 함부로 남발하게 되면 효율성에서 치명적인 결함은 생기게 하기 때문에 꼭 필요한 곳이 아니라면 사용하지 않아야 한다. 자.. 마지막으로 또 하나의 역활... 사용자의 요청을 무한 기다림으로 하면 안된다. 일정 시간동안 기다리다가 자원이 없으면 사용자에게 자원이 없음을 알려야 한다. 따라서 사용자에게 자원이 없음을 알리고 이에 따른 행동을 유도하게 하여야 한다. 이상으로 기본적인 Pool Manager의 역활이고 추가할것이 있다면 여러분께서 해보길 바란다. 이번에는 Pool Manager가 관리할 객체에 대한 것이 제공할 기본 사항을 생각해 보자... Pool Manager가 관리하기 위해서는 여러개의 관리용 메소드가 필요할 것이다. 필수적인 것으로 하나는 자기 자신의 생성을 위한 것이며 또하나는 자기 자신의 파괴를 위한 것이다. 부수적으로 효율적인 Thread 관리를 위해서 wait과 notify를 사용한 대기 상태와 running 상태를 할 것들이다. 이러한 것들은 최종 Thread에 따라 내용이 다른기 때문에 abstract class를 사용하여 만들었다. 이는 다음과 같이 선언만 하면 된다. package rushipel.util.threadpool; public abstract class ThreadObject { abstract public void create() throws NotCreateException; } // end class ThreadObject 이를 상속 받은 클래서는 저런것을 처리해 주어야 한다. 자 이번에는 Pool Manager의 내용을 보면서 설명을 해 보겠다.. package rushipel.util.threadpool; import java.io.*; /* public abstract class PoolManager { protected int initSize = 2; // 초기 Pool Size private static PoolManager manager = null; protected PoolManager() throws NotCreateException { readyPool = new java.util.Vector(); readProperties(); System.out.println("현재 전체 " + currentSize + "개 Pooling 되어졌으며 " + usedCount + "개 사용중입니다."); } // end private PoolManager() /** private void readProperties() { // Properties 파일을 얻어온다. InputStream is = getClass().getResourceAsStream("/pool.properties"); int tmpInitSize; try { initSize = tmpInitSize; System.out.println("초기화 : init_size = " + initSize + ", max_size = " + maxSize + ", waiting_time = " + waitingTime); } catch (Exception e) { //e.printStackTrace(); } // end try } // end private void readProperties() /** private void initPool() throws NotCreateException { // 초기값 만큼 풀을 생성한다. for (int i = 0; i < initSize; i++) { try { obj = createThreadObject(); } catch (NotCreateException e) {} // end try } // end for if (currentSize == 0) { } // end if (currentSize == 0) } // end private void initPool() throws NotCreateException /** protected abstract ThreadObject createThreadObject() throws NotCreateException; /** private void destroyAll() { System.out.println("전체 ThreadObject을 닫습니다."); ThreadObject obj; for (int i = 0; i < length; i++) { obj = (ThreadObject) pool.elementAt(i); } // end for } // end private destroyAll() /** public synchronized void reset() throws NotCreateException { System.out.println("전체 ThreadObject을 재 설정 합니다."); // 전체 ThreadObject을 닫는다. // 기본 풀들을 초기화 한다. // 새롭게 풀들을 설정한다. } // end public void reset() throws NotCreateException /** private ThreadObject getObject() throws NotCreateException { ThreadObject obj = null; if (readyPool.size() > 0) { } // end if (readyPool.size() > 0) else if (maxSize == 0 || currentSize < maxSize) { } // end else if (maxSize == 0 || currentSize < maxSize) if (obj != null) { usedCount++; } // end if (obj != null) return obj; } // end private ThreadObject getObject() throws NotCreateException /** public synchronized ThreadObject getThreadObject() throws NotCreateException { ThreadObject obj = null; while ((obj = getObject()) == null) { try { } catch (InterruptedException e) {} if ((System.currentTimeMillis() - startTime) >= waitingTime) { } // end if ((System.currentTimeMillis() - startTime) >= timeout) } // end while ((obj = getObject()) == null) System.out.println("현재 전체 " + currentSize + "개 Pooling 되어졌으며 " + usedCount + "개 사용중입니다."); return obj; } // end public synchronized ThreadObject getThreadObject() /** public synchronized void release(ThreadObject obj) { readyPool.add(obj); //notify(); System.out.println("반납 성공 : 현재 사용 개수 " + usedCount); } // end public synchronized void release(ThreadObject obj) } // end public abstract class PoolManager 생성자에서는 환경 설정 파일을 읽어서 설정 값들을 얻어오는 것이며 각종 객체의 생성과 초기화를 해주는 것이다. protected abstract ThreadObject createThreadObject() throws NotCreateException; 이부분 때문에 그렇다.. 왜 abstract로 했을까.... 실제 생성해야 할 Thread는 Extends ThreadObject implements Runnable를 한 것 일것이다. (거 말이상하네 '것 일것이다') 채팅을 처리하는 Thread Pool이 될수도 있고... 서블릿 엔진이 http 요청을 위한 Thread Pool일 수도 있고 FTP나 다양한 Pool로 사용될 수 있기에 이를 위해 필수 기능만을 코딩하고 변화로 될 수 있는 부분을 상속받아 처리하게 만든 것이다. 여기서 가장 중요한 것은 다음 2개 이다. public synchronized ThreadObject getThreadObject() 둘다 동기화 처리를 해준다. 더 중요하다면 역시 대여를 해주는 getThreadObject라 할 수 있다.. Source를 봐 보자... public synchronized ThreadObject getThreadObject() throws NotCreateException { ThreadObject obj = null; while ((obj = getObject()) == null) { try { } catch (InterruptedException e) {} tmpTime = System.currentTimeMillis(); if ((tmpTime - startTime) >= waitingTime) { } // end if ((tmpTime - startTime) >= waitingTime) time = tmpTime - startTime; } // end while ((obj = getObject()) == null) System.out.println("현재 전체 " + currentSize + "개 Pooling 되어졌으며 " + usedCount + "개 사용중입니다."); return obj; } // end public synchronized ThreadObject getThreadObject() 소스를 보면 while 문을 도는데 getObject()를 호출하여 null이 아닐때 까지 루프를 돈다... 좀더 좋은 방법은 예외를 발생시켜 예외 처리를 하면 될것이다. 이것두 여러분의 숙제로 내줄것이다. (정말 넘하네... 지가 좀 할것이지) 이것을 이해 하고 나머지 소스를 본다면 그리 어려울것이 없다... 자 이제 실제 이것을 상속받아서 구현한 것들을 보자... 이것에 살을 붙혀 나간다면 효율적인 채팅 프로그램을 만들수 있을 것이다. (다음번에 시간이 난 다면 채팅 프로그램에 대한 강좌를 열수 있으나 장담을 할 수 없다.. 왜냐면 피곤하니까.... ㅡ.ㅡ) 먼저 위에 2개의 abstract class들을 상속 받은 넘들을 봐보자... package rushipel.chat.server; import java.io.*; import rushipel.chat.common.*; public class ServerThread extends rushipel.util.threadpool.ThreadObject implements Runnable { private Socket mySocket; // 나의 소켓 // 생성자 ServerThread() { lockObject = new Object(); } // end ServerThread() /** public void setData(Socket socket) throws Exception { mySocket = socket; } // end public void setData(Socket socket) throw Exception /** public void create() throws rushipel.util.threadpool.NotCreateException { waitThread(); } // end public void create() throws rushipel.util.threadpool.NotCreateException /** public void waitThread() { try { lockObject.wait(); } catch(Exception e) {} } // end public void waitThread() /** public void notifyThread() { synchronized(lockObject) { } // end synchronized(lockObject) if (runner == null) { } // end if (runner == null) } // end public void notifyThread() /** public void destroy() { disconnect(); } // end public void destroy() /** public void start() { if (runner == null) { } // end if (runner == null) else { stop(); } // end else } // end public void start() /** public void stop() { if (runner != null) { isRun = false; // Thread가 종료할때까지 대기한다. try { runner.join(1000); } catch(Exception e) {} // stop을 안불러 줘도 괜찮지만 확인 사살이다. } // end public void stop() /** public void run() { TransObject aTransObject = null; while (isRun) { synchronized (lockObject) { try { aTransObject = (TransObject) reciver.readObject(); // 사용자의 명령이 무엇인지 알아 낸다. switch (protocol) { case 1000: send(1001, "접속 성공."); break; case 1040: break; } // end swtich (protocol) // 자신의 자원을 양보하여 동시 사용을 높인다. } catch(SocketException e) { } catch(IOException e) { } catch (Exception e) { } // end try } // end synchronized(lockObject) } // end while(true) } // end public void run() /** private void disconnect() { try { // 소켓을 닫고 쓰레드를 종료한다. } catch(Exception e) { } finally { try { // 순서에 주의 } catch (Exception e) {} } // end try } // end private void disconnect() /** public void send(int protocol, Object sendObject) { send( new TransObject(protocol, sendObject) ); } // end public void send(int protocol, Object sendObject) /** public void send(TransObject aTransObject) { try { sender.reset(); } catch(IOException e) { } // end try } // end public void send(TransObject aTransObject) } // end class ServerThread
그럼 이 넘은 ThreadObject의 abstract 메소드들을 어떻게 구현 했는지 알아보자... create()는 내부에서 단순히 waitThread()를 호출하였다. 다음은 waitThread()을 보면 lockObject.wait() 하였다.. lockObject는 효율적인 Thread의 사용을 위해 lock 객체를 만들고 이를 run() 메소드에서 동기화 처리를 하면서 무한 루프를 도는 것이다. 이번에는 notifyThread()인데 내부를 보니 lockObject.notify() 이것을 불러 Thread가 움직이게 한다. 위의 PoolManager에서는 notifyAll() 해주었다. PoolManager는 동시에 여러 요청을 처리하는 것이지만 이 ServerThread는 자기 자신만 책임 지면 되기 때문에 자기 자신만 깨우면 된다. 마지막으로 destroy()는 disconnect()를 불러 접속을 종료하고 stop()을 불러 Thread를 종료 시켜 주는 것이다. 다음 중요한 실제 수행중인 run() 메소드를 보자.. 중요한 것은 순서이다. 먼저 while이 오고 synchronized (lockObject) 그 다음이 try 이다..
그다음 lock을 걸구 풀기 위해 synchronized (lockObject)를 루프 전 범위에 걸어 주어야 한다. 만일 try 구문 밑에 걸어 놓으면 예외가 발생하면 synchronized 블럭을 벗어 날것이다. 이렇게 되면 이 Thread를 관리할 수가 없다... 이렇기 때문에 순서에 유의 하라는 것이었다.. 자 마지막으로 반납을 할 시기이다.. 반납은 접속 종료를 할때 반납한다. ChatThreadPoolManager.getInstance().release(this) 먼저 반납을 하고 자기 자신을 멈추는 것이다... 나머지 소스에 대해서는 살펴 보길 바란다.. 이를 응용하면 채팅 프로그램을 완료할 수 있다. 다음으로 PoolManager를 상속 받은 ChatThreadPoolManager를 보자... import rushipel.util.threadpool.*; public class ChatThreadPoolManager extends PoolManager { /** Singleton 기법을 위해 */ /** private ChatThreadPoolManager() throws NotCreateException { super(); } // end private ChatThreadPoolManager() throws NotCreateException /** public static synchronized ChatThreadPoolManager getInstance() throws NotCreateException { if (aChatThreadPoolManager == null) { aChatThreadPoolManager = new ChatThreadPoolManager(); } // end public static synchronized ChatThreadPoolManager getInstance() throws NotCreateException return aChatThreadPoolManager; } // end public static synchronized ChatThreadPoolManager getInstance() throws NotCreateException /** protected synchronized ThreadObject createThreadObject() throws NotCreateException { ThreadObject obj = new ServerThread(); return obj; } // end private ThreadObject createThreadObject() throws NotCreateException } 여기서 중요한 것은 createThreadObject() 이다... RunTime에 자신에 맞는 ThreadObject를 생성하는 것이다... 그 외에는 Singleton 기법을 사용하는 것에 유념하자... 여기까지 왔으면 한가지 의문점이 생길것이다.. 분명히 create() 하면 단지 waitThread()을 호출해 Thread를 대기 상태로 만드는데... 어디서 notifyThread()을 해 줄까.... ServerThread class를 보면 Socket을 통해 데이터를 주고 받는 것이다.. 그럼 이러한 Socket이 필요하다.. 즉 사용자의 접속이 있어야만 해당 Thread가 작업을 할 수 있을것이다. 따라서 사용자의 접속 요청이 있을경우 Pool Manager에서 Thread을 얻어 온 뒤 사용자의 Socket을 설정해 주면서 notifyThread() 할 것이다. 실제 서버의 진입 클래스인 ChatServer class를 봐 보자.. package rushipel.chat.server; import java.io.*; public class ChatServer implements Runnable { private ChatThreadPoolManager aChatThreadPoolManager; // ThreadPool을 관리하는 관리자 /** public ChatServer() { try { aChatThreadPoolManager = ChatThreadPoolManager.getInstance(); } catch (Exception e) { e.printStackTrace(); } // end try } // end public ChatServer() /** public void start() { if (runner == null) { runner = new Thread(this, "Chat Server"); } // end if (runner == null) else { stop(); } // end else } // end public void start() /** public void stop() { if (runner != null) { isRun = false; } // end if (runner != null) } // end public void stop() /** public void run() { ServerThread aServerThread = null; // 사용자를 처리하는 서버 Thread while (isRun == true) { try { aSocket = aServerSocket.accept(); System.out.println("User Accept"); if (aServerThread != null) { } // end if (aServerThread != null) else { sendFullMessage(aSocket); } // end else runner.yield(); } catch (Exception e) { e.printStackTrace(); } // end try } // end while (isRun == true) if (aServerSocket != null) { try { aServerSocket.close(); } catch (IOException e) {} } // end if (aServerSocket != null) } // end public void run() /** public void sendFullMessage(Socket socket) { ObjectOutputStream sender = null; try { sender = new ObjectOutputStream(socket.getOutputStream()); System.out.println("Server Busy"); } catch (Exception e) { } finally { try { if (sender != null) sender.close(); } catch (Exception e) {} } // end finally } // end public void sendFullMessage(Socket socket) public static void main(String[] arg) { ChatServer aChatServer = new ChatServer(); } // end public static void main(String[] arg) } // end class ChatServer 위의 소스에서 중요하게 볼 부분은 run() 메소드 이다.. aSocket = aServerSocket.accept(); if (aServerThread != null) { } // end if (aServerThread != null) else { sendFullMessage(aSocket); } // end else 먼저 사용자가 접속을 하면 Socket을 얻는다.. ThreadPool로 부터 Thread를 얻는다.. 이것으로 허접하지만 Thread Pool에 대한 강좌를 끝내기로 한다... 최고(Best)가 되기보다는 최선(better)을 다하고 싶은 .... |
'Jop Tip' 카테고리의 다른 글
jvm terminated 와 함께 eclipse가 비정상종료되는 에러!!! (0) | 2006.12.28 |
---|---|
http://www.javastudy.co.kr/api/index.html (1) | 2006.12.28 |
대량데이타 조회시 고려사항 (1) | 2006.12.16 |
쉘 프로그램의 종류 (0) | 2006.12.14 |
Websphere V3 and V4 Connection Pool 사용법 (0) | 2006.12.14 |