java.util.Timer에 대한 Good Practice에 대한 내용으로 http://tech.puredanger.com 블로그에 있는 내용입니다.
Two good rules of thumb for the use of Timer are:

1. Always start your Timer as a daemon thread. By default, new Timer() and other constructors use a non-daemon thread which means that the Timer will stick around forever and prevent shutdown. That may be what you expect, but I doubt it.
2. Always name your Timer. That name is attached to the Timer background thread. That way, if you do something dumb, and look at a thread dump, it will be exceedingly obvious when you’ve started 500 FooBarCleanupThreads.
위의 내용을 요약하면, "가능한 데몬 쓰레드로 실행을 시키고 Timer에 이름을 줘서 쓰레드 덤프등을 통해서 분명하게 디버깅을 할 수 있도록 하자"로 파악을 하였습니다.

그리고, http://tech.puredanger.com 블로그에 있는 내용의 댓글로 Timer를 사용하지 말고, ScheduledExecutorService를 사용하는 것이 좋다고 하네요.. 이유는 Timer(싱글 쓰레드)가 가지는 원천적인 문제를 해결하기 위해서라고 하네요.. 원본 내용이 짧기 때문에 읽어보세요.

ScheduledExecutorService의 간단한 예제는 http://www.java2s.com/Code/Java/Threads/Java1550newfeatureThreadSchedule.htm 를 참고하세요.. ^^

java.util.Timer의 schedule vs scheduleAtFixedRate에 대한 내용도 참고해 보세요. ^^

sjava-logging 1.0 Level

from sjava project 2009/07/13 09:57
sjava-logging library는 level에 따른 로깅 가능여부를 체크하고 있지는 않습니다.
아래의 Level은 로깅파일의 분류 및 파일의 내용에 기입을 하기 위한 클래스입니다.
그리고, Level에 대한 사용을 위해서는 LevelFactory를 사용하면 됩니다.

LevelFactory.java
package net.sjava.logging;

import java.util.Map;
import java.util.HashMap;

/**
 *
 * @author mcsong@gmail.com
 * @since 2009. 7. 1.
 */
public class LevelFactory {   
    /** map of levels     */
    private Map<String, Level> levelMap;

    /** singleton instance */
    private static LevelFactory instance = new LevelFactory();
   
    /** constructor */
    private LevelFactory() {
        this.levelMap = new HashMap<String, Level>();
       
        this.levelMap.put("all", new Level(0, "all"));
        this.levelMap.put("fatal", new Level(1, "fatal"));
        this.levelMap.put("error", new Level(2, "error"));
        this.levelMap.put("warn", new Level(3, "warn"));
        this.levelMap.put("info", new Level(4, "info"));
        this.levelMap.put("debug", new Level(5, "debug"));
        this.levelMap.put("trace", new Level(6, "trace"));
        this.levelMap.put("system", new Level(7, "system"));
    }
   
    /**
     *
     * @return
     */
    public static LevelFactory getInstance() {
        return instance;
    }
   
    /**
     *
     * @param level
     * @return
     */
    public Level getLevel(int level) {
        switch (level) {
        case 1:
            return this.getLevel("fatal");
        case 2:
            return this.getLevel("error");
        case 3:
            return this.getLevel("warn");
        case 4:
            return this.getLevel("info");
        case 5:
            return this.getLevel("debug");
        case 6:
            return this.getLevel("trace");
        case 7:
            return this.getLevel("system");
           
        default:
            return this.getLevel("all");
        }
    }
   
    /**
     *
     * @param name
     * @return
     */
    public Level getLevel(String name) {
        if (name == null)
            return null;
       
        if(!this.levelMap.containsKey(name.toLowerCase()))
            return (Level)this.levelMap.get("all");
       
        return (Level)this.levelMap.get(name.toLowerCase());
    }
}

Level.java
package net.sjava.logging;

/**
 *
 * @author mcsong@gmail.com
 * @since 2009. 6. 19.
 */

public class Level {

    /** level number */
    public int level;
   
    /** level name */
    public String name;

    /**
     * Constructor
     *
     * @param level
     * @param name
     */
    public Level(int level, String name) {
        this.level = level;
        this.name = name;
    }
   
    /**
     * @return the level
     */
    public int getLevel() {
        return level;
    }

    /**
     * @param level the level to set
     */
    public void setLevel(int level) {
        this.level = level;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "name is " + this.name + ", level is " + this.level;
    }
}

'sjava project' 카테고리의 다른 글

