출처 : http://interwater.tistory.com/13

Java 하며서 이론적으로 자주 마주 치는 놈이 이것인데....
이건 간단히 말하면 db 락의 개념으로 보면 된다.
synchronized 이게 붙어 있는 매쏘드는 그 매쏘드의 작업이 끝나기 전에 다른 놈이 사용을 못하게 하는 것이지..

엇 자 그런데 만약에 synchronized 가 붙은 method 가 있는 클래스를 new 를 통해서 생성해서 각각 쓰면 이게 동기화가 될까?
----------------------------------
ex)
class A{
synchronized test(){
}
}
class B{
new A().test();
new A().test();
}
----------------------------------

결론은 아니다 이다.
즉, 위에 이놈들은 각각 object 가 틀린 놈이기 때문에 다른 놈으로 본다 이거지... 이걸 막기 위해서 최초 프로그램이 loading 될때 메모리에 적재 시키게 하는 static 를 넣어서 method 를 정의 하면 된다.

참고로
public synchronized void A() { ... } 는
public void A() { synchronized(this){ ... } } 과 똑같고,

public static synchronized void A() {  ... } 는
public static void A() { synchronized(this.getClass()){ ... } } 과 똑같다.
출처 : http://javafreak.tistory.com/210

처음 자바를 배울때 가장 난해한 개념이 Thread의 인터럽트였던걸로 기억한다.

"인터럽트를 건다"는 개념도 생소했거니와 그래서 어떤 영향을 미치는가? 를 도무지 알 길이 없었다. 왜냐하면 thread에 대한 이해도 일천했는데다가  자바 쓰레드에서 언급되는 "인터럽트"와 운영체제에서 배우던 인터럽트가 상호 교차되어 퓨전 떡볶이처럼 두리뭉실하게 머릿속에 자리잡았기 때문인 듯 하다.

운영체제를 배울때의 인터럽트를 간단하게 정리해보면 

1. cpu가 무슨 일을 열심히하고 있는데
2. 어디선가 인터럽트 신호가 들어온다.
3. cpu는 현재 하고 있는 job을 대강 정리하고 상태를 보존해놓는다.
4. 인터럽트 신호를 따라가서 인터럽트 처리 루틴을 실행해서 문제를 해결해주고
5. 아까 중간에 멈췄던 job의 상태를 복원해서 그 일을 마저 한다.

위와같은데 하던 일을 잠깐 멈추고 다른 일을 봐주고, 다시 돌아와서 아까 그 일을 마저 해주는 식이니 일종의 "새치기"라고 보면 된다.(사실 새치기로 번역한책도 있었다 -_-;)

하지만 자바 인터럽트는 의미가 약간 다른데 한쪽 thread가 다른 thread에게 신호만 보낼 뿐이다. 정상적인 경우라면(프로그램을 제대로 짰을 경우) 쓰레드에 인터럽트가 걸렸을 때 적절한 조치를 취하겠지만 프로그램 구조를 잘못 잡았을 경우에는 인터럽트 신호를 받은 쓰레드가 이 신호를 그냥 "씹어먹고" 지나쳐버릴 수가 있다.(대역죄인...)

예를 들어서 아래와 같은 코드를 보면 Main 쓰레드가 yielder 쓰레드에게 인터럽트 신호를 보냈으나 이를 전달받은 yielder 쓰레드가 보고도 모른척하고 그냥 지 하던 일을 계속 하는 모습을 볼 수 있다.
public class ThreaInterrupt implements Runnable{
    static private StringBuffer buf = new StringBuffer();
    static long start  = System.currentTimeMillis();
    static String lastMassage = "";
    
    public void run(){
        long end = System.currentTimeMillis();
        String message = "";
        while ( true ) {
            Thread.yield();
            
            if ( Thread.currentThread().isInterrupted())
                message = "Yielder : interrupted. ";
            
            if ( end - start > 1500){
                message += "Yielder : time over ";
                break;
            }
            else message += "Yielder : keep going!!! ";
            end = System.currentTimeMillis();
            print(message);
            message = "";
        }
        print(" : Yielder ends");
    }
    
    static void print(String msg){
        synchronized (buf) {
            if ( lastMassage.equals(msg)) return;        
            buf.append((System.currentTimeMillis() - start ) + " " + msg + "\n");
            lastMassage = msg;
        }
    }
    public static void main(String[] args) {
        // on the main thread stack
        
        Thread yielder = new Thread(new ThreaInterrupt());
        yielder.start(); // main thread makes yielder thread begin
        try {
            Thread.sleep(500);
            yielder.interrupt();
            print(">>>>>>>>>>>> first interrupt! ");
        } catch (InterruptedException e) {}
        
        try {
            Thread.sleep(1000);
            yielder.interrupt();
            print(">>>>>>>>>>>> second interrupt! ");
        } catch (InterruptedException e) {}
        print("Main Thread ends");
        
        try {    yielder.join(); } catch (InterruptedException e) {}
        
        System.out.println(buf.toString());
    }
}
0 Yielder : keep going!!! 
500 Yielder : interrupted. Yielder : keep going!!! 
500 >>>>>>>>>>>> first interrupt! 
500 Yielder : interrupted. Yielder : keep going!!! 
1500 >>>>>>>>>>>> second interrupt! 
1500 Yielder : interrupted. Yielder : keep going!!! 
1500 Main Thread ends
1500 Yielder : interrupted. Yielder : keep going!!! 
1501  : Yielder ends

메인쓰레드는 0.5초와 1초에 yielder 쓰레드에게 두차례나 인터럽트 신호를 보냈지만 이를 확인한 yielder 쓰레드는 이 신호를 적절히 처리하지 않고(무시하고) 자신에게 주어진 1.5초동안 계속해서 루프를 돌다가 끝나는 모습이다.

위에서 보여주려고 한 것은 yield와 같이 블로킹 되지 않는 메소드가 실행되는 와중에 인터럽트가 걸려 있음을 보여주려는 것이다. yield가 아니더라도 쓰레드가 블로킹 되지 않고 계속 실행 중인 상태에서는 인터럽트가 걸렸을때 반드시 루프 안에서

Thread.currentThread.isInterrupted();

메소드로 확인을 해야만 한다. "실행 중인 상태"를 강조하는 것은 Thread.sleep();이나 Obejct.wait(); 와 같은 블로킹 메소드를 실행해서 쓰레드가 BLOCK 상태(실행중이지 않은 상태)에 있을때에는 외부에서 인터럽트 신호가 들어오면 지체하지 않고 실행 상태로 전환되기 때문이다. 즉, 쓰레드가 실행중인 상태가 아니라면 인터럽트 신호가 전달될 때 반드시 "깨어나게" 된다.

그런데 실행중이지 않은 상태에서 인터럽트 신호가 들어와서 단잠에서 깨어나면 이때는 이미 인터럽트 신호가 해제된 상태이다.
public class ThreadInterrupt2 implements Runnable{