sjava-config 프로젝트의 소스 및 문서 위치 변경합니다.  (0) 2009/11/10
sjava-config 1.3  (0) 2009/10/23
sjava-logging 1.0 Level  (0) 2009/07/13
sjava-logging 1.0  (0) 2009/07/09
sjava-config 1.2  (0) 2009/07/08

sjava-logging 1.0

from sjava project 2009/07/09 10:55
java logging library 입니다.
file에 write 하는 BufferedWriter의 성능을 높이기 위한 tip이 적용되어 있습니다.
config 처리는 sjava-config 코드를 복사해서 패키지 이름만 바꿨습니다. ^^;;
아래 예제는 설정파일과 sjava-logging-1.0.jar를 클래스 패스가 잡힌 위치에 복사를 하시고 돌리시면 됩니다.

예제
package net.sjava.logging.test;

import net.sjava.logging.Logger;

import java.util.Date;
import java.text.SimpleDateFormat;

public class LoggingTest {

    private static SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS");
   
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //load();

        // add shutdown hook
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                net.sjava.logging.util.BufferedWriterCacheUtility.shutdown();
            }
        });
       
        System.out.println("s - " + format.format(new Date()));
       
        for(int i=0; i < 1000000; i++) {
            Logger.getInstance().log("aaaaaaaaaaaaaaa");
            Logger.getInstance().log("aaaaaaaaaaaaccccccccccccccccccaaa");
           
            Logger.getInstance().log("metoo", "푸푸푸푸푸박.. ");
           
            Logger.getInstance().log("abcded", "abcde", "aaaa b aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
        }
   
        System.out.println("e - " + format.format(new Date()));
    }
}

설정파일
<?xml version="1.0" encoding="utf-8"?>
<sjava-logging>
    <!-- array variable delimeter is "," -->
    <!-- do not delete  -->
    <sjava-service name="config">
        <key name="watch" value="false" /> <!-- true, false -->
        <key name="period" value="60" /> <!-- 60 seconds -->
    </sjava-service>
   
    <!-- 로그서버 설정 -->
    <sjava-service name="logging">
        <key name="baseDir" value="d:\sjava-logging" />
        <key name="serviceDir" value="default" />
        <key name="fileName" value="default" />
        <key name="fileExt" value="log" />
        <key name="bufferSize" value="1024" />
        <!-- dayily(1), hourly(2), minutes(3) -->
        <key name="strategy" value="2" />
    </sjava-service>
   
</sjava-logging>

바이너리 & 소스

'sjava project' 카테고리의 다른 글

sjava-config 1.3  (0) 2009/10/23
sjava-logging 1.0 Level  (0) 2009/07/13
sjava-logging 1.0  (0) 2009/07/09
sjava-config 1.2  (0) 2009/07/08
sjava-config 1.1  (0) 2009/07/03

sjava-config 1.2

from sjava project 2009/07/08 17:10
테스트 코드의 변경이 있습니다.
기존코드에 영향이 없기 때문에 버전차이는 의미가 없을듯 합니다.
사용법은 http://www.sjava.net/121를 참고하시면 됩니다.

바이너리 & 소스


'sjava project' 카테고리의 다른 글

sjava-logging 1.0 Level  (0) 2009/07/13
sjava-logging 1.0  (0) 2009/07/09
sjava-config 1.2  (0) 2009/07/08
sjava-config 1.1  (0) 2009/07/03
sjava-config 1.0  (0) 2009/06/30
파일에 로그를 남긴다거나 데이타를 쓰기위해서 java.io.File 객체를 써도 되지만, 성능을 위해서 BufferedWriter를 많이 이용하게 됩니다. 특히 로그 라이브러리나 로깅 유틸리티 클래스에서 거의 대부분 사용할 것으로 생각이 드네요..
그래서, 생각한 방법이 BufferedWriter를 캐싱하는 것입니다.

아래 코드의 shutdown() 메쏘드는 ShudownHooking으로 등록된 쓰레드가 프로세스가 종료되면 호출을 해서 버퍼에 남아있는 내용을 기록하도록 되어 있습니다.

BufferedWriterCacheUtility.java
package net.sjava.io.test;