    public void run(){
        try {
            Thread.sleep(10* 1000);
        } catch ( InterruptedException e) {
            if ( Thread.currentThread().isInterrupted() )
                System.out.println("interruped state");
            else
                System.out.println("not interrupted");
        }
        System.out.println("sleeper finshed");
    }
    public static void main(String[] args) {
        Thread sleeper = new Thread(new ThreadInterrupt2());
        sleeper.start();
        
        try {
            Thread.sleep(2*1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        sleeper.interrupt(); // send interrupt signal to sleeper
        System.out.println("main finished");
    }
}
not interrupted
sleeper finshed
main finished

위에서 sleeper 쓰레드가 run() 메소드를 실행하면 10초간 BLOCK 상태로 들어가있게 된다. 즉 별다른 일이 없었다면 sleeper 쓰레드는 10초 후에 깨어나게 된다.

그러나 메인 쓰레드가 2초 후에 sleeper.interrupt(); 를 호출하고 이로 인해서 한참 잠을 자고 있던 sleepr 쓰레드는 BLOCK 상태에서 깨어나고 이 때 InterruptedException을 전달받으며 catch 절부터 다시 실행하게 된다.

하지만 이렇게 자다가 깨어났을때에는 위 코드에서 보듯이 인터럽트 신호가 해제된 상태이다. 반면에, yielder 쓰레드처럼 블로킹되지 않은, 실행중인 상태의 쓰레드가 인터럽트 신호를 받았을때에는 isInterrupted() 메소드로 확인을 하지 않는 신호가 왔는지도 모르고 계속 자기 할 일만 하게 된다.
실행중인 쓰레드는 인터럽트 신호를 받아도 본인이 확인하기 전까지는 이를 알지 못한다.
블로킹 상태의 쓰레드는 인터럽트 신호를 받으면 곧바로 깨어난다. 하지만 깨어났을때에는 이미 인터럽트 신호가 해제된 상태이다.
이렇게 쓰레드의 상태에 따라서 인터럽트 신호에 대한 반응이 다르기 때문에 run() 메소드 안에서는 이 두가지 상태에서 인터럽트 신호를 처리하는 로직을 제대로 갖추고 있어야 한다.

결국 쓰레드간에 주고받는 인터럽트는 단순히 신호를 보내서 "너 인터럽트 걸렸다"라고 말해주는 것 외에 아무런 기능이 없다. 한 쓰레드가 다른 쓰레드를 멈추게 하거나 종료하게 할 수 있는 수단은 전혀 없고 오로지 쓰레드 본인만이 이를 결정할 수 있다.

결국 남는 것은 쓰레드간에 주고받는 인터럽트 신호를 어떤 의미로 사용할 것인가? 하는 것이다.

일례로 큐에서 요청을 하나씩 가져다가 파일을 다운로드하는 기능을 멀티스레드로 구현할 경우, 파일 내려받기를 잠시 멈추거나 또는 아예 취소할 수 있어야 한다. 이럴 경우 FileDownloader 는 "시작전" "내려받기중" "잠시 정지" "취소중" "대기중" "종료중" 과 같은 다양한 상태를 왔다갔다하게 되고 이런 상태 변화는 외부에서 start, pause, resume, cancel 등 메소드 호출을 통해서 이루어진다.
public interface FileDownloader {
    /**
     * 다운로드 시작
     */

    public void start();
    /**
     * 일시 정지
     */

    public void pause();
    /**
     * 내려받기 재개함
     */

    public void resume();
    /**
     * 현재 내려받기를 취소
     */

    public void cancel();
    /**
     * 완전히 종료
     */

    public void exit();
}
FileDownloader 에 대한 참조를 통해 요청을 전달하는 외부 스레드들은는 인터페이스를 통해서 호출만 하고 원하는대로 수행되기를 기대한다. 인터럽트 신호를 걸거나 이 신호에 반응해서 작업을 잠시 중지할지, 이미 중지 상태라면 하던 일을 마저 계속 할지, 아니면 아예 취소해버리고 다른 작업을 기다릴지는 전적으로 FileDownloader 를 구현하는 클래스 내부에서만 이루어져야 한다.
public class DefaultFileDownloader implements FileDownloader, Runnable {
    Thread thisThread = null;
    private String INIT = "INIT";
    private String READY = "READY";
    private String RUNNING = "RUNNING";
    private String PAUSED = "PAUSED";
    private String EXIT = "EXIT";
    
    private String state =  INIT;
    
    DefaultFileDownloader(){    }
    
    @Override
    public void start() {
        if ( this.state != INIT){
            throw new IllegalStateException("already started");
        }
        // 직접 생성한 쓰레드에 대해서 인터럽트를 걸도록 한다.
        thisThread = new Thread(this);
        thisThread.start();
        this.state = READY;
    }
    
    @Override
    public void cancel() {
        if ( this.state != EXIT){
            throw new IllegalStateException("already cancelled");
        }
        this.state = EXIT;
        thisThread.interrupt(); // 인터럽트 신호를 설정해놓는다.
    }
        ..... // rest of implemetation
}
FileDownloader 인터페이스를 구현한 DefaultFileDownloader는 본인이 직접 쓰레드를 생성하고 관리하는 책임을 맡고 있다. 따라서 인터럽트 신호가 들어왔을 때 이를 어떻게 해석하고 어떤 행동을 할지는 DefaultFileDownloader 안에서 이루어져야 한다. 위에서 말했듯이 인터럽트 신호는 말 그대로 신호에 불과하기 때문에 이 신호를 전달 받고, 확인하고 여기에 반응하는 모든 행위들은 DefaultFileDownloader 안에서만 은밀하게(?) 관리되어야 한다.

만일 DefaultFileDownloader를 사용하는 스윙 애플리케이션에서 한참 내려받던 파일을 취소하려는데, 단순히 cancel() 메소드 호출 대신에 DefaultFileDownloader가 생성하고 시작시킨 thisThread 쓰레드에 대한 참조를 얻어서 직접 인터럽트 신호를 보낸다면(thisThread.interrupte()) 어떻게 될까?

캡슐화의 원칙에도 위배되지만 더 큰 문제는 DefaultFileDownloader 바깥의 다른 컴포넌트들은 "인터럽트 신호"가 thisThread에게 어떤 영항을 미치는지 알 수 없기 때문에(DefaultFileDownloader의 실제 구현 내용을 모르기 때문에) 의도하지 않은 결과를 가져올 수가 있다.

따라서 인터럽트 신호를 어떤 식으로 이용하고 있는지 가장 잘 알고 있는 DefaultFileDownloader만이 thisThread에게 인터럽트 신호를 보내고 이를 처리하도록 하는게 멀티쓰레드 환경에서 삑사리(?)를 방지하고 코드가 난잡해지는 것을 막을 수 있다.
출처 : http://javafreak.tistory.com/232

자바 언어가 1.x 에서 2.x 대를 넘나들 시절에 thread 를 다룰때 뻔질나게 자주 쓰였던 thread 메소드가 resume, suspend , stop 인데 아쉽게도 deprecated (앞으로 쓰지 말라는 뜻) 되어서 별 수 없이 쓰레드의 상태를 관리하는 방식으로 구현을 해야 한다.

구현은 아래와 같은 간단한 코드에서 시작한다.
public class ThreadHandle implements Runnable {
    @Override    public void run() {
        // TODO Auto-generated method stub
    }
}
Runnable을 구현한 것을 볼 수 있는데, 꼭 저렇게 할 필요는 없으나 Runnable을 implement 해주지 않으면 별도의 구현체를 클래스로 정의해야하기 때문에 쓰레드를 이용해서 단위 작업을 수행하는 ThreadHandle과 같은 클래스가 자체적으로 Runnable을 구현하면 편리하다.

이제 저 상태에서 start, resume, suspend, stop 메소드를 정의해준다.
public class ThreadHandle implements Runnable {
    public void start(){
        ;
    }
    public void resume() {
        ;
    }
    public void suspend() {
        ;
    }
    public void stop() {
        ;
    }
    public void join() {
        ;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
    }
}
ThreadHandle을 사용하는 쪽에서는 아래와같이 초기화하는데 쓰레드를 사용하는 것과 별 차이가 없어보이게 된다.
    public void test_thread_handle() throws Exception {
        ThreadHandle threadHandle = new ThreadHandle();
        threadHandle.start();
    }
그리고 start() 메소드가 제대로 동작하도록 하려면 아래와 같이 ThreadHandle이 thread를 생성하고 실행하게 만들어준다.
    private Thread thisThread ;
    private String threadName ;
    public ThreadHandle(){}
    public ThreadHandle(String threadName){
        this.threadName = threadName;
    }
    public void start(){
        thisThread = new Thread(this);
        if ( threadName != null) thisThread.setName(threadName);
        thisThread.start();
    }
ThreadHandle.start() 안에서 쓰레드 인스턴스를 생성하고 start()를 호출함으로써 새로운 쓰레드가 실행된다. threadHandle.start()를 호출이 <<main>> 쓰레드에서 실행되었다면 아래와 같이 새로운 쓰레드가 분기하게 된다.

두 개의 쓰레드가 함께 실행된다.

"t-0"라고 이름을 붙인 새로운 쓰레드는 run() 메소드를 시작으로 작업을 수행하는데 쓰레드에 일시정지, 재시작, 종료 기능을 추가하기 위해서 run() 메소드 안에서 반복문을 집어넣는다.
public class ThreadHandle implements Runnable {
    ....
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while ( true ){
            processTask();
        }
    }
}
반복문에 별다른 종료 조건을 주지 않았기 때문에 위와같은 상태에서는 정신없이 작업이 계속될 것이다. while 안에 적절한 종료 조건을 넣어서 적절한 시점에 쓰레드가 종료하게 해야 한다.

종료 조건은 두가지로 나눌 수 있다.

1. run() 메소드의 반복문 안에서 더이상 작업을 계속할 필요가 없어서 끝내는 경우
2. 외부에서 ThreadHandler.stop() 메소드를 호출했을 때.

1번은 쓰레드가 스스로 종료 조건을 판단하는 것이고 2번은 외부에서 강제로 종료시키는 경우에 해당한다.

1번의 전형적인 예로 소켓 연결을 통해서 데이터를 주고받은 뒤 연결을 끊는 경우를 들 수 있다.
    public void run() {
        while ( true ) {
            int nRead = in.read(buf, 0, buf.length);
            if ( nRead < 0 ) break;
            ....
        }
    }
더이상 읽을 데이터가 없다면 while 문을 끝내고 run() 메소드가 종료하면서 쓰레드도 생을 마감하게 된다.

2번처럼 외부에서 강제로 종료시키는 경우(정확하게 말하면 종료하라고 신호만 보낸다. 자바 스레드에서는 한 스레드가 다른 스레드를 강제로 종료시킬 방법이 없다.)가 바로 여기서 구현할 내용인데 다음과 같이 thread의 상태를 나타내는 static 변수를 정의하고 이를 참조하는 stateCode 프로퍼티를 도입해서 상태를 관리한다.
public class ThreadHandle implements Runnable {
    final private static int STATE_INIT = 0x1;
    final private static int STATE_STARTED = 0x1 << 1;
    final private static int STATE_SUSPENDED = 0x1 << 2;
    final private static int STATE_STOPPED = 0x1 << 3;

    private int stateCode = STATE_INIT;
    public void start(){
        if ( stateCode != STATE_INIT) // INIT 상태가 아니면 이미 실행되었다.
            throw new IllegalStateException("already started");
        
        thisThread = new Thread(this);
        if ( threadName != null) thisThread.setName(threadName);
        thisThread.start();
        stateCode = STATE_STARTED;
    }
    ....
    public void stop() {
        if ( stateCode == STATE_STOPPED) // 이미 멈췄다면 또 호출할 필요가 없음.
            throw new IllegalStateException("already stopped");
        thisThread.interrupt();
        stateCode = STATE_STOPPED ;
    }
    ....
}
STATE_INIT은 쓰레드 인스턴스가 생성되었지만 실행되지는 않은 상태를 의미하고, STATE_STARTED 는 Thread의 start() 메소드가 호출되어서 실행된 상태를 의미한다. 마찬가지로 STATE_STOPPED 는 쓰레드가 끝났거나 끝나는 중임을(while 루프를 빠져나와서 run() 메소드가 끝나가는 단계) 나타낸다.

각 상태는 다음 상태의 조건이 되는데, start() 메소드에서 현재 상태를 확인해서 STATE_INIT이 아니라면 이미 시작되었다는 뜻이므로 예외를 던지게 한다. 마찬가지로 stop() 메소드에서도 상태가 STATE_STOPPED 라면 이미 종료가 되었거나 종료 중이므로 예외를 던진다.

위 구현은 멀티쓰레드 환경에서는 오작동을 할 가능성이 있는데, 하나의 ThreadHandle 인스턴스를 여러개의 스레드들이 공유하는 상황이라면 스레드들이 동시에 start(), stop() 메소드를 호출할 때 stateCode 프로퍼티의 일관성이 깨질 수가 있다.

스레드 A와 스레드 B가 동시에 start()를 호출할 때 A가 현재 stateCode가 STATE_INIT임을 보고 if 조건절을 무사히 통과해서 thisThread.start();를 호출할 것이다. 그리고 스레드 A가 stateCode를 STATE_STARTED로 갱신하기 전에 스레드 B가 if 조건절에 진입하면 stateCode가 STATE_INIT 이기 때문에 무사히 통과해서 또다시 스레드 인스턴스를 생성하고 실행시키게 된다.

스레드 A가 stateCode를 갱신한 후에 스레드 B가 if 조건절에 진입하더라도 stateCode가 volatile이 아니기 때문에 스레드 B는 스레드 A가 갱신한 최신의 stateCode 값을 보지 못할 수도 있다.

따라서 ThreadHandle을 멀티쓰레드에서 안전하게 하려면 아래와 같이 critical section을 잘 막아줘야 한다.
    public void start(){
        synchronized ( this ){
            if ( stateCode != STATE_INIT)
                throw new IllegalStateException("already started");
            
            thisThread = new Thread(this);
            if ( threadName != null) thisThread.setName(threadName);
            thisThread.start();
            stateCode = STATE_STARTED;
        }
    }
    ....
    public void stop() {
        synchronized ( this ){
            if ( stateCode == STATE_STOPPED)
                throw new IllegalStateException("already stopped");
            this.stateCode = STATE_STOPPED;
            thisThread.interrupt();
        }
    }
suspend(), resume() 도 위와같이 구현하면 오직 하나의 스레드만이 threadHandle의 상태를 변경할 수 있고 상태를 변경하는동안 다른 스레드들이 진입해서 일관성이 깨지는 것을 막을 수 있다.

stop() 메소드가 호출된 후 상태가 변경되었으므로(stateCode가 변경되었으므로) run() 메소드의 while 문 안에서 이 변경을 확인해서 반복문을 빠져나오는 코드를 삽입한다.
    @Override
    public void run() {
        while ( true ){            
            if ( stateCode == STATE_STOPPED)
                break//종료하라는 신호이므로 루프를 끝낸다.
            processComplexJob();
        }
    }
위에서는 processComplexJob(); 메소드를 실행하기 전에 상태를 확인했는데, 수행할 작업을 끝내는데 꽤 많은 시간이 걸린다면 그 작업을 시작하기 전에 확인해주면 응답성을 높일 수가 있다. 

하지만 if 조건절을 판단한 직후에(종료조건이 아님을 확인한 직후에) 다른 스레드가 stop() 을 호출해서 스레드의 상태를 STATE_STOPPED로 바꾸주었다면 간발의 차이로 오랜 시간이 걸리는 작업은 실행될 수 밖에 없다.

이럴 경우 processComplexJob() 메소드 안에서 별도로 stateCode를 다시 한 번 확인하지 않는 한, 일단 시작된 작업이 끝나고 나서 while문을 한 번 반복한 후 다시 if 조건절에 도착해서 STAE_STOPPED 값을 보고 루프를 빠져나올 것이다.

suspend()를 구현한 코드는 아래와 같은데 stateCode 값을 변경하기 전에 현재의 상태를 미리 확인하는 과정을 거친다.
    public void suspend() {
        synchronized ( this ){
            if ( stateCode == STATE_SUSPENDED) return;
            if ( stateCode == STATE_INIT )
                throw new IllegalStateException("not started yet");
            if ( stateCode == STATE_STOPPED)
                throw new IllegalStateException("already stopped");
            stateCode = STATE_SUSPENDED;
        }    
    }
현재의 상태가 STATE_STARTED 가 아니라면 일시 정지가 별 의미가 없으므로 위처럼 모든 상태를 체크해서 적절히 예외를 던지거나 더이상 진행하지 말아야 하는데, 모든 상태를 체크하다보니 실제 상태를 변경하는 코드보다 조건을 검증하는 코드(if 구문들)가 더 많아진다. 

여기서는 STARTED, SUSPENDED, STOPPED 의 조건만을 판단하기 때문에 코딩량을 감당할 수 있으나 구현 조건에 따라서 정지중, 정지, 종료중, 종료, 재시작중, 재시작처럼 쓰레드가 거치는 상태가 많다면state pattern을 도입하는 것을 고려해볼만 하다. 

일시 정지 기능을 구현했으니 stateCode의 변경을 보고 스레드를 잠시 정지하는 기능을 run 메소드의 while 반복문 안에 삽입해준다.
    public void run() {
        while ( true ){
            // 상태 코드가 일시 정지라면 while문에서 계속 대기하도록 한다.
            while ( stateCode == STATE_SUSPENDED){
                try {
                    System.out.println("[handle] suspending...");
                    Thread.sleep(24 * 60 * 60 * 1000);
                } catch (InterruptedException e) {
                    if ( stateCode != STATE_SUSPENDED){
                        System.out.println("[handle] resuming...");
                        break;
                    }
                }
            }
            if ( stateCode == STATE_STOPPED){
                System.out.println("[handle] stopping...");
                break;
            }
            processComplexJob();
        }
여기서는 아주 오랜시간동안 쓰레드를 재우는 식으로 구현했는데 저렇게 오랫동안 스레드를 정지시켰을때에는 반드시 누군가가 thisThread.interrupt(); 를 호출해주어야 한다. 따라서 suspend()에서 잠재운 스레드를 다시 실행시키는 resume() 메소드에서는 반드시 thisThread.interrupt(); 를 호출해 준다.
    public void resume() {
        synchronized ( this ){
            if ( stateCode == STATE_STARTED || stateCode == STATE_INIT) return;
            if ( stateCode == STATE_STOPPED)
                throw new IllegalStateException("already stopped");
            stateCode = STATE_STARTED;
            thisThread.interrupt(); // 꼭 해줘야 한다.
        }
    }
stop() 메소드 구현에서도 interrupte() 를 호출하는데, stop() 메소드는 이미 멈춘 상태가 아니라면 언제든 호출될 수 있기 때문에 resume()과 마찬가지로 상태를 변경한 후에 스레드를 깨워주어야 한다. 

마지막으로 stateCode 를 volatile 로 바꿔주어서 run() 메소드를 실행중인 스레드가 공유 변수의 복사본만 바라보느라 외부 스레드가 갱신한 최신의 stateCode 의 값을 놓치는 일이 없게 해준다.
public class ThreadHandle implements Runnable {
    ....
    private volatile int stateCode = STATE_INIT;
이렇게 대강 start, resume, suspend, stop 기능을 구현했는데, 이런 메소드가 호출될때마다 ThreadHandle 내에서 관리하는 스레드는 아래와 같이 상태 전이가 반복된다.
스레드의 상태 전이를 관리할 때 위와같이 state diagram을 그려놓으면 구현할 때 도움이 많이 된다.  현재 상태에 따라서 호출할 수 있는 메소드의 종류가 나뉘는 것을 한눈에 알 수 있기 때문에 상태 관리 및 전이를 코드로 옮기기에 용이하다.

어떤 코드들을 보면 스레드에 걸리는 인터럽트 신호를 상태 전이의 조건으로 사용하는 경우도 있다. 

위에서는 stop()이 호출된 후 run() 메소드의 while 문 안에서 STATE_STOPPED 값을 확인하고 break; 하도록 했는데 아래처럼 while문의 조건절에서 인터럽트 신호 여부를 종료 조건으로 판단할 수도 있다.
    while ( ! thisThread.isInterrupted() ){
        .....
    }
이럴 경우 스레드 내에서 함부로 인터럽트 신호를 먹어버리지 않도록 유의해야한다. ( 관련글 : [Java] 자바 Thread의 interrupt 이해하기 ) 실행 상태가 아닌 BLOCK 상태에서 인터럽트를 걸면 스레드가 깨어나면서 인터럽트 신호가 해제되므로 catch 절에서 다시 한 번 인터럽트를 걸어서 인터럽스 신호를 복구시켜야 한다.
    try {
        Thread.sleep( sleepTime );
    catch (InteruptedException e){
        thisThread.interrupt(); // 다시 한 번 신호를 걸어준다.
    }
인터럽트 신호를 상태 전이의 힌트로 사용하는 방식도 나쁘진 않지만 스레드가 java nio 를 이용해서 스트립 입출력을 다룬다면 스레드에 인터럽트를 걸 때 신중해야 한다.

NIO 이전의 입출력 메소드들은 읽기, 쓰기 도중에 BLOCK 상태에 있으면 인터럽트에도 반응하지 않았지만 NIO 에서는 읽고 쓰는 도중에 BLOCK 상태에 있을때 인터럽트를 걸면 ClosedByInterruptException 예외가 던져지면서 스트림이 닫혀버린다. ( BLOCK 상태가 아니더라도 현재 스레드에 인터럽트가 걸려있는걸 확인하면 NIO 관련 스트림 클래스들은 스트림을 닫아버린다.)

위의 구현에는 해당되지 않으나 단지 스레드를 깨울 목적으로 인터럽트를 걸었는데 NIO 의 read() 에서 여전히 살아있는 인터럽트 신호를 보고 스트림을 닫아버리는 일이 발생한다. 이럴 경우 NIO의 read, write 실행 직전에 반드시 인터럽트 신호를 해제해줘야하고 read, write 실행 중에 인터럽트가 걸리지 않게 해줘야 하는데 이렇게되면 "입출력중" 이라는 별도의 상태를 정의해야 할 수도 있다. ( 복잡도 증가 )


public class ThreadHandle implements Runnable {

    private Thread thisThread ;

    private String threadName ;


    

    final private static int STATE_INIT = 0x1;

    final private static int STATE_STARTED = 0x1 << 1;

    final private static int STATE_SUSPENDED = 0x1 << 2;

    final private static int STATE_STOPPED = 0x1 << 3;


    private volatile int stateCode = STATE_INIT;

        

    public ThreadHandle(){ }

    

    public ThreadHandle(String threadName){

        this.threadName = threadName;

    }

    public void start(){

        synchronized ( this ){

            if ( stateCode != STATE_INIT)

                throw new IllegalStateException("already started");

            

            thisThread = new Thread(this);

            if ( threadName != null) thisThread.setName(threadName);

            thisThread.start();

            stateCode = STATE_STARTED;

        }

    }


    public void stop() {

        synchronized ( this ){

            if ( stateCode == STATE_STOPPED)

                throw new IllegalStateException("already stopped");

            this.stateCode = STATE_STOPPED;

            thisThread.interrupt();

        }

    }

    public void resume() {

        synchronized ( this ){

            if ( stateCode == STATE_STARTED || stateCode == STATE_INIT) return;

            if ( stateCode == STATE_STOPPED)

                throw new IllegalStateException("already stopped");

            stateCode = STATE_STARTED;

            thisThread.interrupt(); // 꼭 해줘야 한다.

        }

    }

    public void suspend() {

        synchronized ( this ){

            if ( stateCode == STATE_SUSPENDED) return;

            if ( stateCode == STATE_INIT )

                throw new IllegalStateException("not started yet");

            if ( stateCode == STATE_STOPPED)

                throw new IllegalStateException("already stopped");

            stateCode = STATE_SUSPENDED;

        }    

    }


    public void run() {

        while ( true ){

            // 상태 코드가 일시 정지라면 while문에서 계속 대기하도록 한다.

            while ( stateCode == STATE_SUSPENDED){

                try {

                    System.out.println("[handle] suspending...");

                    Thread.sleep(24 * 60 * 60 * 1000);

                } catch (InterruptedException e) {

                    if ( stateCode != STATE_SUSPENDED){

                        System.out.println("[handle] resuming...");

                        break;

                    }

                }

            }

            if ( stateCode == STATE_STOPPED){

                System.out.println("[handle] stopping...");

                break;

            }

            processComplexJob();

        }

}



public void test_thread_handle() throws Exception {

    ThreadHandle threadHandle = new ThreadHandle();

    threadHandle.start();

}


public : 공공의, 공중의, 공적인, 공립의, 공공연한
But first I had to go through a buttload of pain using JNI, which I 
THOUGHT would be the best bet. 

Then I realized where the solution lies. The first app that uses this update
utility I'm writing will be a program that presents a popup menu in
the Windows system tray and allows you to open URLs and documents and launch
programs from it. Well, the main app calls RUNDLL32.EXE, a Windows program 
that lets you call DLL functions from the command line or a batch file, and
uses RUNDLL32 to call ShellExec.

ShellExec is a Windows library call that launches an app. Or you can pass it a
URL or the path to a document, and if a default app or handler is registered
for the type of URL or document you're opening, it'll automatically open it
(for example, http:// URLs would, by default, open in the default web 
browser, and .doc files open in Microsoft Word by default).

ShellExec creates a new process where the new app will run, and exits 
immediately. It's perfect!

public void launch(String path) {
String commandLine = "rundll32 shell32.dll,ShellExec_RunDLL " + path;
String[] args = commandLine.split(" ");
ProcessBuilder pb = new ProcessBuilder(args);
try {
pb.start();
} catch (Exception exc) {
JOptionPane.showMessageDialog(null, exc.getMessage(),
"Can't launch file/program", JOptionPane.ERROR_MESSAGE);
}
}

A call to launch() followed by a System.exit(0) works EXACTLY the way I 
want it to work.

For Linux/FreeBSD/OS X, what I'll probably do is write a small C program
that forks a new process and uses execl, and call it this way. I can write
that program in such a way that it will be uber-portable.

Thanks to everyone for the suggestions.

1. 객체를 사용하고 나서 null 할당하는건 코드만 더럽히는 짓이다 vs 꼭꼭 null 처리해주는 좋은습관!
2. null 을 할당하는것은 별 의미없다 vs 이건 정말 중요하다
3. null 을 할당한다면 언제 할당해야 하느냐?

이런것들은 자바를 사용하는 개발자들 사이에서 오랫동안 논란이 되어왔고 아직도 논란이 되고 있는 부분이지요. 사실상 이 논란은 자바라는 언어에서 프로그래머가 직접 메모리를 할당/관리 하지 않기때문에 발생하는 것입니다. 하지만 GC 의 원리를 이해하게되면 null 할당이 편집증걸린 프로그래머나 할만한 일은 아니라는것을 알게됩니다. 그리고, 자바라는 언어가 이런 부분들을 해소하기 위해 어떤 노력을 했는지도 알수 있지요. (뒤에 null 할당이 아닌 다른 방법으로 동일한 효과를 얻는 예제를 소개 하겠습니다.) 

위의 3가지 논란에 대해 정리하기에 앞서 한가지 모두가 인정할만한 부분을 짚고 넘어가겠습니다. 

작은 프로그램(혹은 메서드)에서는 null 할당은 큰 의미가 없습니다. 여기서 작은 프로그램이란 
  • 프로그램(혹은 메서드) 자체의 동작시간이 짧고
  • 객체의 생성이 빈번하지 않으며
  • 적은 메모리를 사용하는 
  • 메모리 누수가 문제되지 않는
  • 실행시 지정한(혹은 기본값) 힙사이즈를 채 반도 사용하지 않는
프로그램(메서드)으로 정의해 보겠습니다. (좀더 구체적으로 정의할 방법도 있겠습니다만 당장 떠오르는게 저것밖에 없네요. ^-^;;) 사실 공부하려고 짜보는 프로그램이 아닌 어느정도 규모있는 프로그램이라면 대부분 위의 가지에 정반대되는 경우일 겁니다.  제가 계속 프로그램이라는 단어에 괄호치고 메서드라고 넣는 이유는 프로그램 전체가 위의 항목에 해당 될수도 있지만 어느 클래스의 특정 메서드가 위의 항목에 해당되는 경우도 있기 때문입니다. 이를 테면 작은 프로그램인데 몇개의 클래스에 정의된 특정 메서드들이 프로그램의 실행시간의 많은 부분을 차지한다던가 그 메서드에서만 메모리를 많이 사용한다던가 라는 상황도 있기 때문입니다. 

(프로그램(메서드)의 규모는 개발자마다 체감되는게 다르기 때문에 작다 크다 정의내리면 논란이 되는 경우가 많은데 일단 얘기를 이어나가야 하니 넘어갑시다.)

맨위에서 언급한 논란의 각각에 작은 프로그램(메서드)의 정의를 반대로 적용해 보시면 도움이 될겁니다. 정의를 반대로 적용봤을때 해당한다면 고민하지 마시고 아래의 방법을 추천드립니다.

지금 작성하고 있는 코드가 큰 프로그램(메서드)의 일부라면 항상 가능한한 모든 객체의 사용후 null 을 할당해 주어야 하며, 이것은 정말 중요한 부분입니다.

왜 위와 같이 해야하는지 몇가지 예제을 통해 살펴보도록 하겠습니다. 직접 붙여넣고 실행 해보시기 바랍니다. (실행시 VM 옵션으로 -verbose:gc 를 추가하셔서 실제 GC 가 일어나는 것을 확인해 보세요.)

첫번째 예제 : null 을 안주면 어떻게 되나?

public class GCTest {
private final static int ALLOC_SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.60);

public void allocate() {

System.out.println("Before first allocation");
byte[] b = new byte[ALLOC_SIZE];
System.out.println("After first allocation");

System.out.println("Before second allocation");
byte[] b2 = new byte[ALLOC_SIZE];
System.out.println("After second allocation");

}

public static void main(String[] args) {
new GCTest().allocate();
}
}

먼저 예제를 설명 드리자면 가용한 메모리의 60% 를 allocate 라는 메서드의 첫번째 지역 변수인 b 에서 할당을 합니다. 그리고 이후에는 전혀 사용되지 않고 있지요. 그리고 나서 다시 두번째 지역변수인 b2 에서 또 60% 를 할당을 합니다. 

위의 예제의 결과는 아래 와 같습니다.

Before first allocation
After first allocation
Before second allocation
[GC 555205K->555136K(712576K), 0.0017096 secs]
[GC 555136K->555104K(712576K), 0.0045040 secs]
[Full GC 555104K->555088K(660160K), 0.0052565 secs]
[GC 555088K->555088K(712576K), 0.0071493 secs]
[Full GC 555088K->555066K(665280K), 0.0062704 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at GCTest.allocate(GCTest.java:12)
at GCTest.main(GCTest.java:18)

첫번째 할당은 정상적으로 이뤄졌지만 두번째 할당을 진행하려고 보니 메모리가 부족한 상황이라 마이너 GC 가 발생하고 연달아 Full GC 까지 이뤄 졌습니다. 하지만 메모리는 여전히 부족합니다. 왜냐면 메서드가 반환되기 이전에는 b 의 참조가 존재하므로 (설사 사용되지 않는다 하더라도) GC 대상에서 제외되었기 때문입니다. 누가봐도 당연히 OOM (Out of memory) 가 나는 상황입니다. 또한 누가봐도 b2 를 할당하는 시점에 GC 가 일어 난다고 예상이 됩니다. (메모리가 부족한 상황에서 GC 는 반드시 일어납니다.

하지만 이 예제는 조건을 극단적으로 단순화 해놨을 뿐이지 규모가 큰 프로그램(메서드) 에서는 언제든 발생할 수 있는 상황입니다. 예를 들어 여러 스레드가 동작하며 각각 어떤 메서드를 실행을 하는데 그 메서드가 이런 구조라면

메서드 호출 -> 객체 잔뜩 생성 -> 객체 마구 사용(이후에는 사용안함) -> 블럭되는 I/O 처리 -> 반환

메서드가 종료되기 전까지 참조를 물고있는 사용되지 않는 지역변수들이 점점 쌓여 갈겁니다. 결국 메모리가 부족해 GC 가 수행되더라도 GC 의 대상이 되어야할 객체들은 참조를 물고 있으니 OOM(버틸수가없다) 으로 직행하는 거지요.

두번째 예제 : null 을 주면 어떻게 되나?

public class GCTest2 {
private final static int ALLOC_SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.60);

public void allocate() {

System.out.println("Before first allocation");
byte[] b = new byte[ALLOC_SIZE];
System.out.println("After first allocation");
b = null;
System.out.println("After assign null to b");
System.out.println("Before second allocation");
byte[] b2 = new byte[ALLOC_SIZE];
System.out.println("After second allocation");
}
public static void main(String[] args) {
new GCTest2().allocate();
}
}

첫번째 예제와 상황은 동일합니다. 하지만 b 를 다쓰고 나서 null 할당 해주었지요.
결과는 이렇습니다.

Before first allocation
After first allocation
After assign null to b
Before second allocation
[GC 555205K->555168K(712576K), 0.0019313 secs]
[GC 555168K->555104K(712576K), 0.0027482 secs]
[Full GC 555104K->208K(105344K), 0.0509913 secs]
After second allocation

예상하셨다시피 OOM 은 일어나지 않았습니다. 앞서 언급했던 

메서드 호출 -> 객체 잔뜩 생성 -> 객체 마구 사용(이후에는 사용안함) -> 블럭되는 I/O 처리 -> 반환

이런 구조의 메서드를 

메서드 호출 -> 객체 잔뜩 생성 -> 객체 마구 사용(이후에는 사용안함) -> 마구사용한 객체 가차없이 null 할당 -> 블럭되는 I/O 처리 -> 반환

이렇식으로 수정한 것과 동일합니다. OOM 으로 직행하려다가 현명한 개발자 덕분에 GC 가 보람있는 일을 했네요. 일례로 많은 사용자를 대상으로 하는 웹어플리케이션에는 아주 딱 들어맞는 상황입니다. 특히. 웹이라는 특성상 서비스의 유형에 따라 피크타임이 존재하기 때문에 더더욱 적합합니다. (개발자의 상황에 적합하지 못한 코드 덕분에 심심하면 죽어나간 서버가 한둘이 아닙니다.)

조건을 단순화한 예제이지만 대부분의 큰 프로그램의 어딘가에서 null 할당을 해줘야 하나 말아야 하나를 고민할때 생각해 볼만한 예제라고 생각이되는군요.

적다보니 내용이 너무 길어 지는 감이 있어 앞에서 얘기한 null 할당 이외의 방법을 소개하고 끝내도록 하겠습니다. 

자바가 처음 나온후 컴퓨터 분야에서는 꽤 오랜 시간이 흘렀습니다.  VM 이 알아서 메모리를 관리해 준다는 장점은 사용하다보니 다른 문제들을 야기하게 되었습니다. 바로 "우린 좀더 GC 와 얘기하고싶다" 라는 건데요.

null 할당은 GC 가 일어나기만 하면 바로 회수해 가버리고 이후에 해당 객체를 더이상 사용할 수 없지만.

1. GC 를 한번 해보고, 메모리가 충분하다면 계속 참조하고 싶다
2. GC 가 일어나기 전까지만 계속 참조하고 싶다.
3. finalize 이후에 뭔가 더 처리 하고 싶다.

라는 요구사항들이 여기저기서 터져나오기 시작한거죠. 결국 1.2 버전에서 새로운 클래스들이 소개되기 에 이르렀는데 그게 바로 

java.lang.ref 

패키지 입니다. 현재 오픈소스나 캐싱 라이브러리 들이 이 패키지를 잘 활용한 예제이지요.  여기에는 딱 5개의 클래스가 존재 합니다.

Reference, PhantomReference, SoftReference, WeakReference, ReferenceQueue

입니다. 여기서 주역들은 

1. GC 를 한번 해보고, 메모리가 충분하다면 계속 참조하고 싶다 (SoftReference)
2. GC 가 일어나기 전까지만 계속 참조하고 싶다. (WeakReference)
3. finalize 이후에 뭔가 더 처리 하고 싶다. (PhantomReference)

이 3가지 클래스 입니다. 저는 WeakReference 와 SoftReference 를 소개 하겠습니다.

WeakReference 예제

import java.lang.ref.WeakReference;

public class GCTest3 {
private final static int ALLOC_SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.60);
public void allocate() {
System.out.println("Before first allocation");
WeakReference<byte[]> b = new WeakReference<byte[]>(new byte[ALLOC_SIZE]);
System.out.println("After first allocation");

System.out.println("b is alive ? " + b.get());
System.out.println("Before second allocation");
byte[] b2 = new byte[ALLOC_SIZE];
System.out.println("After second allocation");
System.out.println("b is alive ? " + b.get());
}
public static void main(String[] args) {
new GCTest3().allocate();
}
}

수행 결과는 

Before first allocation
After first allocation
b is alive ? [B@64c3c749
Before second allocation
[GC 555205K->555168K(712576K), 0.0016671 secs]
[GC 555168K->555104K(712576K), 0.0015086 secs]
[Full GC 555104K->208K(105344K), 0.0562847 secs]
After second allocation
b is alive ? null

GC 한방에 날아가버렸네요.


SoftReference 예제


import java.lang.ref.SoftReference;

public class GCTest3 {
private final static int ALLOC_SIZE = (int) (Runtime.getRuntime().maxMemory() * 0.60);
public void allocate() {
System.out.println("Before first allocation");
SoftReference<byte[]> b = new SoftReference<byte[]>(new byte[ALLOC_SIZE]);
System.out.println("After first allocation");

System.out.println("b is alive ? " + b.get());
System.out.println("Before second allocation");
byte[] b2 = new byte[ALLOC_SIZE];
System.out.println("After second allocation");
System.out.println("b is alive ? " + b.get());
}
public static void main(String[] args) {
new GCTest3().allocate();
}
}


WeakReference 의 예제에서 SoftReference 로 클래스만 바뀌었습니다.

결과를 볼까요?

Before first allocation
After first allocation
b is alive ? [B@7150bd4d
Before second allocation
[GC 555205K->555136K(712576K), 0.0016918 secs]
[GC 555136K->555104K(712576K), 0.0014965 secs]
[Full GC 555104K->555088K(660160K), 0.0045636 secs]
[GC 555088K->555088K(712576K), 0.0068316 secs]
[Full GC 555088K->186K(110208K), 0.0575150 secs]
After second allocation
b is alive ? null

한번 해보고 버틸수가 없다! 이러니까. 결국 이녀석도 날아가 버렸네요.

원문 출처: http://www.odi.ch/prog/design/newbies.php#31
번역 출처: http://jnylove.tistory.com/192

 

# String concatenation

- very bad code
String s = "";
for (Person p : persons) {
    s += ", " + p.getName();
}
s = s.substring(2); //remove first comma

이건 정말 멍청한 짓이다. loop 안에서 String의 concatenation을 반복하는 것은 쓰잘데기 없는 array copy와 garbage를 남발하는 것이다.
게다가, 마지막에 콤마를 제거하는 연산을 한 번 더 해줘야 한다.

- better code
StringBuilder sb = new StringBuilder(persons.size() * 16); // well estimated buffer
for (Person p : persons) {
    if (sb.length() > 0) sb.append(", ");
    sb.append(p.getName);
}


# Lost StringBuffer performance

- not so good code
StringBuffer sb = new StringBuffer();
sb.append("Name: ");
sb.append(name + '\n');
sb.append("!");
...
String s = sb.toString();

좋아 보임에도 불구하고 위 코드는 문제가 있다. 가장 명백하게 눈에 띄는 실수는 3번째 라인의 String concatenation 이다.
4번째 라인의 char를 append 하는 것은 String을 append 하는 것 보다 빠르다.
또한 하나 간과한 것이 buffer의 사이즈를 초기화하지 않음으로해서, 불필요한 resizing(array copy)이 일어날 수 있다는 것이다.
JDK 1.5에서는 synchronization이 필요없는 로컬 변수에는 StringBuilder가 StringBuffer 대신에 사용된다.

- good code
StringBuilder sb = new StringBuilder(100);
sb.append("Name: ");
sb.append(name);
sb.append('\n');
sb.append('!');
String s = sb.toString();


# Testing for string equality

- not perfect
if (name.compareTo("John") == 0) ...
if (name == "John") ...
if (name.equals("John")) ...
if ("".equals(name)) ...

위 비교는 잘못된 결과를 초래할 수 있다. 
compareTo() method는 좀 지나치고, == 연산자는 객체 동치에 대한 연산을 수행하는데, 이는 아마도 원치 않는 결과를 초래할 것이다.
equals() method를 사용해야 할 것인데, 변수와 상수의 위치를 바꾸면 NullPointerException을 피할 수 있는 부가적인 안정성도 얻을 수 있고,
loop 안에서 equals() method가 호출될 때 equals() method가 같은 object로부터 호출이 되기 때문에 수행 속도의 향상도 가져올 수 있다.
빈 문자열을 체크할 때는 문자열의 길이를 체크하는 것이 가장 빠른데, 이는 equals() method가 hash code를 먼저 계산하기 때문이다.

if ("John".equals(name)) ...
if (name.length() == 0) ...


# Converting numbers to Strings

"" + set.size()
new Integer(set.size()).toString()

Set.size()의 반환값은 int 이다. 결과를 String 으로 바꾸려고 하는데, 위의 두 줄의 코드를 이용하여 String으로의 변환을 수행할 수 있으나,
첫번째 라인에서 String concatenation의 문제가 있고, 두번째 라인에서는 toString() method를 수행하기 위한 Integer class를 생성하기까지 한다.
이는 아래와 같이 간단하게 수정할 수 있다.

String.valueOf(set.size())

# Not taking adavantage of immutable objects

- code wastes resources
zero = new Integer(0);
return Boolean.valueOf("true");

Integer class와 Boolean class는 immutable이다. 즉, 불변객체이다. 쓸데없이 객체를 생성할 필요가 없다.
위 코드에서 사용된 Integer와 Boolean class 들은 자주 사용하는 instance들에 대한 built-in cache가 존재한다.
Boolean class의 경우는 값이 오로지 true/false 두가지 뿐이다.

- conservative code
zero = Integer.valueOf(0);
return Boolean.TRUE;


# XML parsers are for sissies

- native code
int start = xml.indexOf("<name>");
int end = xml.indexOf("</name>");
String name = xml.substring(start, end);

위에서 사용한 XML parsing은 오로지 정말 단순한 XML 문서에서만 동작을 한다.
다음과 같은 경우의 XML 문서에서는 오동작을 할 것이다.
a) 문서상에서 name element가 유일하지 않을 때
b) name의 내용이 문자데이터로만 이루어지지 않았을 때
c) 문서에서 XML namespace를 사용할 경우

XML은 문자열 연산을 하기에 너무 복잡하다. Xereces 같은 XML parser의 경우 jar 파일이 1 MB 가 넘는데는 다 그만한 이유가 있는 것이다.

- more professional code
SAXBuilder builder = new SAXBuilder(false);
Document doc = doc = builder.build(new StringReader(xml));
String name = doc.getRootElement().getChild("name").getText();


# The XML encoding trap

- very bad code
String xml = FileUtils.readTextFile("my.xml");

XML 파일의 내용을 읽어서 String class에 저장하는 것은 멍청한 짓이다.
XML 파일은 XML header에 해당 내용에 대한 encoding을 지정하고 있다.
XML 파일을 읽기 전에 해당 파일에 대한 encoding 정보를 알아야 한다.
또한, XML 파일의 내용을 String class에 저장하는 것은 메모리 낭비다.
XML parser들은 XML 파일을 parsing하는데 InputStream을 이용하여 내용을 읽어 들이고, encoding이 올바로 되었는지 확인한다.


# Undefined encoding

- not portable code
Reader r = new FileReader(file);
Writer w = new FileWriter(file);
Reader r = new InputStreamReader(inputStream);
Writer w = new OutputStreamWriter(outputStream);
String s = new String(byteArray); // byteArray is a byte[]
byte[] a = string.getBytes();

위 코드들은 각각 기본 platform encoding 정보를 사용하여 byte와 char를 변환하고 있다.
위 코드들은 어떤 platform에서 실행되느냐에 따라서 다른 결과를 초래한다.
이는 다른 platform 간의 데이터 전송에 있어서 문제를 유발할 수 있다.
platform의 기본 encoding 정보를 이용하는 것은 나쁜 습관이다.
변환은 encoding 정보를 지정하여 수행되어야 한다.

- portable code
Reader r = new InputStreamReader(new FileInputStream(file), "ISO-8859-1");
Writer w = new OutputStreamWriter(new FileOutputStream(file), "ISO-8859-1");
Reader r = new InputStreamReader(inputStream, "UTF-8");
Writer w = new OutputStreamWriter(outputStream, "UTF-8");
String s = new String(byteArray, "ASCII");
byte[] a = string.getBytes("ASCII");


# Unbuffered streams

- performance sinking
InputStream in = new FileInputStream(file);
int b;
while ((b = in.read()) != -1) {
   ...
}

위 코드는 파일을 byte 단위로 읽는다.
read() method가 호출 될때마다 매번 JNI call이 일어난다.
JNI call은 비용이 비싸다. native call의 횟수를 stream을 warpping한 BufferedInputStream을 사용하여 획기적으로 줄일 수 있다.
노트북에서 /dev/zero 로 부터 1MB의 데이터를 읽어들이는데 위 코드는 1초가 걸린 반면 BufferedInputStream을 사용한 경우는 
60 ms로 줄었음을 확인할 수 있다. 자그만치 94%의 속도 증가이다. 이 stream wrapping 은 output stream에 대해서도 적용이 가능하며,
file system 에서 뿐만 아니라 socket 에 대해서도 적용가능하다.

- reasonable performance
InputStream in = new BufferedInputStream(new FileInputStream(file));

# Infinite heap

- resource waster
byte[] pdf = toPdf(file);

파일의 내용을 읽어서 byte array로 반환하여 PDF 파일을 만드는 method가 있다.
이 코드는 파일의 내용이 적을 경우에만 유효하다. Heap에 파일 크기만한 여유가 있을 경우에만 가능하다는 얘기다.
그렇지 않을 경우 OOE(OutOfMemoryException)에 직면하게 된다.
Bulk 데이터는 절대로 byte array로 취급되면 안된다. Stream을 이용해야 하고 데이터는 DB나 disk에 저장되어야 한다.

- conservative code
File pdf = toPdf(file);


# Catch all: I don't know the right runtime exception

- wrong code
Query q = ...
Person p;
try {
    p = (Person) q.getSingleResult();
} catch(Exception e) {
    p = null;
}

이는 J2EE EJB3의 query 이다. getSingleResult() method는 아래와 같은 경우에 RuntimeException을 야기한다.
a) 결과가 유일하지 않을 때
b) 결과가 없을 때
c) query가 DB 오류등으로 인해 실행되지 못했을 때