import java.util.Map;
import java.util.LinkedHashMap;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.util.Iterator;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BufferedWriterCacheUtility {
   
    /** max cache size */
    private static final int size = 10;
    /** lock */
    private static Lock lock = new ReentrantLock();
   
    /** initialized lru cache */
    private static Map<String, BufferedWriter> cache = new LinkedHashMap<String, BufferedWriter> (size, .75F, true) {
        private static final long serialVersionUID = 1L;
        protected boolean removeEldestEntry(Map.Entry<String, BufferedWriter> eldest) {
            if(size() > size) {
                try {
                    // flush and close before deleted
                    eldest.getValue().close();
                } catch(java.io.IOException e) {
                    // ignore
                }
            }
           
            return size() > size;  
        }
    };

   
    /**
     *
     * @param fileName
     * @return
     */
    public static BufferedWriter createBufferedWriter(String fileName) {
        lock.lock();
        try{
            if(cache.containsKey(fileName))
                return cache.get(fileName);
       
            return new BufferedWriter(new FileWriter(fileName, true), 1024);
        } catch(java.io.IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            lock.unlock();
        }
    }
   
    /**
     *
     * @param fileName
     * @param writer
     */
    public static void close(String fileName, BufferedWriter writer) {
        lock.lock();
        try {
            cache.put(fileName, writer);
        } finally {
            lock.unlock();
        }
    }
   
    /** shutdown hooking thread call */
    public static void shutdown() {
        // 아래의 코드를 통해서 종료가 되더라도 다 flush 하고 종료한다.     
        Iterator<BufferedWriter> iter = null;
        try {
            iter = cache.values().iterator();
            while (iter.hasNext()){
                iter.next().close();
                //iter.next().close();
            }
           
        }catch(java.io.IOException e) {
            e.printStackTrace();
        }
    }
}

아래 코드는 BufferedWriter를 사용하는 방법에 대한 3가지의 예입니다.
물론 2번째(bwriter02)를 사용하는 예가 가장 빠르겠지만, 3번째 예는 여러개의 fileName을 가지고 file write를 하기에 가장 좋은 방법으로 생각이 듭니다.

BufferedWriterTest.java
package net.sjava.io.test;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.text.SimpleDateFormat;
import java.util.Date;

public class BufferedWriterTest {

    private static SimpleDateFormat format = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss SSS");

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        //load();
               
        System.out.println("s - " + format.format(new Date()));
       
        BufferedWriter bwriter01 = null;
        try {
            for(int i=0; i < 100000; i++) {
                bwriter01 = new BufferedWriter(new FileWriter("c:\\test01.txt", true), 1024);
                bwriter01.write("aaaaaaaaaaaaccccccccccccccccccaaa");
                bwriter01.newLine();
                bwriter01.flush();
                bwriter01.close();
            }
        } catch(java.io.IOException e) {
            e.printStackTrace();
        }
        System.out.println("e - " + format.format(new Date()));
        
        System.out.println("s - " + format.format(new Date()));
        BufferedWriter bwriter02 = null;
        try {
            bwriter02 = new BufferedWriter(new FileWriter("c:\\test02.txt", true), 1024);
            for(int i=0; i < 100000; i++) {
                bwriter02.write("aaaaaaaaaaaaccccccccccccccccccaaa");
                bwriter02.newLine();
            }
            bwriter02.flush();
            bwriter02.close();
        } catch(java.io.IOException e) {
            e.printStackTrace();
        }
        System.out.println("e - " + format.format(new Date()));        
        
        // 날짜별 혹은 서비스별로 로깅을 해야 될경우
        System.out.println("s - " + format.format(new Date()));
        BufferedWriter bwriter03 = null;
        try {
            for(int i=0; i < 100000; i++) {
                bwriter03 = BufferedWriterCacheUtility.createBufferedWriter("c:\\test03.txt");   
                bwriter03.write("aaaaaaaaaaaaccccccccccccccccccaaa");
                bwriter03.newLine();
                BufferedWriterCacheUtility.close("c:\\test03.txt", bwriter03);
            }
        } catch(java.io.IOException e) {
            e.printStackTrace();
        }
       
        System.out.println("e - " + format.format(new Date()));
    }

}

여담이지만, java.io.BufferedWriter 소스를 보면, write시에 lock을 잡고 씁니다.
아래와 같은 코드를 많이 사용하지만요..
lock.lock();
try {
 bWriter.write("000000000000000");
}catch(java.io.IOException e) {
  e.printStackTrace();
} finally {
  lock.unlock();
}
위 코드보다는 아래코드를 사용하는게 맞겠습니다.
try {
 bWriter.write("000000000000000");
}catch(java.io.IOException e) {
  e.printStackTrace();
}

OOP에 대한 내용들..

from ooad 2009/07/06 10:30
OOP란 조건문(if)을 줄이는 것

정말 다형성(서브타입)이 IF를 줄일수 있을까?

객체지향 프로그래밍에 대한 오해와 진실 I