위 코드는 모든 exception을 잡을 수 있다. 전형적인 catch-all 블럭이다.
null 이라는 결과를 사용하는 것은 b) 의 경우에는 유효할 지 모르나, a)나 c)의 경우에는 그렇지 않다.
일반적으로 필요한 exception 이외에는 더 잡을 필요도 없다.

- correct code
Query q = ...
Person p;
try {
    p = (Person) q.getSingleResult();
} catch(NoResultException e) {
    p = null;
}

# Exceptions are annoying

- hard to debug code
try {
    doStuff();
} catch(Exception e) {
    log.fatal("Could not do stuff");
}
doMoreStuff();

위 코드에는 두가지 문제점이 있다.
첫째, 이 코드에서 정말로 치명적인 오류가 발생한다면, 이를 호출한 쪽에 알리고, 적당한 exception을 발생시켜야 한다.
그렇지 않으면, 치명적인 오류 이후에도 동작을 계속하게 된다.
둘째, 문제가 생겼을 경우에 exception에 대한 정보가 사라지기 때문에 debug 하기가 힘들다.
exception 객체는 문제가 생기면 어디서 어떤 문제가 생겼는지 상세한 정보들을 담고 있다.
exception이 발생한다면 적어도 오류 메세지와 stack trace 정도는 log에 남겨야 한다.

- better code
try {
    doStuff();
} catch(Exception e) {
    throw new MyRuntimeException("Could not do stuff because: "+ e.getMessage, e);
}


# Catching to log

- stupid code
try {
    ...
} catch(ExceptionA e) {
    log.error(e.getMessage(), e);
    throw e;
} catch(ExceptionB e) {
    log.error(e.getMessage(), e);
    throw e;
}

위 코드를 보면 catch 절에서 단지 log만 남기고 원래 exception을 throw 하는 것을 볼 수 있다.
이는 멍청한 짓이다. 이 코드가 속한 method를 호출한 곳에서 해당 exception에 대해서 처리하도록(log를 남기던지 등) 
try/cat 전체를 옮기는 것이 낫겠다.


# Incomplete exception handling

- this instable code can leak file handles
try {
    is = new FileInputStream(inFile);
    os = new FileOutputStream(outFile);
} finally {
    try {
        is.close();
        os.close();
    } catch(IOException e) {
        /* we can't do anything */
    }
}

stream이 close 되지 않으면, 해당 OS 에서는 사용했던 resource를 해제할 수 없게 된다.
이 코드에서는 stream 두개를 닫기 위해서 finally 절에 close 를 넣었는데,
만약에 is.close()에서 IOException이 발생하면 os.close()는 수행되지 않게 된다.
두개의 close 는 각각 try/cat 절로 쌓여야 한다.
게다가, input stream을 생성하다가 exception이 발생하면 os 가 null이 되므로 os.close() 에서는 NullPointerException이 발생할 것이다.