객체지향 프로그래밍에 대한 오해와 진실 II

'ooad' 카테고리의 다른 글

Specification Pattern  (2) 2009/09/16
디자인 패턴 그리고 그 이후..  (0) 2009/09/16
OOP에 대한 내용들..  (0) 2009/07/06
단순 Reactor 패턴의 성능향상 방안..  (0) 2009/06/25
DI(Dependency Injection)에 대해서..  (0) 2009/06/25
Tag // ooad, OOP

sjava-config 1.1

from sjava project 2009/07/03 14:17
config 파일을 모니터링하는 부분의 코드가 변경이 되었습니다.
기존의 net.sjava.config.util.Watcher 클래스를 Timer를 이용하는 TimerTask로 변경을 하였습니다.
사용법은 http://www.sjava.net/121를 참고하시면 됩니다.

바이너리

'sjava project' 카테고리의 다른 글

sjava-logging 1.0 Level  (0) 2009/07/13
sjava-logging 1.0  (0) 2009/07/09
sjava-config 1.2  (0) 2009/07/08
sjava-config 1.1  (0) 2009/07/03
sjava-config 1.0  (0) 2009/06/30
일정 시간이 지난뒤에 실행을 시켜주는 Utility 클래스로 Java에서는 Timer 클래스가 있습니다.
Timer 클래스를 사용하다 보니, 동일한 기능의 다른 메쏘드(schedule, scheduleAtFixedRate)가 있어서 차이점에 대해서 살펴보았습니다.

Java API에서는 Timer의 schedule과 scheduleAtFixedRate 메쏘드 둘다,
지정한 태스크가 지정한 지연의 후에 시작되어 「고정 빈도 실행」을 반복하도록 스케줄 합니다. 그 후는 지정한 기간과는 별도로 거의 일정한 간격으로 실행됩니다.

Java API의 schedule 메쏘드에 대한 내용은,
고정 지연 실행에서는 전의 실행의 실제의 실행 시간을 기준으로의해 각각의 실행이 스케줄 됩니다. 어떠한 이유로써 실행이 지연 했을 경우 (가비지 컬렉션, 그 외의 백그라운드 작업 등), 그 후의 실행도 지연 됩니다. 최종적으로 실행의 빈도는 보통, 지정한 기간의 대응하는 빈도보다 약간 늦어집니다 (기본이 되는 Object.wait(long)를 지지하고 있는 시스템 클록이 정확이라고 하는 전제로).

Java API의 scheduleAtFixedRate 메쏘드에 대한 내용은,
고정 빈도 실행에서는 최초의 실행의 스케줄 된 실행 시간을 기준으로의해 각각의 실행이 스케줄 됩니다. 어떠한 이유로써 실행이 지연 했을 경우 (가비지 컬렉션 또는 그 외의 백그라운드 작업 등), 「지연을 되찾는다」위해 2개 이상의 실행이 연속해 행해집니다. 최종적으로 실행의 빈도는 지정한 기간의 대응하는 빈도와 같게 됩니다 (기본이 되는 Object.wait(long)를 지지하고 있는 시스템 클록이 정확이라고 하는 전제로).

따라서, 위 내용을 토대로 schedule되는 job의 수행 횟수 또는 비교적 critical한 수행을 해야 되는 경우에는  scheduleAtFixedRate 메쏘드를 사용해야 될것 같습니다.

예제코드
TimerTest.java
import java.util.Timer;
import java.util.TimerTask;

public class TimerTest {

    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {
        Timer timer = new Timer("test");
        timer.schedule(new TimerTask() {
            public void run(){
                try {
                        Thread.sleep(2000);
                        System.out.println("- schedule job run at " + new java.util.Date());
                    } catch(Exception e) {
                        e.printStackTrace();
                    }
                }
            }, 0, 1000 * 1); // 10 초
       
       
        timer.scheduleAtFixedRate(new TimerTask() {
            public void run(){
                try {
                        Thread.sleep(2000);
                        System.out.println("- scheduleatfixedrate run at " + new java.util.Date());
                    } catch(Exception e) {
                        e.printStackTrace();
                    }
                }
            }, 0, 1000 * 1); // 10 초
    }
}

예제화면


그리고, Timer에 schedule된 Task가 중간에 exception이 발생하면, 그 Timer의 모든 task들이 실행이 안 되네요. ^^;;

* reference
- http://rijusnotes.blogspot.com/2009/06/how-to-implement-scheduler-in-java.html
- http://blog.naver.com/saojung50/120050451007