- stable codes that frees all resources
try {
    is = new FileInputStream(inFile);
    os = new FileOutputStream(outFile);
} finally {
    try { if (is != null) is.close(); } catch(IOException e) {/* we can't do anything */}
    try { if (os != null) os.close(); } catch(IOException e) {/* we can't do anything */}
}


# The exception that never happens

- bad code
try {
  ... do risky stuff ...
} catch(SomeException e) {
  // never happens
}
... do some more ...

위 코드에서 개발자는 귀차니즘으로 인해 이 코드를 호출한 곳으로 어떤 exception을 다시 던지기를 원치 않는다.
게다가 exception이 절대 발생하지 않을 거라는 자만심에 빈 catch 절에 주석까지 달아놓았다.
하지만, exception이 절대 발생하지 않는다고 어떻게 장담할 수 있을까?
호출하는 method의 내용이 변경되기라도 한다면, 특정 경우에 exception이 발생하는데도 그런 경우에 대해서 생각을 못하고 있다면 어떨까?
만약 문제가 생기면 try/catch 이후의 코드들이 수행이 되면서 잘못된 결과를 초래할 것이다.  exception은 절대로 잡을 수 없게 될 것이다.
위 코드는 runtime exception을 던짐으로해서 보다 안정적이 될 필요가 있다.
그럼으로써 assertion의 역할도 하고 "crash early" 원칙을 따르는 것이기도 하다.
개발자는 모든 경우에 대해서 생각해야 하고, 작성한 코드가 완벽하다는 가정은 애시당초 틀렸다는 것을 가정해야 하며,
오류가 발생한 지점의 try/catch 이후의 코드는 실행되지 않도록 해야 한다. 
이렇게 처리한 후에 exception이 나지 않으면 그만이고, exeption이 발생하면 다행인 것이다.

- more reliable code
try {
  ... do risky stuff ...
} catch(SomeException e) {
  // never happens hopefully
  throw new IllegalStateException(e.getMessage(), e); // crash early, passing all information
}
... do some more ...


# The transient trap

- wrong code
public class A implements Serializable {
    private String someState;
    private transient Log log = LogFactory.getLog(getClass());
    
    public void f() {
        log.debug("enter f");
        ...
    }
}

Log 객체는 직렬화되지 않았다. 개발자도 이를 알고 직렬화되지 않도록 log 필드를 transient로 정의했다.
그러나, 이 변수의 초기화는 class의 initializer에서 이루어진다.
역직렬화시에는 initializers와 constructors는 수행이 되지 않는다.
역직렬화된 객체에는 log 변수가 null로 되어 있고, f() method에서는 NullPointerException을 야기할 것이다.
transient 변수는 절대 class 초기화에 사용하지 말아야 하며, 이 문제는 static 변수나 로컬 변수로 사용함으로써 해결할 수 있다.

- correct code
public class A implements Serializable {
    private String someState;
    private static Log log = LogFactory.getLog(getClass());
    
    public void f() {
        log.debug("enter f");
        ...
    }
}

public class A implements Serializable {
    private String someState;
    
    public void f() {
        Log log = LogFactory.getLog(getClass());
        log.debug("enter f");
        ...
    }
}


# Overkill initialization

- overkill
public class B {
    private int count = 0;
    private String name = null;
    private boolean important = false;
}

위 코드를 작성한 사람은 C로 프로그래밍하는데 익숙한 사람이어서, 모든 변수가 적절한 값으로 초기화 되기를 바란다.
Java 에서는 member 변수의 경우에 0, null, false 등으로 자동으로 초기화가 된다.
따라서 위 코드는 아래와 깉이 고치는 것이 좋다.

- faster and slicker and still the same
public class B {
    private int count;
    private String name;
    private boolean important;
}


# Too much static

- prevents class collection
private static Log LOG = LogFactory.getLog(MyClass.class);
많은 개발자들은 Log instance를 static 멤버 변수로 사용하는데 이는 디자인 관점에서 보면 맞는다.
하지만, 이는 절대 그럴 필요가 없다.
LogFactory class는 이미 Log 객체에 대한 static 참조를 갖고 있어서 getLog() 를 호출하면 hash table에서 찾게 되고, 비용도 비싸지 않다.
하지만, 모든 class 들이 static 멤버 변수를 갖도록 하는 것은 class들에 대한 gc를 방해하기 때문에 몹쓸 짓이다.
단지, 드물게 위에 위에 설명했던 직렬화 class 같은 경우에는 static 변수를 사용하는 것이 방법이다.
이는 클래스명을 찾기 위해 getClass()를 사용할 수 없게 만든다. 
대신에 클래스명을 하드코딩 해야만 하고, 로그 개체를 상속 클래스와 공유할 수 없어서 이를 private으로 선언해야만 한다.

- lets the class unload when not used
protected Log log = LogFactory.getLog(getClass());


# Chosing the wrong class loader

- rarely uses the right class loader
Class clazz = Class.forName(name);

위 코드는 현재 class를 load하는 class loader를 사용한다.
이것은 다른 class를 동적으로 load 하는데 맘대로 되지 않는다.
Servlet engine, Java Webstart 또는 Application server 같은 환경에서는 확실히 잘못된 것이다.
이 코드는 실행되는 환경에 따라 다르게 동작한다.
컨텍스트 클래스 로더를 사용하는 환경에서는 어플리케이션이 자신의 클래스를 로드하기 위해 사용해야 하는 클래스 로더를 제공한다.

- mostly uses the right class loader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) cl = getClass().getClassLoader(); // fallback
Class clazz = cl.loadClass(name);


# Poor use of reflection

- dangerous
Class beanClass = ...
if (beanClass.newInstance() instanceof TestBean) ...

이 개발자는 reflection API와 싸움중이다.
상속 여부에 대해서 체크하는 방법을 알아야 되는데 아직 방법을 찾지 못했다.
그래서 새로운 instance를 하나 생성하고 instanceof 연산자를 사용하곤 했다.
class의 instance를 생성하는 것이 얼마나 위험한지 모른다.
이 class가 뭐하는 class 인지도 모른다. 객체 생성 비용이 비쌀 수도 있다. 기본 생성자가 없을 수도 있다.
exception을 던지기라도 한다면.
이를 해결하는 방법은 Class.isAssignableFro(Class) method를 사용하는 것이다.
이것은 instanceof의 역방향이다.

- correct
Class beanClass = ...
if (TestBean.class.isAssignableFrom(beanClass)) ...


 

# Synchronization overkill

- synchronization overkill
Collection l = new Vector();
for (...) {
   l.add(object);
}

Vector는 동기화된 ArrayList이고, Hashtable도 동기화된 HashMap 이다.
두 class들은 객체를 동시에 접근하는 일이 있을 경우에만 사용 되어져야 한다.
동기화가 필요없는 지역 임시 변수에 이것들을 사용하는 것은 오버이며, 성능을 꽤 떨어뜨릴 수도 있다.

- no synchronization
Collection l = new ArrayList();
for (...) {
   l.add(object);
}


# Wrong list type

- 초보 개발자들은 적절한 list 유형을 결정하는데 어려움을 겪는다.
그들은 보통 Vector, ArrayList, LinkedList 중에 아무거나 골라 사용한다.
하지만, 성능 향상에 대한 문제를 고민해 봐야 한다.
하지만, 각 classe들의 구현은 adding, iterating, accessing 에 따라서 상당히 다르다.
아래 간략하게 표로 정리했는데, 이 표에서 Vector는 동기화 때문에 약간 느린거 말고는 ArrayList와 비슷하기때문에 생략했다.

                                   ArrayList                LinkedList 
add (append)        O(1) or ~O(log(n))             O(1) 
insert (middle)       O(n) or ~O(n*log(n))         O(1) 
iterate                 O(n)                                 O(n) 
get by index         O(1)                                 O(n)

linked list가 insert 작업에 대한 성능이 지속적인 반면, list entry마다 보다 많은 메모리를 사용하고 있다.
ArrayList의 insert 성능은 초기화 사이즈가 적당한지, insert 도중에 size가 늘어나는지에 따라 좌우된다.
확장은 2의 지수 형식으로 늘어나며, O(log(n))의 비용이 든다.
지수 형태의 확장은 정말 필요한 양보다 많은 양의 메모리를 사용하게 된다.
리스트 크기의 재설정은 또한 응답시간을 느리게 만들고 리스트가 크다면 major 가비지 컬렉션을 일으킬 수도 있다. 
list에서 Iterator를 사용하는 것은 그리 큰 비용이 들지 않지만, index를 사용하는 것은 아주 느리다.


# I love arrays

/**
 * @returns [1]: Location, [2]: Customer, [3]: Incident
 */
Object[] getDetails(int id) {...

문서화되었다 하더라도, 이런 종류의 메소드의 값 반환은 다루기 난처하고 에러를 쉽게 유발한다. 
반드시 이 값들을 담는 작은 클래스를 선언해서 사용해야 한다. 이는 C의 struct와 비슷하다.

Details getDetails(int id) {...}

private class Details {
    public Location location;
    public Customer customer;
    public Incident incident;
}


# Premature object decomposition

public void notify(Person p) {
    ...
    sendMail(p.getName(), p.getFirstName(), p.getEmail());
    ...
}
class PhoneBook {
    String lookup(String employeeId) {
        Employee emp = ...
        return emp.getPhone();
    }
}

첫번째 예제에서 단지 method에 상태를 전달하기 위해 객체를 분해하는 건 참 쓰잘데기 없는 일이다.
두번째 에제에서 이 method의 사용은 매우 제한적이다.
object 자체를 넘기도록 디자인되어야 한다.

public void notify(Person p) {
    ...
    sendMail(p);
    ...
}
class EmployeeDirectory {
    Employee lookup(String employeeId) {
        Employee emp = ...
        return emp;
    }
}


 

# Modifying setters

private String name;

public void setName(String name) {
    this.name = name.trim();
}

public void String getName() {
    return this.name;
}

이 개발자는 사용자가 입력한 이름 값의 앞/뒤 빈 공백에 대해서 처리를 하기 위해서
bean 의 setter method에서 공백들을 없애는 처리를 했다.
하지만, bean은 단지 값을 저장하기 위한 공간이지 Business Logic이 아니다.
bean 에서는 데이터를 변경하면 안된다. 값을 변경하려면 값이 입력되는 곳에서 차단하거나, 빈 공백을 원하지 않는 곳에서 처리해야 한다.

person.setName(textInput.getText().trim());


# Unnecessary Calendar

Calendar cal = new GregorianCalender(TimeZone.getTimeZone("Europe/Zurich"));
cal.setTime(date);
cal.add(Calendar.HOUR_OF_DAY, 8);
date = cal.getTime();

date, time, calendar, time zone에 대한 혼동이 있는 개발자들의 의한 전형적인 실수이다.
Date에 8시간을 더하기 위해 Calendar는 필요 없다. time zone도 상관이 없다.
(Think about is if you don't understand this!) However if we wanted to add days (not hours) we would need a Calendar, 
하지만, 시간이 아니라 날짜를 더하기 위해서는 Calendar가 필요하다.
because we don't know the length of a day for sure (on DST change days may have 23 or 25 hours). 
왜냐하면, 우리는 정확히 하루의 길이가 어떻게 되는지 모르기 때문이다.(DST- Daylight-Saving Time- 상에서는 23시간이 되기도 하고 25시간이 되기도 한다)

date = new Date(date.getTime() + 8L * 3600L * 1000L); // add 8 hrs

# Relying on the default TimeZone

Calendar cal = new GregorianCalendar();
cal.setTime(date);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
Date startOfDay = cal.getTime();

개발자는 하루의 시작( 0시 0분)을 계산하고 싶어한다. 이 개발자는 Calendar의 밀리초를 놓쳤다.
하지만, 진짜 큰 실수는 Calendar 객체에 TimeZone을 설정하지 않은 것이다.
TimeZone을 설정하지 않으면 Calendar는 기본 time zone을 사용한다.
이는 Desktop 프로그램에서는 괜찮을지 모르나, server-side 프로그램에서는 원하는 결과가 나오지 않을 수도 있다.
상하이에서의 0시 0분은 런던에서의 0시 0분과 전혀 다른 시간이다.
개발자는 날짜 계산을위해 타당한 time zone을 사용하는지 확인해볼 필요가 있다.

Calendar cal = new GregorianCalendar(user.getTimeZone());
cal.setTime(date);
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
Date startOfDay = cal.getTime();


# Time zone "conversion"

- stupid
public static Date convertTz(Date date, TimeZone tz) {
  Calendar cal = Calendar.getInstance();
  cal.setTimeZone(TimeZone.getTimeZone("UTC"));
  cal.setTime(date);
  cal.setTimeZone(tz);
  return cal.getTime();
}

만약 위 코드가 뭔가 쓸모있다고 생각한다면 가서 article about time(http://www.odi.ch/prog/design/datetime.php)을 다시 읽어보라.
이 개발자는 위 기사를 읽지 않았고, 그의 날짜의 time zone을 수정하려고 노력하고 있다.
사실 그 method는 아무 일도 안한다. 반환된 Date는 parameter로 넘겼단 값과 다르지 않을 것이다.
왜냐하면, Date는 time zone에 대한 정보를 갖고 있지 않기 때문이다.
항상 UTC를 따른다. 그리고, Calendar의 getTime()/setTime() method는 항상 UTC와 실제 time zone 사이의 변환을 한다.

# Calling Date.setTime()

- not recommended
account.changePassword(oldPass, newPass);
Date lastmod = account.getLastModified();
lastmod.setTime(System.currentTimeMillis());

위 코드는 account entity의 최근 수정 날짜를 변경하고 있다.
프로그래머는 보수적이고, 새로운 Date 객체를 생성하는 것을 피하고 있다.
대신에 존재하는 Date instance를 수정하기 위해 setTime() method를 사용한다.

사실 위 코드에 잘못된 것은 없다. 하지만, 추천하고 싶지는 않다.
Date 객체는 일반적으로 조심스럽게 전달된다. 같은 Date instance가 setter에서 copy를 하지 않는 여러개의 객체들에 전달이 될 수 있다.
Date는 종종 원시형 처럼 사용된다. 그래서, Date instance를 수정하면, 이 instance를 사용하는 다른 객체에서 기대하지 않은 일이 발생할 수 있다.
물론, 프로그래머가 OO 원칙에 입각한 코드를 작성한다면, 객체가 그 자신의 본질적인 Date instance를 바깥 세상에 노출시키는 것이 깨끗한 디자인은 아니다.
대부분의 Java 코드에서 그들의 setter 에서 복제하지 않고, 단지 Date 참조를 복사하고 있다.
모든 프로그래머는 Date 를  불변(immutable) 로 취급해서, 존재하는 instance에 수정을 가하면 안된다.
이는 오직 성능 향상의 이유로만 이루어져야 한다. 한편으로는 간단한 long의 사용이 좋을 수도 있다.


- real world code
account.changePassword(oldPass, newPass);
account.setLastModified(new Date());

# Not noticing overflows

- overflow
public int getFileSize(File f) {
  long l = f.length();
  return (int) l;
}

이 개발자는 무슨 이유에서인지 파일의 크기를 반환하는 method에서 반환값을 long 대신에 int를 사용했다.
이 코드는 파일의 크기가 2GB를 넘을 경우 제대로 작동하지 않는다.
작은 값으로의 cast일 경우에는 반드시 overflow에 대해서 체크하고 exception을 던지도록 해야 한다.

- no overflow
public int getFileSize(File f) {
  long l = f.length();
  if (l > Integer.MAX_VALUE) throw new IllegalStateException("int overflow");
  return (int) l;
}

다른 버전의 overflow 버그는 아래와 같다.

long a = System.currentTimeMillis();
long b = a + 100;
System.out.println((int) b-a);
System.out.println((int) (b-a));


# Storing money in floating point variables

- broken sum
float total = 0.0f;
for (OrderLine line : lines) {
  total += line.price * line.count;
}

많은 개발자들이 위와 같이 loop을 코딩한다.
하나에 0.3$ 짜리 물건을 100개 계산한다고 하면, total 에 계산되어지는 값은 정확히 29.999971 일 것이다.
개발자들은 이 이상한 동작에 대해서 알아채고, 보다 정확한 double로 변경하여 30.000001192092896 값을 얻는다.
이런 결과는 사람과 컴퓨터가 숫자에 대해서 인식하는 방법의 차이 때문이다.
이는 소숫점자리의 돈 계산시에 가장 귀찮은 형태로 항상 발생한다.
따라서, 돈 같은 경우는 계산이 필요 없는 경우라면 text 로 저장되어야 한다.
계산시에 text 표현을 integer로 안전하게 변환해야 한다.
Double.parseDouble(amount) * 100 사용에 주의하라. 
차라리 String의 소숫점 자리에 0을 붙이거나, 소숫점을 없애고, Integer.parseInt를 이용해서 integer로 변환하라.
덧붙일 길이는 정밀도 요구에 따라 달라진다.


- correct sum
int totalcents = 0;
MoneyUtils money = new MoneyUtils(2);
for (OrderLine line : lines) {
  totalcents += money.toCents(line.priceString) * line.count;
}
String total = money.fromCents(totalcents);


public class MoneyUtils {
  private int precision;
  private String zero;
  
  public MoneyUtils(int precision) {
      this.precision = precision;
      char[] z = new char[precision];
      for (int i=0; i<z.length; i++) z[i] = '0';
      zero = new String(z);
  }
  
  public int toCents(String amount) {
      String[] parts = amount.split("\\.");
      String padded;
      switch (parts.length) {
          case 1:
              padded = parts[0] + zero.substring(0, precision);
              break;
         
          case 2:
              int end = precision - parts[1].length();
              if (end < 0) throw new IllegalArgumentException("precision not enough");
              padded = parts[0] + parts[1] + zero.substring(0, end);
              break;
              
          default: throw new IllegalArgumentException("too many dots");
      }
      return Integer.parseInt(padded);
  }
  
  public String fromCents(int cents) {
      String amount = String.valueOf(cents);
      return amount.substring(0, amount.length() - precision) 
             +"."+ amount.substring(amount.length() - precision, amount.length());
  }
}


# Abusing finalize()

- abusive code
public class FileBackedCache {
   private File backingStore;
   
   ...
   
   protected void finalize() throws IOException {
      if (backingStore != null) {
        backingStore.close();
        backingStore = null;
      }
   }
}

이 class에서는 file handle을 해제하기 위해서 finalize() method를 사용했다.
문제는 이 method가 언제 호출이 되는지 모른다는 것이다.
이 method는 garbage collector에 의해서 호출이 된다.
만약에 file handler을 다 쓰고 난 후에 이 method가 호출되기를 원할것이다
하지만, 이 method는 GC가 프로그램이 heap을 다 사용했을 경우에 호출하게 되어 있다. 
garbage collector는 단지 메모리를 관리한다. 하지만, 메모리 관리 이외의, 다른 resource를 관리하도록 남용되어서는 안된다.

- proper code
public class FileBackedCache {
   private File backingStore;
   
   ...
   
   public void close() throws IOException {
      if (backingStore != null) {
        backingStore.close();
        backingStore = null;
      }
   }
}


 ArrayList -> Byte[] -> ArrayList 이걸 원하시죠?

 

API로 바로 설명드립니다.

 

ArrayList arrayList 는 있다고 가정..

 

Byte[] bytes = (Byte[]) arrayList.toArray(); //  ArrayList -> Byte[]

 

arrayList = new ArrayList(Arrays.asList(bytes )); // Byte[] -> ArrayList

 


만약 프리미티브 byte[]로 전환하고자 하신다면, Bytes[X].byteValue() 를 X값을 루프를 돌면서 복사해주면됩니다.

출처 : http://0thinktank.tistory.com/entry/ThreadLocal-%EC%98%88%EC%A0%9C

특정 쓰레드의 스코프에서 사용할 로컬변수가 필요했던 적이 있는가? 이때에 각각의 쓰레드는 고유의 스토리지를 갖고 하나의 쓰레드는 다른 쓰레드의 상태 정보를 액세스하는 것이 불가능할 것이다. 표준 라이브러리는 이러한 요구를 가능케 하는 ThreadLocalInheritableThreadLocal, 2개의 클래스들을 제공하고 있다.

클래스들이 사용되고 있는 예를 보자.

  import java.util.Random;

  public class ThreadLocal1 {

    // Define/create thread local variable
    static ThreadLocal threadLocal = 
                                   new ThreadLocal();
    // Create class variable
    static volatile int counter = 0;
    // For random number generation
    static Random random = new Random();

    // Displays thread local variable, counter, 
    // and thread name
    private static void displayValues() {
      System.out.println (
        threadLocal.get() + "\t" + counter +
        "\t" + Thread.currentThread().getName());
    }

    public static void main (String args[]) {

      // Each thread increments counter
      // Displays variable info
      // And sleeps for the random amount of time
      // Before displaying info again
      Runnable runner = new Runnable() {
        public void run() {
          synchronized(ThreadLocal1.class) {
            counter++;
          }
          threadLocal.set(
             new Integer(random.nextInt(1000)));
          displayValues();
          try {
            Thread.currentThread().sleep (
              ((Integer)threadLocal.get()).intValue());
            displayValues();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      };

      // Increment counter, access thread local from 
      // a different thread, and display the values
      synchronized(ThreadLocal1.class) {
          counter++;
      }
      threadLocal.set(
         new Integer(random.nextInt(1000)));
      displayValues();

      // Here's where the other threads
      //  are actually created
      for (int i=0; i<5; i++) {
        Thread t = new Thread(runner);
        t.start();
      }
    }
  }

ThreadLocal1클래스는 2개의 정적 변수를 갖는다. common integer primitive 타입인 counterThreadLocal 타입의 변수인 threadLocal이 바로 그것이다. 일반적으로 클래스 변수를 사용하면, 해당 클래스의 모든 인스턴스들은 동일한 값을 공유하게 된다. 하지만 ThreadLocal의 경우는 다르다. ThreadLocal클래스를 사용하면 동일한 쓰레드내에서만 같은 정적 정보를 공유하게 되는 반면, 서로 다른 쓰레드에서 ThreadLocal 클래스를 사용하면 서로 다른 정적 정보를 갖게 된다.

ThreadLocal1에서, ThreadLocal 인스턴스는 0에서 999사이의 랜덤한 숫자로 초기화된다.

   threadLocal.set(
     new Integer(random.nextInt(1000)));

다수의 서로 다른 쓰레드가 생성되었다. 각각의 쓰레드 별로, 2개의 클래스 변수에 대한 상태 정보가 디스플레이된다. 그러면 이 쓰레드는 ThreadLocal이 랜덤한 숫자로 지정한 만큼의 밀리초동안 휴면한다. 이후, 2개의 클래스 변수의 상태 정보가 다시 디스플레이되고, ThreadLocal1를 실행하면, 아래와 같은 출력값을 보게 된다.

   828     1       main
   371     2       Thread-0
   744     3       Thread-1
   734     4       Thread-2
   189     5       Thread-3
   790     6       Thread-4
   189     6       Thread-3
   371     6       Thread-0
   734     6       Thread-2
   744     6       Thread-1
   790     6       Thread-4

여기에서 주목할 것은 세번째 컬럼에서 쓰레드의 이름이 같을 때를 살펴보면, 첫번째 컬럼의 ThreadLocal 변수값이 프로그램이 실행되는 동안 같은 상태를 유지한다는 것이다. 반면, 두번째 컬럼에서 보이는 정적 변수는 프로그램이 실행하는 동안 변화하고, 기존 쓰레드를 재검토할 때 그 바뀐 값을 유지한다. 이것이 바로 적절하게 동기화된 정적 변수가 해야 하는 일이다.

프로그램을 실행할 때마다 모든 컬럼에는 각기 다른 값이 생성되기 때문에 결과에서 보이는 첫번째 컬럼의 시간들이 위에서 보는 바와 다르다거나, 마지막 5개의 행의 이름의 순서가 다르다고 해도 놀랄 필요는 없다. 그 순서는 첫번째 컬럼에서 보여진 시간(times)에 기반한 것이다.

쓰레드 로컬 변수는 단순히 인스턴스 변수들의 또 다른 형태만이 아니다. 만약 특정 클래스의 10개의 인스턴스를 모두 일반적인 쓰래드내에서 접근한다면, 그것들의 ThreadLocal 정보는 모두 같을 것이다. 그러나, 만약 이러한 인스턴스들 중의 하나를 다른 쓰레드에서 접근하게 되면, ThreadLocal 정보는 바뀌게 될 것이다.

ThreadLocal 클래스는 오직 3개의 메소드만을 포함하고 있는데 get, setinitialValue가 바로 그것이다. ThreadLocal1 예제를 보면, set메소드는 인스턴스가 생성된 이후 호출되는 것을 알 수 있다. ThreadLocal의 상태를 이후에 다시 초기화하는 방법 대신에, ThreadLocal을 상속한 서브클래스에서 initialValue 메소드를 오버라이드할 수 있다. 다음의 ThreadLocal2 클래스가 바로 그 일을 하는데, 생성된 출력값은 ThreadLocal1의 경우와 비슷하다.

   import java.util.Random;

   public class ThreadLocal2 {

     // Create thread local class
     // Initial value is a random number from 0-999
     private static class MyThreadLocal
                                  extends ThreadLocal {
       Random random = new Random();
       protected Object initialValue() {
         return new Integer(random.nextInt(1000));
       }
     }

     // Define/create thread local variable
     static ThreadLocal threadLocal =
                           new MyThreadLocal();

     // Create class variable
     static volatile int counter = 0;

     // For random number generation
     static Random random = new Random();

     // Displays thread local variable, counter,
     // and thread name
     private static void displayValues() {
       System.out.println (
         threadLocal.get() + "\t" + counter +
         "\t" + Thread.currentThread().getName());
     }
    
     public static void main (String args[]) {

       // Each thread increments counter
       // Displays variable info
       // And sleeps for the random amount of time
       // Before displaying info again
       Runnable runner = new Runnable() {
         public void run() {
           synchronized(ThreadLocal2.class) {
             counter++;
           }
           displayValues();
           try {
             Thread.currentThread().sleep (
               ((Integer)threadLocal.get()).intValue());
             displayValues();
           } catch (InterruptedException e) {
             e.printStackTrace();
           }
         }
       };

       // Another instance of class created
       // and values displayed
       displayValues();

       // Here's where the other threads
       // are actually created
       for (int i=0; i<5; i++) {
         Thread t = new Thread(runner);
         t.start();
       }
     }
   } 

ThreadLocalinitialValue 메소드는 각각의 쓰레드가 상태를 알아내기 위해 하는 첫번째 호출에서 사용된다. Set이 이미 호출된 상태라면, initialValue 메소드는 절대로 호출되지 않는다. initialValue 메소드를 사용할 때, public이 아니라 protected로 만들어야 한다는 것을 기억하자. 일반적으로 initialValue 메소드가 임의로 호출되는 것은 바람직하지 않기 때문이다. initialValue 가 오버라이드되지 않았을 경우, 쓰레드 로컬 변수를 나타내는 객체의 초기 값은 null이다.

ThreadLocal변수에 담을 컨텐츠는 어떤 Object 타입이든지 가능하다. ThreadLocal2에서 컨텐츠는 Integer이다. get을 이용해서 상태를 받아낼 때 Object를 적절한 타입으로 캐스팅해야 한다는 것을 기억하자.

InheritableThreadLocal라고 불리는 ThreadLocal의 두번째 타입을 보자. ThreadLocal과 같이, InheritableThreadLocal을 사용하는 각각의 쓰레드는 고유의 상태객체를 갖는다. ThreadLocalInheritableThreadLocal의 차이점은 InheritableThreadLocal의 초기값은 생성하는 쓰레드로부터 변수의 상태를 얻어낸다는 데에 있다. 상태의 변경은 다른 쓰레드를 제외한 로컬 쓰레드에서만 가능하다. InheritableThreadLocal의 새로운 childValue 메소드는 그것의 상태를 초기화하기위해 사용되는데 이는 상태를 얻어내려고 할 때가 아니라 자녀객체의 생성자에 의해 호출된다.

실제로 ThreadLocal의 사용을 검토하기 위해서는, java.nio.charsetCharset 클래스를 보거나 java.util.LoggingLogRecord를 참고한다. Charset의 경우에는, ThreadLocal가 캐릭터 세트 인코더와 디코더를 검색하기 위해 사용된다. 이것은 초기화하는 코드들이 절절한 시기에 사용되도록 조절하는 역할을 한다. LogRecord에서 ThreadLocal의 사용은 각각의 쓰레드에 Login을 위한 고유한 id가 할당되는 것을 보장해 준다.