'java core'에 해당되는 글 237건

  1. 2005.02.18 Java condition variable
  2. 2005.02.12 Threads from aritma
  3. 2005.01.28 Reference object model from java world
  4. 2005.01.28 Garbage collection from javaworld
  5. 2005.01.24 자바 코드 컨벤션
  6. 2005.01.24 J2ME쪽 JSR모음
  7. 2005.01.24 Java 코딩 지침

Java condition variable

java core 2005. 2. 18. 08:51
저자: Scott Oaks and Henry Wong, 역 한동훈

원문: http://www.onjava.com/pub/a/onjava/excerpt/jthreads3_ch6/index1.html

편집자 노트: J2SE 5.0에는 새로운 점들이 있습니다: wait()와 notify()를 이용한 스레드 간의 흐름 제어(coordinating)에 대한 이전 버전의 선택사항들은 이제 스레드 작업을 위한 새롭고 복잡한 전략들을 표현하고 있는 클래스들로 보강되었습니다. Scott Oaks와 Henry Wong의 Java Threads, 3판의 첫번째 발췌는 java.util.concurrent 패키지에 대한 것입니다.

6장. 고급 동기화 문제들

이 장에서는 데이터 동기화와 관련된 보다 깊이 있는 이해를 요구하는 문제들을 살펴볼 것입니다. 이 주제는 주로 데이터 동기화와 관련된 시간제어(timing) 문제입니다. 스레드를 여러 개 사용하는 자바 프로그램을 작성하는 경우 데이터 동기화와 관련된 문제들 때문에 프로그램 디자인에 어려움이 생기는 경우가 많습니다. 게다가, 데이터 동기화와 관련된 오류는 특정 순서대로 이벤트가 발생할 때 생기기 때문에 발견하기도 매우 어려운 경우가 대부분입니다. 데이터 동기화에 대한 문제는 주로 시간제어 의존성(timing dependencies) 때문에 밝혀지지 않는 경우가 빈번합니다. 프로그램이 정상적으로 실행되는 동안에 발생하는 데이터 손상 문제를 발견한다해도 디버거나 코드에 디버깅 문장을 추가하여 실행할 경우 프로그램의 시간제어(timing)이 완전히 바뀌어버렸기 때문에 데이터 동기화 오류가 더 이상 발생하지 않는 경우도 있습니다.


이들 문제는 간단히 해결되지 않습니다. 대신에, 개발자는 이러한 문제들을 고려하여 프로그램을 설계(design)해야 합니다. 개발자들은 무엇이 원인인지, 무엇을 찾아봐야 하는지, 문제 발생을 피하기 위해 어떤 테크닉을 사용할 수 있는지와 같은 다양한 스레드 관련 문제들을 이해하고 있어야 합니다. 또한, 개발자들은 프로그램에서 필요한 유형의 동기화를 제공하는 도구, 스레드 안정성(threadsafe)로 알려진 도구들과 같은 고급 수준의 동기화 도구를 사용하는 것을 고려해야 합니다. 이 장에서는 이러한 아이디어들을 함께 살펴볼 것입니다.

동기화 용어

특정 스레드 시스템에 대해 알고 있는 프로그래머들은 이 장에서 논의할 몇 가지 개념들을 해당 시스템에서 사용하는 용어로 사용하는 경향이 있으며, 이러한 스레드 시스템에 대한 배경지식이 없는 프로그래머들은 여기서 사용하는 용어들을 꼭 이해할 필요가 있는 것은 아닙니다. 그래서, 여기에서는 여러분이 알고 있는 용어와 이 장에서 사용하는 특정 용어들을 비교할 것입니다.
장벽(Barrier)
장벽은 다양한 스레드들이 만나는 집합 장소(rendezvous point)입니다. 모든 스레드는 반드시 장벽에 도착해야 하며, 그 이후에 스레드들중에 일부만이 장벽을 통과해서 계속 수행할 수 있도록 허가됩니다. J2SE 5.0은 장벽(barrier) 클래스를 제공하며, 이전 버전의 자바를 위한 장벽 클래스는 부록 A에 있습니다.
역주: Barrier는 장벽 또는 배리어라는 두 가지 모두 쓰이고 있으며, rendezvous point는 랑데뷰라고 얘기하는 것이 보통입니다.

상태변수(Condition variable)
상태 변수는 실제로 잠금(Lock)이 아니며, 잠금과 관련된 변수입니다. 상태 변수는 데이터 동기화 문맥(context)에서 자주 사용됩니다. 상태 변수는 일반적으로 자바의 wait-and-notify 메커니즘과 동일한 기능을 수행합니다. 이 메커니즘에서 상태 변수는 실제로 보호하고 있는 객체 잠금(object lock)입니다. J2SE 5.0에서는 명시적인 상태 변수를 제공하며, 이번 버전의 자바를 위한 구현은 부록 A에 있습니다. 상태 변수의 종류 두 가지는 4장에서 다루고 있습니다.

임계 영역(Critical section)
임계 영역은 동기화 되는 메서드나 블록입니다. 임계 영역은 동기화 메서드(synchronized methods)나 블록처럼 중첩될 수 없습니다.
역주: synchronized 키워드로 선언된 메서드들이 중첩될 수 없음을 의미합니다.

이벤트 변수(Event variable)
이벤트 변수는 상태 변수의 다른 용어입니다.

잠금(Lock)
이 용어는 특정 스레드가 동기화되는 메서드나 블록에 들어가 있도록 액세스가 허가된 상태를 지칭합니다. 이것은 잠금이 필요한 메서드나 블록에 진입한 스레드라고 얘기합니다. 3장에서 논의한 것처럼 잠금은 객체의 인스턴스 또는 클래스 수준에서 적용될 수 있습니다.

모니터(Monitor)
스레드 시스템에서 일관되게 사용되지 않는 용어로, 일부 시스템에서는 모니터를 간단히 잠금(Lock)이라 하며, 다른 시스템에서는 모니터를 wait-and-notify 메커니즘과 유사한 의미로 사용합니다.

뮤텍스(Mutex)
잠금의 또 다른 용어. 뮤텍스는 동기화 메서드 또는 블록처럼 중첩되지 않으며 운영체제 수준에서 프로세스간에 사용될 수 있습니다.

읽기/쓰기 잠금(Reader/writer locks)
여러 스레드가 연속적으로 공유 데이터로부터 데이터를 읽기만 하는 경우나 하나의 스레드가 공유 데이터에 쓰기만 하는 경우에 획득할 수 있는 잠금(Lock). J2SE 5.0에서는 읽기/쓰기 잠금을 위한 클래스를 제공하며, 이전 버전의 자바를 위해 비슷한 기능을 하는 클래스는 부록 A에 있다.

세머포어(Semaphores)
세머포어는 컴퓨터 시스템에서 저마다 다르게 사용되고 있습니다. 많은 개발자들은 자바 잠금에서 하는 것처럼 객체를 잠그기 위해 세머포어를 사용합니다. 세머포어를 이용한 보다 복잡한 작업은 코드의 임계 영역 획득을 중첩하기 위해 카운터를 이용하는 경우입니다. 이러한 형태의 잠금은 자바 잠금과 정확하게 동일합니다. 또한, 세머포어는 코드에 대한 액세스 보다 자원에 대한 액세스를 얻기 위해 사용할 수 있습니다. 이들 기능의 대부분을 구현한 세머포어 클래스들은 J2SE 5.0에서 이용할 수 있습니다.
J2SE 5.0에서 동기화 클래스 추가

독자들은 위 용어 목록을 읽는 동안 강력한 패턴을 알아챘을 겁니다. J2SE 5.0부터 위에서 언급한 거의 모든 것들이 자바의 핵심 라이브러리로 포함되어 있다는 것입니다. 이제, J2SE 5.0 클래스들에 대해서 간략하게 살펴볼 것입니다.

세머포어(Semaphore)

자바에서 세머포어는 기본적으로 카운터가 있는 잠금입니다. 잠금이 있는 경우에 액세스를 금지하기 위해 사용된다는 점에서는 Lock 인터페이스와 유사하지만, 차이점은 카운터입니다.

세머포어는 중첩될 수 없지만 잠금은 구현에 따라 잠금이 중첩될 수 있다는 점을 제외하면, Lock 인터페이스에 대한 카운터가 있는 세머포어는 잠금과 동일합니다.

Semaphore 클래스는 발행할 수 있는 허가권 숫자를 유지합니다. 이 같은 정책을 사용하면 여러 스레드가 하나 이상의 허가권을 가지는 것을 가능하게 합니다. 허가권에 대한 실질적인 사용은 개발자에 달려 있습니다. 따라서, 세머포어는 허용할 수 있는 잠금의 수를 표현하기 위해 사용됩니다. 마찬가지로, 네트워크 연결이나 디스크 공간과 같은 리소스 제한 때문에 병렬로 작업할 수 있는 스레드 수를 제어하기 위해 사용될 수 있습니다.

Semaphoreinterface를 살펴봅니다.

public class Semaphore {
   public Semaphore(long permits);
   public Semaphore(long permits, boolean fair);
   public void acquire( ) throws InterruptedException;
   public void acquireUninterruptibly( );
   public void acquire(long permits) throws InterruptedException;
   public void acquireUninterruptibly(long permits);
   public boolean tryAcquire( );
   public boolean tryAcquire(long timeout, TimeUnit unit);
   public boolean tryAcquire(long permits);
   public boolean tryAcquire(long permits,
                      long timeout, TimeUnit unit);
   public void release(long permits);
   public void release( );
   public long availablePermits( );
}

Semaphore 인터페이스는 Lock 인터페이스와 매우 유사합니다. 허가권을 얻거나 반환하기 위해 사용하는 acquire()와 release() 메서드는 Lock 인터페이스의 lock(), unlock()과 비슷합니다. tryAcquire() 메서드는 개발자가 잠금이나 허가권을 얻기 위해 사용하는 tryLock() 메서드와 비슷합니다. 이들 메서드는 허가권을 바로 얻을 수 없는 경우의 대기 시간과 잠금을 획득하거나 반환할 허가권의 수(기본값은 1)를 지정할 수 있습니다.

Semaphore는 Lock과 몇 가지 다른 점이 있습니다. 첫째, 생성자는 허용할 수 있는 허가권 수를 지정해야 합니다. 총 허가권 수나 남아있는 허가권을 반환하는 메서드가 있습니다. 이 클래스는 잠금을 획득하거나 반환하는 알고리즘만을 구현하고 있습니다. Lock 인터페이스와 달리 Semaphore에서는 어떤 상태 변수도 사용하지 않습니다. 중첩의 개념이 없습니다. 동일한 스레드가 여러번 획득하는 것은 세머포어로부터 허가권을 여러번 얻는 것입니다.

세머포어가 fair 플래그가 true로 설정하여 생성된다면 세머포어는 요청이 만들어지는 순서대로 허가권을 할당합니다. ? 이것은 선착순과 매우 유사합니다. 이 선택사항의 단점은 속도입니다. 이는 가상머신에서 허가권을 순서대로 얻는 것은 임의의 스레드가 허가권을 획득하는 것 보다 더 많은 시간이 걸리기 때문입니다.

장벽(Barrier)

모든 스레드 동기화 도구들 가운데 장벽은 아마도 가장 이해하기 쉬운 것이면서 가장 적게 사용되는 것이라 생각합니다. 동기화를 생각할 때 첫번째 고려사항은 전체 작업의 일부분을 실행하는 스레드 그룹, 스레드들의 결과를 동기화해야 하는 위치에 대한 것입니다. 장벽은 단순히 결과를 모으거나 다음 작업으로 안전하게 이동하기 위해 모든 스레드들을 동기화하기 위한 대기장소라 할 수 있습니다. 응용 프로그램이 단계별로 수행될 때 장벽을 사용할 수 있습니다. 예를 들어, 대다수의 컴파일러들은 소스를 읽어들이는 것과 실행 파일을 생성하는 작업 사이에 많은 임시 파일과 다양한 경로를 만들어냅니다. 이런 경우에 장벽을 사용하여, 모든 스레드가 같은 단계에 머무르는 것을 보장할 수 있습니다.

이런 단순함에도 불구하고, 장벽은 왜 널리 사용되지 않는가? 기능은 단순하기 때문에 자바에서 제공되는 저수준(low-level) 도구로도 수행할 수 있습니다. 우리는 장벽을 사용하지 않고 이 문제를 두가지 방법으로 해할 수 있습니다. 첫번째는 상태 변수에 따라 대기하는 스레드를 만드는 것입니다. 마지막으로 수행되는 스레드는 다른 스레드들에게 작업이 완료되었음을 알리기 위해 장벽을 반환합니다. 두번째 방법은 join() 메서드를 사용하여 대기 종료 스레드를 사용하는 것입니다. 모든 스레드가 연결될 때 프로그램의 다음 단계를 위한 새 스레드를 시작하는 방법입니다.

그러나, 어떤 경우에는 장벽을 사용하는 것이 더 바람직합니다. join() 메서드를 사용할 때 스레드가 모두 종료되고, 우리는 새 스레드를 시작합니다. 따라서, 스레드는 이전 스레드 객체가 저장했던 상태 정보를 잃어버립니다. 따라서, 스레드는 종료되기 전에 상태를 저장해야 합니다. 뿐만아니라, 항상 새 스레드를 생성해야 한다면, 논리 연산자를 함께 사용할 수 없습니다. 왜냐하면, 각각의 하위작업에 대해 새 스레드를 생성해야하고, 하위 태스크에 대한 코드는 각각의 run() 메서드에 있어야 하기 때문입니다. 이런 경우에 모든 로직을 하나의 메서드로 작성하는 것이 더 쉬울 수 있습니다. 특히, 하위 작업이 매우 작은 경우에는 더욱 그렇습니다.

장벽 클래스의 인터페이스입니다.

public class CyclicBarrier {
   public CyclicBarrier(int parties);
   public CyclicBarrier(int parties, Runnable barrierAction);
   public int await( ) throws InterruptedException, BrokenBarrierException;
   public int await(long timeout, TimeUnit unit) throws InterruptedException,
               BrokenBarrierException, TimeoutException;
   public void reset( );
   public boolean isBroken( );
   public int getParties( );
   public int getNumberWaiting( );
}

장벽의 핵심은 await() 메서드입니다. 이 메서드는 기본적으로 상태변수의 await() 메서드와 비슷하게 동작합니다. 여기에는 장벽이 스레드를 풀어줄 때까지 대기하는 것과 만료시간 제한까지 대기하는 선택사항이 있습니다. 정확한 스레드 수가 대기중일 때 장벽이 통지를 수행하기 때문에 signal() 메서드가 있을 필요가 없습니다.

장벽을 생성할 때 개발자는 장벽을 사용하는 스레드 수를 지정해야 합니다. 이 숫자는 장벽을 동작시키는데 사용됩니다. 장벽에서 대기중인 스레드 수가 지정된 스레드 수와 일치할 경우에만 스레드가 모두 해제됩니다. 이 뿐만 아니라, run() 메서드를 구현하는 객체의 동작까지 지정할 수 있는 방법도 있습니다.

장벽이 해제되기 위한 조건을 만족하는 경우 스레드를 해제하기 전에 barrierAction 객체의 run() 메서드가 호출됩니다. 이를 이용하여 스레드 안정성이 없는 코드를 실행할 수 있습니다. 일반적으로, 이것을 이전 단계의 정리 코드 또는 다음 단계를 위한 준비(setup) 코드라 합니다. 장벽에 도달한 마지막 스레드가 동작을 실행하는 스레드가 됩니다.

await() 메서드를 호출하는 각 스레드는 고유한 반환값을 돌려 받습니다. 이는 장벽에 도달한 스레드 순서와 관련있는 값입니다. 이 값은 개별 스레드가 프로세스의 다음 단계를 수행하는 동안 작업을 어떤식으로 분배할지 결정하는 경우에 필요합니다. 첫번째로 도착한 스레드는 총 스레드 수 보다 1 작은 값이 되고, 마지막으로 도착한 스레드는 0이 됩니다.

일반적인 사용에서, 장벽은 매우 간단합니다. 모든 스레드는 필요한 수 만큼의 스레드가 도착할 때까지 기다립니다. 마지막 스레드가 도착하자마자 동작(action)이 실행되고, 스레드는 해제되고, 장벽은 재사용할 수 있습니다. 그러나, 예외 조건이 발생하고, 장벽이 실패할 수 있습니다. 장벽이 실패할 때 CyclicBarrier 클래스는 장벽을 없애고, BroenBarrierException과 함께 await() 메서드에서 대기중인 스레드를 모두 해제합니다. 장벽은 다양한 이유로 깨질 수 있습니다. 대기 스레드가 중지(interrupted)될 수도 있으며, 스레드가 제한시간 조건 때문에 깨질 수도 있으며, barrierAction에 발생한 예외 때문에 깨질 수 있습니다.

모든 예외 조건에서 장벽은 간단히 깨집니다. 따라서 개별 스레드는 이 문제를 해결해야 합니다. 게다가, 장벽은 다시 초기화 될 때까지 초기화되지 않습니다. 즉, 이 상황을 해결하는 복잡한 알고리즘은 장벽을 재초기화하는 경우도 포함해야 합니다. 장벽을 다시 초기화하기 위해 reset() 메서드를 사용하지만, 장벽에 이미 대기중인 스레드가 있다면 장벽은 초기화되지 않습니다. 즉, 장벽은 동작하지 않게 됩니다. 장벽을 재초기화하는 것은 상당히 복잡하기 때문에 새로운 장벽을 생성하는 것이 보다 더 쉬울 수 있습니다.

마지막으로 CyclicBarrier 클래스는 몇 가지 보조 메서드를 제공합니다. 이들 메서드는 장벽에 대기중인 스레드 수에 대한 정보라든가 장벽이 이미 깨졌는지를 알려줍니다.

카운트다운 래치(Countdown Latch)

카운트다운 래치는 장벽과 매우 유사한 동기화 도구입니다. 실제로, 래치는 장벽 대신 사용할 수 있습니다. 자바를 제외한 일부 스레드 시스템은 세머포어를 지원하는 기능들을 구현하기 위해 카운트다운 래치를 사용하기도 합니다. 장벽 클래스와 마찬가지로 스레드가 어떤 상태를 대기하는 기능을 제공합니다. 차이점은 대기 해제 조건이 대기중인 스레드 수가 아니라는 것입니다. 대신에, 지정된 숫자가 0이 될 때 스레드가 해제됩니다.

CountDownLatch 클래스는 카운트를 감소시키는 메서드를 제공합니다. 동일한 스레드가 이 메서드를 여러 번 호출할 수 있습니다. 뿐만아니라, 대기중이 아닌 스레드도 이 메서드를 호출할 수 있습니다. 카운터가 0이 되면 모든 대기 스레드가 해제됩니다. 경우에 따라 대기중인 스레드가 없는 경우도 가능하며, 지정된 수 보다 많은 스레드가 대기하는 것도 가능합니다. 래치가 발생한 다음에도 대기를 시도하는 스레드는 즉시 해제됩니다. 래치는 초기화(reset)되지 않습니다. 뿐만 아니라, 래치가 발생한 후에는 카운트를 감소시키는 어떠한 시도도 동작하지 않습니다.

카운터다운 래치의 인터페이스는 다음과 같습니다.

public class CountDownLatch {
   public CountDownLatch(int count);
   public void await( ) throws InterruptedException;
   public boolean await(long timeout, TimeUnit unit)
                  throws InterruptedException;
   public void countDown( );
   public long getCount( );
}

인터페이스는 매우 간단합니다. 생성자에서 초기화할 숫자를 지정합니다. 오버로드 메서드 await()는 카운트가 0이 될 때까지 스레드를 대기시킵니다. countDown()과 getCount() 메서드는 카운트를 제어하는 기능을 제공합니다. ? countDown()은 카운트를 감소시키고, getCount()는 카운트를 조회합니다. timeout 변수가 있는 await() 메서드의 반환값이 boolean인 것은 래치가 발생했는지의 여부를 나타내기 위한 것입니다. ? 래치가 해제된 경우 true를 반환합니다.

익스체인저(Exchanger)

익스체인저는 다른 스레드 시스템에서 해당하는 것을 찾아볼 수 없는 동기화 도구를 구현한 것이다. 이 도구를 설명하는 가장 쉬운 방법은 데이터 전달을 하는 장벽의 조합이라고 얘기하는 것이다. 이것은 스레드 쌍이 서로를 만나도록(랑데뷰) 하는 장벽이다. 스레드가 쌍으로 만나게 되면 서로 데이터를 교환하고 각자의 작업을 수행한다.

***
역주: 현재 C#의 다음 버전을 위해 시험중인 Comega에 포함된 Polyphony C#을 기억한다면 익스체인저가 자바에서만 찾아볼 수 있는 것이 아님을 알 것이다. 다만, 상용 언어들중에 적용된 것이 드물뿐이다. Polyphony C#을 기억한다면 다음 코드를 살펴보기 바란다.

public class Buffer {
   public string Get() & public async Put( string s ) {
      return s;
   }
}

Get()이 스레드이며 Put()이 스레드이다. Put()이 여러번 수행되어도 Get()이 수행되지 않는 한 어떤 일도 발생하지 않는다. Get()이 1회 실행되면 처음 실행된 Put()과 쌍이 되어 처리가 발생하고 데이터를 교환할 수 있다. 위 예제는 Polyphony C#(또는 C# 3.0에 포함되리라 알려진)으로 구현할 수 있는 하나의 예이며 자바의 Exchanger 클래스는 Polyphony의 특정 구현에 해당한다.
***

익스체인저 클래스는 스레드간 데이터를 전달하는데 주로 사용되기 때문에 동기화 도구라기 보단 컬렉션 클래스에 가깝습니다. 스레드는 반드시 짝을 이뤄야하며, 지정된 데이터 타입이 교환되어야 합니다. 이럼에도 불구하고, 이 클래스는 나름대로의 장점을 갖고 있습니다.

public class Exchanger {
   public Exchanger( );
   public V exchange(V x) throws InterruptedException;
   public V exchange(V x, long timeout, TimeUnit unit)
          throws InterruptedException, TimeoutException;
}

exchange() 메서드는 데이터 객체와 함께 호출되어 다른 스레드와 데이터를 교환합니다. 다른 스레드가 이미 대기중이라면 exchange() 메서드는 다른 스레드의 데이터를 반환합니다. 대기 스레드가 없으면 exchange() 메서드는 대기 중인 스레드가 생길때까지 대기합니다. 만료시간(timeout) 옵션은 호출하는 스레드가 얼마나 오랜동안 대기할 것인가를 제어합니다.

장벽 클래스와 달리 Exchanger 클래스는 깨지는 경우가 발생하지 않기 때문에 매우 안전합니다. 얼마나 많은 스레드들이 Exchanger 클래스를 사용하는가는 중요하지 않습니다. 왜냐하면, 스레드가 들어오는 대로 짝을 이루기 때문입니다. 스레드는 간단하게 예외 조건을 생성할 수 있습니다. 익스체인저는 예외 조건까지 스레드들을 짝짓는 과정을 계속 수행합니다.

읽기/쓰기 잠금(Reader/Writer Locks)

때때로 시간이 오래 걸리는 작업에서 객체로부터 정보를 읽어올 때가 있습니다. 읽어들이는 정보가 변경되지 않게 잠금을 할 필요는 있지만, 다른 스레드가 정보를 읽는 것까지 막아버릴 필요는 없습니다. 모든 스레드가 데이터를 읽을 때는 각 스레드가 데이터를 읽는 것에 영향을 주지 않기 때문에 방해할 필요가 없습니다.

실제로, 데이터 잠금이 필요한 경우는 데이터를 변경할 때 뿐입니다. 즉, 데이터에 쓰기를 하는 경우입니다. 데이터를 변경하는 것은 데이터를 읽고 있는 스레드가 변경된 데이터를 읽게 할 가능성이 있습니다. 지금까지는 스레드가 읽기나 쓰기 작업을 하는 것에 관계없이 하나의 스레드가 데이터에 액세스하는 것을 허용하는 잠금이었습니다. 이론적으로 잠금은 매우 짧은 시간동안만 유지되야 합니다.

만약 잠금이 오랜 시간동안 유지된다면 다른 스레드들이 데이터를 읽는 것을 허용하는 것을 생각해볼 필요가 있습니다. 이렇게 하면 스레드들간에 잠금을 얻기 위해 경쟁할 필요가 없습니다. 물론, 우리는 데이터 쓰기에 대해서는 하나의 스레드만 잠금을 획득하게 해야 하지만, 데이터를 읽는 스레드들에 대해 그렇게 할 필요는 없습니다. 하나의 쓰기 스레드만 데이터의 내부 상태를 변경합니다.

J2SE 5.0에서 이러한 형태의 잠금을 제공하는 클래스와 인터페이스는 다음과 같습니다.

public interface ReadWriteLock {
   Lock readLock( );
   Lock writeLock( );
}

public class ReentrantReadWriteLock implements ReadWriteLock {
   public ReentrantReadWriteLock( );
   public ReentrantReadWriteLock(boolean fair);
   public Lock writeLock( );
   public Lock readLock( );
}

ReentractReadWriteLock 클래스를 사용하여 읽기-쓰기 잠금을 생성할 수 있습니다. ReentrantLock 클래스와 마찬가지로 이 선택사항은 잠금을 정당하게(fair) 분배합니다. "Fair"라는 의미대로 선착순, 먼저 도착한 스레드에게 먼저 잠금을 허용하는 것과 매우 가깝게 동작합니다. 잠금이 해제될 때 읽기/쓰기에 대한 다음 집합은 도착 시간을 기준으로 잠금을 얻습니다.

잠금의 사용은 예상할 수 있습니다. 읽기 스레드는 읽기 잠금을 획득하는 반면 쓰기 스레드는 쓰기 잠금을 획득합니다. 이 두 가지 잠금은 모두 Lock 클래스의 객체입니다. 그러나, 한기지 주요한 차이점은 읽기/쓰기 락은 상태 변수에 대한 다른 지원을 한다는 것입니다. newCondition() 메서드를 호출하여 쓰기 잠금과 관련된 상태 변수를 획득할 수 있으며, 읽기 잠금에 대해 newCondition() 메서드를 호출하면 UnsupportedOperationException이 발생합니다.

이들 잠금은 중첩이 가능합니다. 즉, 잠금의 소유자가 필요에 따라 반복적으로 잠금을 획득할 수 있습니다. 이러한 특성 때문에 콜백이나 다른 복잡한 알고리즘을 안전하게 수행할 수 있습니다. 뿐만아니라, 쓰기 잠금을 가진 스레드는 읽기 잠금도 획득할 수 있습니다. 그러나, 그 반대는 성립하지 않습니다. 읽기 잠금을 획득한 스레드가 쓰기 잠금을 획득할 수 없으며, 잠금을 업그레이드하는 것도 허용되지 않습니다. 그러나 잠금을 다운그레이드하는 것은 허용됩니다. 즉, 이것은 쓰기 잠금을 해제하기 전에 읽기 잠금을 획득하는 경우에 수행됩니다.

이 장의 뒤에서는 궁핍현상(lock starvation)에 대해 자세히 살펴볼 것입니다. 읽기-쓰기 잠금은 궁핍현상에서 특별한 의미를 가집니다.
이 절에서는 J2SE 5.0에서 제공하는 보다 높은 수준의 동기화 도구를 살펴볼 것입니다. 이러한 기능들은 이전 버전의 자바에서 제공하는 기능들로 직접 구현해야 했던 것입니다. 또는 서드 파티 제품에서 작성한 도구들을 사용해야 했습니다. 이들 클래스들은 과거에는 수행할 수 없었던 새 기능들을 제공하지 않지만 완전히 자바로 작성되었습니다. 그런 점에서는 편리한 클래스입니다. 다시 말해서, 개발을 보다 쉽게 하고 보다 높은 수준에서 응용 프로그램 개발을 할 수 있게 하기 위해 설계되었습니다.

이들 클래스들간에는 중복되는 부분이 상당히 많습니다. Semaphore는 하나의 허가권을 가진 세머포어를 선언하는 것으로 부분적으로는 Lock을 시뮬레이트하는데 사용할 수 있습니다. 읽기-쓰기 잠금의 쓰기 잠금은 부분적으로 상호 배제 잠금(mutually exclusive lock - 약자로 뮤텍스)을 구현하고 있습니다. 세머포어도 읽기-쓰기 잠금을 시뮬레이트하기 위해 사용될 수 있습니다. 카운트다운 래치도 각 스레드가 대기 하기전에 카운트를 감소시키는 장벽처럼 사용될 수 있습니다.

이들 클래스를 사용하여 얻을 수 있는 주된 장점은 스레드와 데이터 동기화 문제를 해결해 준다는 것입니다. 개발자들은 가능한한 높은 수준에서 프로그램을 설계하고, 낮은 수준의 스레드 문제에 대해 걱정하지 않아도 됩니다. 교착상태의 가능성, 잠금과 CPU 궁핍현상을 비롯한 복잡한 문제들에 대한 것들을 어느 정도 벗어버릴 수 있습니다. 그러나, 이러한 라이브러리를 사용하는 것이 개발자로 하여금 이러한 문제에 대한 책임을 완전히 제거하는 것은 아닙니다.

'java core' 카테고리의 다른 글

Runtime in jdk5.0  (0) 2005.03.26
Annotation  (0) 2005.03.18
Threads from aritma  (0) 2005.02.12
Reference object model from java world  (0) 2005.01.28
Garbage collection from javaworld  (0) 2005.01.28
Posted by '김용환'
,

Threads from aritma

java core 2005. 2. 12. 08:15
 
From
 
Objects and Java Seminar by Bill Venners
Threads
Lecture Handout

Agenda

  • Introduce multi-threading
  • Show two ways to start a thread
  • Talk about synchronization for mutual exclusion
  • Discuss thread cooperation
  • Look at the Java monitor
  • Look at thread blocking, liveness, and scheduling

Multi-Threading in Java

  • Java has support for multi-threading built into the language
  • Threads are "sub-processes" within a process
    • User-interface responsiveness
    • Server responsiveness
    • Can take advantage of multi-processors
  • Each process has a private data segment. Threads share the data segment of their process.
  • Two kinds of synchronization: mutual exclusion and co-operation

Subclassing Thread

  • In Java, threads are represented by an instance of class java.lang.Thread
  • Two ways to define a thread starting point: extend Thread or implementing Runnable

     1 // In file threads/ex1/RepetitiveThread.java
     2 public class RepetitiveThread extends Thread {
     3
     4     private final String msg;
     5     private final long sleepTime;
     6
     7     public RepetitiveThread(String msg, long sleepTime) {
     8         this.msg = msg;
     9         this.sleepTime = sleepTime;
    10     }
    11
    12     public void run() {
    13
    14         for (;;) {
    15
    16             System.out.println(msg);
    17             try {
    18                 sleep(sleepTime);
    19             }
    20             catch (InterruptedException e) {
    21             }
    22         }
    23     }
    24 }
    
     1 // In file threads/ex1/Example1.java
     2 public class Example1 {
     3
     4     // Args to this application specify "msg"
     5     // and "sleepTime" for multiple threads.
     6     // For example, the command:
     7     //
     8     // $ java Example1 Hi 100 Lo 1000
     9     //
    10     // requests two threads, one that prints
    11     // out "Hi" every 100 milliseconds and
    12     // another that prints out "Lo" every
    13     // 1000 milliseconds.
    14     //
    15     public static void main(String[] args) {
    16
    17         // Require an even argCount
    18         int argCount = args.length;
    19         if ((argCount / 2) == 1) {
    20             --argCount;
    21         }
    22
    23         for (int i = 0; i < argCount; i += 2) {
    24
    25             String msg = args[i];
    26             long sleepTime = Long.parseLong(args[i + 1]);
    27
    28             RepetitiveThread rt =
    29                 new RepetitiveThread(msg, sleepTime);
    30
    31             rt.start();
    32         }
    33     }
    34 }
    
  • Java applications keep running until there are no more non-daemon threads.
  • Extending Thread often difficult because its hard to fit Thread into the inheritance hierarchy.

Implementing Runnable

  • Often more flexible to implement Runnable than extend Thread:

    1 // In file threads/ex2/Animal.java
    2 public class Animal {
    3 }
    
     1 // In file threads/ex2/Cat.java
     2 public class Cat extends Animal implements Runnable {
     3
     4     private final String msg;
     5     private final long sleepTime;
     6
     7     public Cat(String msg, long sleepTime) {
     8         this.msg = msg;
     9         this.sleepTime = sleepTime;
    10     }
    11
    12     public void run() {
    13
    14         for (;;) {
    15
    16             System.out.println(msg);
    17             try {
    18                 Thread.sleep(sleepTime);
    19             }
    20             catch (InterruptedException e) {
    21             }
    22         }
    23     }
    24 }
    
     1 // In Source Packet in file threads/ex2/Example2.java
     2 public class Example2 {
     3
     4     // Args to this application specify "msg"
     5     // and "sleepTime" for multiple threads.
     6     // For example, the command:
     7     //
     8     // $ java Example1 Meow 100 Grrr 1000
     9     //
    10     // requests two threads, one that prints
    11     // out "Meow" every 100 milliseconds and
    12     // another that prints out "Grrr" every
    13     // 1000 milliseconds.
    14     //
    15     public static void main(String[] args) {
    16
    17         // Require an even argCount
    18         int argCount = args.length;
    19         if ((argCount / 2) == 1) {
    20             --argCount;
    21         }
    22
    23         for (int i = 0; i < argCount; i += 2) {
    24
    25             String msg = args[i];
    26             long sleepTime = Long.parseLong(args[i + 1]);
    27
    28             Cat cat = new Cat(msg, sleepTime);
    29
    30             Thread catThread = new Thread(cat);
    31             catThread.start();
    32         }
    33     }
    34 }
    

Mutual Exclusion

  • Java has an object-oriented way to deal with thread synchronization.
  • Data is protected by controlling access to code. (Hence, the data must be private.)
  • Can mark blocks of code, or entire methods, as synchronized.
  • Synchronized means only one thread at a time can execute the code.

The Thread-Safe Object

  • A state machine RGBColor object (not thread-safe)

 1 // In file objectidioms/ex6/RGBColor.java
 2 // Instances of this class are NOT thread-safe.
 3
 4 public class RGBColor {
 5
 6     private int r;
 7     private int g;
 8     private int b;
 9
10     public RGBColor(int r, int g, int b) {
11
12         checkRGBVals(r, g, b);
13
14         this.r = r;
15         this.g = g;
16         this.b = b;
17     }
18
19     public void setColor(int r, int g, int b) {
20
21         checkRGBVals(r, g, b);
22
23         this.r = r;
24         this.g = g;
25         this.b = b;
26     }
27
28     /**
29     * returns color in an array of three ints: R, G, and B
30     */
31     public int[] getColor() {
32
33         int[] retVal = new int[3];
34         retVal[0] = r;
35         retVal[1] = g;
36         retVal[2] = b;
37
38         return retVal;
39     }
40
41     public void invert() {
42
43         r = 255 - r;
44         g = 255 - g;
45         b = 255 - b;
46     }
47
48     private static void checkRGBVals(int r, int g, int b) {
49
50         if (r < 0 || r > 255 || g < 0 || g > 255 ||
51             b < 0 || b > 255) {
52
53             throw new IllegalArgumentException();
54         }
55     }
56 }

Write/Write Conflicts

Thread Statement r g b Color
none object represents green 0 255 0  GREEN 
blue blue thread invokes setColor(0, 0, 255) 0 255 0  GREEN 
blue checkRGBVals(0, 0, 255); 0 255 0  GREEN 
blue this.r = 0; 0 255 0  GREEN 
blue this.g = 0; 0 255 0  GREEN 
blue blue gets preempted 0 0 0  BLACK 
red red thread invokes setColor(255, 0, 0) 0 0 0  BLACK 
red checkRGBVals(255, 0, 0); 0 0 0  BLACK 
red this.r = 255; 0 0 0  BLACK 
red this.g = 0; 255 0 0  RED 
red this.b = 0; 255 0 0  RED 
red red thread returns 255 0 0  RED 
blue later, blue thread continues 255 0 0  RED 
blue this.b = 255 255 0 0  RED 
blue blue thread returns 255 0 255  MAGENTA 
none object represents magenta 255 0 255  MAGENTA 

Read/Write Conflicts

Thread Statement r g b Color
none object represents green 0 255 0  GREEN 
blue blue thread invokes setColor(0, 0, 255) 0 255 0  GREEN 
blue checkRGBVals(0, 0, 255); 0 255 0  GREEN 
blue this.r = 0; 0 255 0  GREEN 
blue this.g = 0; 0 255 0  GREEN 
blue blue gets preempted 0 0 0  BLACK 
red red thread invokes getColor() 0 0 0  BLACK 
red int[] retVal = new int[3]; 0 0 0  BLACK 
red retVal[0] = 0; 0 0 0  BLACK 
red retVal[1] = 0; 0 0 0  BLACK 
red retVal[2] = 0; 0 0 0  BLACK 
red return retVal; 0 0 0  BLACK 
red red thread returns black 0 0 0  BLACK 
blue later, blue thread continues 0 0 0  BLACK 
blue this.b = 255 0 0 0  BLACK 
blue blue thread returns 0 0 255  BLUE 
none object represents blue 0 0 255  BLUE 

Thread-Safe RGBColor Object

 1 // In file objectidioms/ex7/RGBColor.java
 2 // Instances of this class are thread-safe.
 3
 4 public class RGBColor {
 5
 6     private int r;
 7     private int g;
 8     private int b;
 9
10     public RGBColor(int r, int g, int b) {
11
12         checkRGBVals(r, g, b);
13
14         this.r = r;
15         this.g = g;
16         this.b = b;
17     }
18
19     public void setColor(int r, int g, int b) {
20
21         checkRGBVals(r, g, b);
22
23         synchronized (this) {
24
25             this.r = r;
26             this.g = g;
27             this.b = b;
28         }
29     }
30
31     /**
32     * returns color in an array of three ints: R, G, and B
33     */
34     public int[] getColor() {
35
36         int[] retVal = new int[3];
37
38         synchronized (this) {
39
40             retVal[0] = r;
41             retVal[1] = g;
42             retVal[2] = b;
43         }
44
45         return retVal;
46     }
47
48     public synchronized void invert() {
49
50         r = 255 - r;
51         g = 255 - g;
52         b = 255 - b;
53     }
54
55     private static void checkRGBVals(int r, int g, int b) {
56
57         if (r < 0 || r > 255 || g < 0 || g > 255 ||
58             b < 0 || b > 255) {
59
60             throw new IllegalArgumentException();
61         }
62     }
63 }

Ready for Threads

Thread Statement r g b Color
none object represents green 0 255 0  GREEN 
blue blue thread invokes setColor(0, 0, 255) 0 255 0  GREEN 
blue checkRGBVals(0, 0, 255); 0 255 0  GREEN 
blue blue thread acquires lock 0 255 0  GREEN 
blue this.r = 0; 0 255 0  GREEN 
blue this.g = 0; 0 255 0  GREEN 
blue blue gets preempted 0 0 0  BLACK 
red red thread invokes setColor(255, 0, 0) 0 0 0  BLACK 
red checkRGBVals(255, 0, 0); 0 0 0  BLACK 
red red thread blocks because object locked 0 0 0  BLACK 
blue later, blue thread continues 0 0 0  BLACK 
blue this.b = 255 0 0 0  BLACK 
blue blue thread returns and releases lock 0 0 255  BLUE 
red later, red thread acquires lock and continues 0 0 255  BLUE 
red this.r = 255; 0 0 255  BLUE 
red this.g = 0; 255 0 255  MAGENTA 
red this.b = 0; 255 0 255  MAGENTA 
red red thread returns and releases lock 255 0 0  RED 
none object represents red 255 0 0  RED 

The Thread-Safe Object

  • Make instance variables private
  • Figure out what the monitor regions should be and mark them synchronized
  • Make objects thread-safe only if they'll actually be used in a multi-threaded environment
  • Why? Performance hit from acquiring the lock and the possibility of deadlock

Synchronized Class Methods

  • Can also synchronize class methods, as in:

    // In file Cat.java
    public class Cat {
        public static final int MAX_LIVES = 9;
        private static Cat[] lives = new Cat[MAX_LIVES];
        public static synchronized Cat[] getLives() {
            return lives;
        }
        //...
    }
    
  • To enter a synchronized class method, must lock the class's java.lang.Class object.

Thread Cooperation

  • Mutual exclusion is only half of the thread synchronization story: Java also supports thread cooperation.
  • Example: Producer thread and consumer thread

    Thread Action Data
    consumer Any Data? none
    consumer WAIT none
    producer Buffer Full? none
    producer Give 1, 2, 3
    producer NOTIFY 1, 2, 3
    producer Process 1, 2, 3
    consumer Any Data? 1, 2, 3
    consumer Take none
    consumer NOTIFY none
    consumer Process none
    consumer Any Data? none
    consumer WAIT none
    producer Buffer Full? none
    producer Give 5, 7, 11
    producer NOTIFY 5, 7, 11
    producer Process 5, 7, 11
    producer Buffer Full? 5, 7, 11
    producer WAIT 5, 7, 11
    consumer Any Data? 5, 7, 11
    consumer Take none
    consumer NOTIFY none
    consumer Process none
    producer Buffer Full? none
    producer Give 13, 17, 19
    producer NOTIFY 13, 17, 19
    producer Process 13, 17, 19
    consumer Any Data? 13, 17, 19
    consumer Take none
    consumer NOTIFY none
    consumer Process none
    consumer Any Data? none
    consumer WAIT none

The Java Monitor

  • A monitor is like a building that contains one special room (which usually contains some data) that can be occupied by only one thread at a time.


Cooperation Example

 1 // In file threads/ex6/IntBuffer.java
 2 public class IntBuffer {
 3
 4     private final int buffSize;
 5     private int[] buff;
 6
 7     // Keeps track of next buff array location
 8     // to be filled. When nextBuffIndex ==
 9     // buffSize, the buffer is full. When
10     // nextBuffIndex == 0, the buffer is
11     // empty.
12     private int nextBuffIndex;
13
14     IntBuffer(int buffSize) {
15
16         this.buffSize = buffSize;
17         buff = new int[buffSize];
18     }
19
20     public synchronized void add(int val) {
21
22         while (nextBuffIndex == buffSize) {
23
24             try {
25                 wait();
26             }
27             catch (InterruptedException e) {
28             }
29         }
30
31         buff[nextBuffIndex] = val;
32         ++nextBuffIndex;
33
34         notifyAll();
35     }
36
37     public synchronized int removeNext() {
38
39         while (nextBuffIndex == 0) {
40
41             try {
42                 wait();
43             }
44             catch (InterruptedException e) {
45             }
46         }
47
48         // This buffer is FIFO, so remove the
49         // first int added and shift the rest
50         // over.
51         int val = buff[0];
52
53         --nextBuffIndex;
54         for (int i = 0; i < nextBuffIndex; ++i) {
55
56             buff[i] = buff[i + 1];
57         }
58
59         notifyAll();
60         return val;
61     }
62 }

 1 // In file threads/ex6/PrimeNumberGenerator.java
 2 public class PrimeNumberGenerator implements Runnable {
 3
 4     private final IntBuffer buff;
 5
 6     public PrimeNumberGenerator(IntBuffer buff) {
 7
 8         this.buff = buff;
 9     }
10
11     public void run() {
12
13         int primeNum = 1;
14         int numToCheck = 2;
15
16         buff.add(primeNum);
17
18         for (;;) {
19
20             boolean foundPrime = true;
21
22             for (int divisor = numToCheck / 2; divisor > 1;
23                 --divisor) {
24
25                 if (numToCheck % divisor == 0) {
26                     foundPrime = false;
27                     break;
28                 }
29             }
30
31             if (foundPrime) {
32                 primeNum = numToCheck;
33                 buff.add(primeNum);
34             }
35
36             ++numToCheck;
37         }
38     }
39 }

 1 // In source packet in file threads/ex6/IntPrinter.java
 2 public class IntPrinter implements Runnable {
 3
 4     private final IntBuffer buff;
 5
 6     public IntPrinter(IntBuffer buff) {
 7
 8         this.buff = buff;
 9     }
10
11     public void run() {
12
13         for (;;) {
14
15             int val = buff.removeNext();
16             System.out.println(val);
17         }
18     }
19 }

 1 // In file threads/ex6/Example6.java
 2 public class Example6 {
 3
 4     public static void main(String[] args) {
 5
 6         IntBuffer buff = new IntBuffer(3);
 7
 8         PrimeNumberGenerator png = new PrimeNumberGenerator(buff);
 9         IntPrinter ip = new IntPrinter(buff);
10
11         Thread producer = new Thread(png);
12         Thread consumer = new Thread(ip);
13
14         producer.start();
15         consumer.start();
16     }
17 }

Thread Blocking

  • A thread can be in any of 4 states:

    • new
    • runnable
    • dead
    • blocked

  • A thread can be blocked for any of 4 reasons:

    • Sleeping (the thread invoked sleep())
    • In entry set of a monitor (the thread invoked a synchronized method)
    • In wait set of a monitor (the thread invoked wait())
    • Waiting for an I/O operation

Program Liveness

  • Liveness means a program will isn't "hung" and will eventually do something useful.
  • A multi-threaded program can lose its liveness in several ways:

    • Deadlock
    • Unsatisfied wait condition
    • Starvation

  • Thread safety often conflicts with thread liveness.

    • If no synchronized methods, program can't deadlock.

Thread Scheduling

  • The JVM holds non-blocked threads in priority-based scheduling queues.

    • By default, each new thread gets the same priority as its creator.
    • Can change a thread's priority by invoking setPriority().

  • JVMs are encouraged to:

    • Cycle through highest priority threads (not necessarily in a fair way).
    • Preempt lower priority threads in favor of higher priority threads.

  • Invoking yield() indicates to the JVM that you are ready for a rest.
  • Don't depend on "time-slicing" for program correctness.

Exercise: The Dreaded, Threaded Fibonacci Generator

Create a Java application named Problem1 that generates the Fibonacci sequence. The first two numbers of the Fibonacci sequence are 1 and 1. Each subsequent number is calculated by summing the previous two numbers, as in: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, and so on.

Input to the Application

The Problem1 application will write the Fibonacci sequence to the standard output. The Problem1 application, which requires no command line arguments, should print out the first 92 Fibonacci numbers The output will look like:

1
1
2
3
5
8
13
<...>
The maximum of 92 arises because the 93rd Fibonacci number is too big to express in a Java long. The biggest Fibonacci number that will fit in Java's long (a 64 bit signed integer) is 7540113804746346429L, which is the 92nd Fibonacci number.

Structure of the Application

The application will be made up of four classes, named:

FibonacciGenerator.java
LongBuffer.java
LongBufferToOutputThread.java
Problem1.java

The application will contain three threads, the main thread and two extra threads that the main thread will start. The two extra threads are defined by FibonacciGenerator, which implements Runnable, and LongBufferToOutputThread, which directly subclasses class Thread.

The main() method of the Problem1 application will create and start these two threads and connect the output of the FibonacciGenerator thread to the input of the LongBufferToOutputThread. The Fibonacci numbers will be generated by the FibonacciGenerator thread, which writes one long value at time into a LongBuffer. The LongBufferToOutputThread will then read long's from the LongBuffer and write them to the standard output.

Classes of the Application

Class Problem1

The main() method should:

  • Create a LongBuffer object with a buffer size of 3.
  • Create a FibonacciGenerator. object
  • Create a LongBufferToOutputThread object.
  • Start the FibonacciGenerator and LongBufferToOutputThread threads.
  • This main thread is now finished and can just return from the main() method.

Class LongBuffer

You can base this class on the IntBuffer class from the lecture slides, which is in the Threads/examples/ex6 directory of the sample code:

// In source packet in file threads/ex6/IntBuffer.java
public class IntBuffer {
    private final int buffSize;
    private int[] buff;
    // Keeps track of next buff array location
    // to be filled. When nextBuffIndex ==
    // buffSize, the buffer is full. When
    // nextBuffIndex == 0, the buffer is
    // empty.
    private int nextBuffIndex;
    IntBuffer(int buffSize) {
        this.buffSize = buffSize;
        buff = new int[buffSize];
    }
    public synchronized void add(int val) {
        while (nextBuffIndex == buffSize) {
            try {
                wait();
            }
            catch (InterruptedException e) {
            }
        }
        buff[nextBuffIndex] = val;
        ++nextBuffIndex;
        notifyAll();
    }
    public synchronized int removeNext() {
        while (nextBuffIndex == 0) {
            try {
                wait();
            }
            catch (InterruptedException e) {
            }
        }
        // This buffer is FIFO, so remove the
        // first int added and shift the rest
        // over.
        int val = buff[0];
        --nextBuffIndex;
        for (int i = 0; i < nextBuffIndex; ++i) {
            buff[i] = buff[i + 1];
        }
        notifyAll();
        return val;
    }
}

Basically, LongBuffer has to do a similar thing to what IntBuffer does, but for longs instead of ints. It needs an add() method and a long removeNext() method, and it must assume different threads will be calling these methods. Thus, the add() and removeNext() methods must be synchronized and use wait() and notifyAll().

Class FibonacciGenerator

This class extends Object and implements Runnable. It has one constructor, which takes one argument: a LongBuffer reference.

It's run() method simply produces the Fibonacci sequence one long at a time and writes each one to the LongBuffer as it is produced. To indicate that it is finished producing numbers, the FibonacciGenerator class declares a public static final int END_OF_DATA field that is initialized to -1. When the FibonacciGenerator's run() method is done generating the first 92 Fibonacci numbers, it writes an END_OF_DATA to the LongBuffer. After that, this thread is finished and the run() method simply returns.

Class LongBufferToOutputThread

This class extends Thread. It has one constructor, which takes one argument: a LongBuffer.

It's run() method simply reads one long at a time from the LongBuffer and writes it as a String to the standard output, placing a return ('\n') after each number it prints. It keeps doing this until it reads an FibonacciGenerator.END_OF_DATA from the LongBuffer. When it finds END_OF_DATA, the run() method returns, and this thread expires.

Odds and Ends

How the app knows to terminate: A Java application terminates when all non-daemon threads expire. In this application, there are three non-daemon threads. The main thread sets up and starts the other two threads, then returns. One thread down. The FibonacciGenerator thread generates the numbers, stores them into the LongBuffer, then writes an END_OF_DATA into the LongBuffer, and returns. By returning from run(), the FibonacciGenerator thread expires. Two threads down. The LongBufferToOutputThread reads from the LongBuffer and writes to the standard output until it finds an END_OF_DATA in the LongBuffer. It then returns. By returning from run(), the LongBufferToOutputThread thread expires. Because this is the third and only remaining non-daemon thread, the entire application terminates.

'java core' 카테고리의 다른 글

Annotation  (0) 2005.03.18
Java condition variable  (0) 2005.02.18
Reference object model from java world  (0) 2005.01.28
Garbage collection from javaworld  (0) 2005.01.28
자바 코드 컨벤션  (0) 2005.01.24
Posted by '김용환'
,

from : http://www.javaworld.com/javaworld/jw-01-2002/jw-0104-java101.html

Trash talk, Part 2

The Reference Objects API allows programs to interact with the garbage collector

Summary
This month, Jeff Friesen explores the Reference Objects API, an API that allows your programs to interact with the garbage collector in limited ways. He shows you how to use that API to manage image caches, obtain notification when significant objects are no longer strongly reachable, and perform post-finalization cleanup.

For answers to last month's homework, for this month's homework, and for additional material related to this article, visit the associated study guide. (3,700 words; January 4, 2002)

By Jeff Friesen


Printer-friendly version
Printer-friendly version | Send this article to a friend Mail this to a friend


Advertisement

Java's garbage collection features tend to confuse new developers. I wrote this two-part series on garbage collection to dispel that confusion. Part 1 introduced you to garbage collection, explored various garbage collection algorithms, showed you how to request that Java run the garbage collector, explained the purpose behind finalization, and mentioned resurrection -- a technique for bringing objects back from the dead. Part 2 explores the Reference Objects API.

Read the whole series on garbage collection:

As you learned in Part 1, Java's garbage collector destroys objects. Although you typically write programs that ignore the garbage collector, situations arise in which a program needs to interact with the garbage collector.

For example, suppose you plan to write a Java-based Web browser program similar to Netscape Navigator or Internet Explorer. When it comes to displaying Webpage images, your first thought is for the browser to always download all images before displaying them to a user. However, you soon realize that the user will spend too much time waiting for images to download. Although a user might be willing to wait when visiting a Webpage for the first time, the user would probably not tolerate waiting each time he revisits the Webpage. To decrease the user's wait time, you can design the browser program to support an image cache, which allows the browser to save each image after the download completes in an object on the object heap. The next time the user visits the Webpage in the same browsing session, the browser can retrieve the corresponding image objects from the object heap and quickly display those images to the user.

Note
To keep the discussion simple, I don't discuss a second-level disk-based image cache mechanism. Browsers like Netscape Navigator and Internet Explorer use this mechanism.

The image cache idea features a problem -- insufficient heap memory. When the user visits numerous Webpages with different sized images, the browser must store all images in heap memory. At some point, the heap memory will decrease to a level where no more room exists for images. What does the browser do? By taking advantage of the Reference Objects API, the browser allows the garbage collector to remove images when the JVM needs additional heap space. In turn, when the browser needs to redraw an image, the garbage collector tells the browser if that image is no longer in memory. If the image is not in memory, the browser must first reload that image. The browser can then restore that image to the image cache -- although the garbage collector might need to remove another image from the cache to make heap memory available for the original image, assuming the object heap's free memory is low.

In addition to teaching you how to use the Reference Objects API to manage an image cache, this article teaches you how to use that API to obtain notification when significant objects are no longer strongly reachable and perform post-finalization cleanup. But first, we must investigate object states and the Reference Objects API class hierarchy.

Object states and the Reference Objects API class hierarchy
Prior to the release of Java 2 Platform, Standard Edition (J2SE) 1.2, an object could be in only one of three states: reachable, resurrectable, or unreachable:

  • An object is reachable if the garbage collector can trace a path from a root-set variable to that object. When the JVM creates an object, that object stays initially reachable as long as a program maintains at least one reference to the object. Assigning null to an object reference variable reduces the object's references by one. For example:

    Employee e = new Employee (); Employee e2 = e; e = null;

    In the above code fragment, the Employee object is initially reachable through e. Then it is reachable through e2 as well as through e. After null assigns to e, the object is only reachable through e2.

  • An object is resurrectable if it is currently unreachable through root-set variables, but has the potential to be made reachable through a garbage collector call to that object's overridden finalize() method. Because finalize()'s code can make the object reachable, the garbage collector must retrace all paths from root-set variables in an attempt to locate the object after finalize() returns. If the garbage collector cannot find a path to the object, it makes the object unreachable. If a path does exist, the garbage collector makes the object reachable. If the object is made reachable, the garbage collector will not run its finalize() method a second time when no more references to that object exist. Instead, the garbage collector makes that object unreachable.

  • An object is unreachable when no path from root-set variables to that object exists and when the garbage collector cannot call that object's finalize() method. The garbage collector is free to reclaim the object's memory from the heap.

With the release of J2SE 1.2, three new object states representing progressively weaker forms of reachability became available to Java: softly reachable, weakly reachable, and phantomly reachable. Subsequent sections explore each of those states.

Note
Also with the J2SE 1.2 release, the state previously known as reachable became known as strongly reachable. For example, in code fragment Employee e = new Employee ();, the Employee object reference in root-set variable e (assuming e is a local variable) is strongly reachable through e.

The new object states became available to Java through reference objects. A reference object encapsulates a reference to another object, a referent. Furthermore, the reference object is a class instance that subclasses the abstract Reference class in the Reference Objects API -- a class collection in package java.lang.ref. Figure 1 presents a hierarchy of reference object classes that constitute much of the Reference Objects API.


Figure 1. A hierarchy of reference object classes composes much of the Reference Objects API

Figure 1's class hierarchy shows a class named Reference at the top and SoftReference, WeakReference, and PhantomReference classes below. The abstract Reference class defines those operations common to the other three classes. Those operations include:

  • Clear the current reference object

  • Add the current reference object to the currently registered reference queue

  • Return the current reference object's referent

  • Determine if the garbage collector has placed the current reference object on a reference queue

The aforementioned operations introduce a reference queue. What are reference queues, and why are they part of the Reference Objects API? I'll answer both questions during our exploration of soft references.

Soft references
The softly reachable state manifests itself in Java through the SoftReference class. When you initialize a SoftReference object, you store a reference to a referent in that object. The object contains a soft reference to the referent, and the referent is softly reachable if there are no other references, apart from soft references, to that referent. If heap memory is running low, the garbage collector can find the oldest softly reachable objects and clear their soft references -- by calling SoftReference's inherited clear() method. Assuming there are no other references to those referents, the referents enter the resurrectable state (if they contain overridden finalize() methods) or the unreachable state (if they lack overridden finalize() methods). Assuming the referents enter the resurrectable state, the garbage collector calls their finalize() methods. If those methods do not make the referents reachable, the referents become unreachable. The garbage collector can then reclaim their memory.

To create a SoftReference object, pass a reference to a referent in one of two constructors. For example, the following code fragment uses the SoftReference(Object referent) constructor to create a SoftReference object, which encapsulates an Employee referent:

SoftReference sr = new SoftReference (new Employee ());

Figure 2 shows the resulting object structure.


Figure 2. A SoftReference object and its Employee referent

According to Figure 2, the SoftReference object is strongly reachable through root-set variable sr. Also, the Employee object is softly reachable from the soft reference field inside SoftReference.

You often use soft references to implement image and other memory-sensitive caches. You can create an image cache by using the SoftReference and java.awt.Image classes. Image subclass objects allow images to load into memory. As you probably know, images can consume lots of memory, especially if they have large horizontal and vertical pixel dimensions and many colors. If you kept all such images in memory, the object heap would quickly fill up, and your program would grind to a halt. However, if you maintain soft references to Image subclass objects, your program can arrange for the garbage collector to notify you when it clears an Image subclass object's soft reference and moves it to the resurrectable state -- assuming no other references to Image exist. Eventually, assuming the Image subclass object lacks a finalize() method with code that resurrects the image, Image will transition to the unreachable state, and the garbage collector will reclaim its memory.

By calling SoftReference's inherited get() method, you can determine if an Image subclass object is still softly referenced or if the garbage collector has cleared that reference. get() returns null when the soft reference clears. Given the preceding knowledge, the following code fragment shows how to implement an image cache for a single Image subclass object:

SoftReference sr = null;

// ... Sometime later in a drawing method.

Image im = (sr == null) ? null : (Image) (sr.get());

if (im == null)
{
    im = getImage (getCodeBase(), "truck1.gif");
    sr = new SoftReference (im);
}

// Draw the image.

// Later, clear the strong reference to the Image subclass object.
// That is done, so -- assuming no other strong reference exists --
// the only reference to the Image subclass object is a soft
// reference. Eventually, when the garbage collector notes that it
// is running out of heap memory, it can clear that soft reference
// (and eventually remove the object).

im = null;

The code fragment's caching mechanism works as follows: To begin, there is no SoftReference object. As a result, null assigns to im. Because im contains null, control passes to the getImage() method, which loads truck1.gif. Next, the code creates a SoftReference object. As a result, there is both a strong reference (via im) and a soft reference (via sr) to the Image subclass object. After the code draws the image, null assigns to im. Now there is only a single soft reference to Image. If the garbage collector notices that free memory is low, it can clear Image's soft reference in the SoftReference object that sr strongly references.

Suppose the garbage collector clears the soft reference. The next time the code fragment must draw the image, it discovers that sr lacks null and calls sr.get () to retrieve a strong reference to Image -- the referent. Assuming the soft reference is now null, get() returns null, and null assigns to im. We can now reload the image by calling getImage() -- a relatively slow process. However, if the garbage collector did not clear the soft reference, sr.get() would return a reference to the Image subclass object. Then we could immediately draw that image without first loading it. And that is how a soft reference allows us to cache an image.

The previous code fragment called sr.get() to learn whether or not the garbage collector cleared the sr-referenced object's internal soft reference to an Image subclass object. However, a program can also request notification by using a reference queue -- a data structure that holds references to Reference subclass objects. Under garbage collector (or even program) control, Reference subclass object references arrive at the end of the reference queue and exit from that queue's front. As a reference exits from the front, the following reference moves to the front, and other references move forward. Think of a reference queue as a line of people waiting to see a bank teller.

To use a reference queue, a program first creates an object from the ReferenceQueue class (located in the java.lang.ref package). The program then calls the SoftReference(Object referent, ReferenceQueue q) constructor to associate a SoftReference object with the ReferenceQueue object that q references, as the following code fragment demonstrates:

ReferenceQueue q = new ReferenceQueue ();
SoftReference sr = new SoftReference (new Employee (), q);

After the garbage collector clears the referent's soft reference, it appends SoftReference's strong reference to the reference queue that q references. The addition of SoftReference happens when the garbage collector calls Reference's enqueue() method (behind the scenes). Your program can either poll the reference queue, by calling ReferenceQueue's poll() method, or block, by calling ReferenceQueue's no-argument remove() method, until the reference arrives on the queue. At that point, your program can either stop polling or automatically unblock. Because the poll() and remove() methods return a SoftReference object reference (through the Reference return types), you discover which soft reference cleared; you can then modify your cache as appropriate.

To demonstrate reference queues and soft references, I have created a SoftReferenceDemo application, which chooses to call poll() instead of remove() to increase the likelihood of garbage collection. If the application was to call remove(), the garbage collector might not run -- because the application doesn't constantly create objects and nullify their references. Hence, the application would remain blocked. Examine SoftReferenceDemo's source code in Listing 1:

Listing 1. SoftReferenceDemo.java

// SoftReferenceDemo.java

import java.lang.ref.*;

class Employee
{
   private String name;

   Employee (String name)
   {
      this.name = name;
   }

   public String toString ()
   {
      return name;
   }
}

class SoftReferenceDemo
{
   public static void main (String [] args)
   {
      // Create two Employee objects that are strongly reachable from e1
      // and e2.

      Employee e1 = new Employee ("John Doe");
      Employee e2 = new Employee ("Jane Doe");

      // Create a ReferenceQueue object that is strongly reachable from q.

      ReferenceQueue q = new ReferenceQueue ();

      // Create a SoftReference array with room for two references to
      // SoftReference objects. The array is strongly reachable from sr.

      SoftReference [] sr = new SoftReference [2];

      // Assign a SoftReference object to each array element. That object
      // is strongly reachable from that element. Each SoftReference object
      // encapsulates an Employee object that is referenced by e1 or e2 (so
      // the Employee object is softly reachable from the SoftReference
      // object), and associates the ReferenceQueue object, referenced by
      // q, with the SoftReference object.

      sr [0] = new SoftReference (e1, q);
      sr [1] = new SoftReference (e2, q);

      // Remove the only strong references to the Employee objects.

      e1 = null;
      e2 = null;

      // Poll reference queue until SoftReference object arrives.

      Reference r;
      while ((r = q.poll ()) == null)
      {
         System.out.println ("Polling reference queue");

         // Suggest that the garbage collector should run.

         System.gc ();
      }

      // Identify the SoftReference object whose soft reference was
      // cleared, and print an appropriate message.

      if (r == sr [0])
          System.out.println ("John Doe Employee object's soft reference " +
                              "cleared");
      else
          System.out.println ("Jane Doe Employee object's soft reference " +
                              "cleared");

      // Attempt to retrieve a reference to the Employee object.

      Employee e = (Employee) r.get ();

      // e will always be null because soft references are cleared before
      // references to their containing SoftReference objects are queued
      // onto a reference queue.

      if (e != null)
          System.out.println (e.toString ());
   }
}

When run, SoftReferenceDemo might poll the reference queue for a short time or a long time. The following output shows one SoftReferenceDemo invocation:

Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Polling reference queue
Jane Doe Employee object's soft reference cleared

SoftReferenceDemo calls System.gc (); (in a loop) to encourage the garbage collector to run. Eventually, at least on my platform, the garbage collector runs, and at least one soft reference clears. After that soft reference clears, a reference to the soft reference's enclosing SoftReference object appends to the reference queue. By comparing the returned reference with each sr array element reference, the program code can determine which Employee referent had its soft reference cleared. We can now recreate the Employee object, if so desired.

Weak references
The weakly reachable state manifests itself in Java through the WeakReference class. When you initialize a WeakReference object, you store a referent's reference in that object. The object contains a weak reference to the referent, and the referent is weakly reachable if there are no other references, apart from weak references, to that referent. If heap memory is running low, the garbage collector locates weakly reachable objects and clears their weak references -- by calling WeakReference's inherited clear() method. Assuming no other references point to those referents, the referents enter either the resurrectable state or the unreachable state. Assuming the referents enter the resurrectable state, the garbage collector calls their finalize() methods. If those methods do not make the referents reachable, the referents become unreachable, and the garbage collector can reclaim their memory.

Note
The primary difference between soft references and weak references is that the garbage collector might clear a soft reference but always clears a weak reference.

To create a WeakReference object, pass a reference to a referent in one of two constructors. For example, the following code fragment creates a ReferenceQueue object and uses the WeakReference(Object referent, ReferenceQueue q) constructor to create a WeakReference object (that encapsulates a Vehicle referent) and associate WeakReference with the reference queue:

ReferenceQueue q = new ReferenceQueue ();
WeakReference wr = new WeakReference (new Vehicle (), q);

Use weak references to obtain notification when significant objects are no longer strongly reachable. For example, suppose you create an application that simulates a company. That application periodically creates Employee objects. For each object, the application also creates an employee-specific Benefits object. During the simulation, employees eventually retire and their Employee objects are made eligible for garbage collection. Because it would prove detrimental for a Benefits object to hang around after its associated Employee object is garbage collected, the program should notice when Employee is no longer strongly reachable.

To obtain that notification, your code first creates WeakReference and ReferenceQueue objects. The code then passes Employee and ReferenceQueue object references to the WeakReference constructor. Next, the program stores the WeakReference object and a newly created Benefits object in a hash table data structure. That data structure forms an association between the WeakReference object (known as the key, because it identifies the hash table entry) and the Benefits object (known as that entry's value). The program can enter a polling loop where it keeps checking the reference queue to find out when the garbage collector clears the weak reference to the Employee object. Once that happens, the garbage collector places a reference to the WeakReference object on the reference queue. The program can then use that reference to remove the WeakReference/Benefits entry from the hash table. To see how the program accomplishes that task, check out Listing 2; it creates a String object as the key and an Object object as the value:

Listing 2. WeakReferenceDemo.java

// WeakReferenceDemo.java

import java.lang.ref.*;
import java.util.*;

class WeakReferenceDemo
{
   public static void main (String [] args)
   {
      // Create a String object that is strongly reachable from key.

      String key = new String ("key");

      /*
         Note: For this program, you cannot say String key = "key";. You
               cannot do that because (by itself), "key" is strongly
               referenced from an internal constant pool data structure
               (that I will discuss in a future article). There is no
               way for the program to nullify that strong reference. As
               a result, that object will never be garbage collected,
               and the polling loop will be infinite.
      */

      // Create a ReferenceQueue object that is strongly reachable from q.

      ReferenceQueue q = new ReferenceQueue ();

      // Create a WeakReference object that is strongly reachable from wr.
      // The WeakReference object encapsulates the String object that is
      // referenced by key (so the String object is weakly-reachable from
      // the WeakReference object), and associates the ReferenceQueue
      // object, referenced by q, with the WeakReference object.

      WeakReference wr = new WeakReference (key, q);

      // Create an Object object that is strongly reachable from value.

      Object value = new Object ();

      // Create a Hashtable object that is strongly reachable from ht.

      Hashtable ht = new Hashtable ();

      // Place the WeakReference and Object objects in the hash table.

      ht.put (wr, value);

      // Remove the only strong reference to the String object.

      key = null;

      // Poll reference queue until WeakReference object arrives.

      Reference r;
      while ((r = q.poll ()) == null)
      {
         System.out.println ("Polling reference queue");

         // Suggest that the garbage collector should run.

         System.gc ();
      }

      // Using strong reference to the Reference object, remove the entry
      // from the Hashtable where the WeakReference object serves as that
      // entry's key.

      value = ht.remove (r);

      // Remove the strong reference to the Object object, so that object
      // is eligible for garbage collection. Although not necessary in this
      // program, because we are about to exit, imagine a continuously-
      // running program and that this code is in some kind of long-lasting
      // loop.

      value = null;
   }
}

Unlike SoftReferenceDemo, WeakReferenceDemo doesn't spend much time polling. After one call to System.gc ();, the garbage collector clears the weak reference to the String referent in the WeakReference object that wr references. Basically, WeakReferenceDemo works as follows:

  1. WeakReferenceDemo creates a String object that serves as a key. You must create that String object with a String constructor instead of simply assigning a String literal to key. The garbage collector will probably never collect String objects that you -- apparently -- create by assigning string literals to String reference variables. (A future article exploring strings will explain why.)

  2. Following a String's creation, WeakReferenceDemo creates a ReferenceQueue object and a WeakReference object that encapsulates String, which becomes the referent. We now have strong (via key) and weak (via the weak reference in the WeakReference object that wr references) references to String.

  3. WeakReference creates an Object value object that eventually associates with the String key object. A Hashtable object is created, and the table stores an entry consisting of the objects WeakReference and Object. (You will learn about the Hashtable data structure class in a future article.)

  4. WeakReference nullifies the String object's key reference, so the only String reference is the weak reference inside the WeakReference object.

  5. A polling loop allows us to wait for the garbage collector to clear the weak reference and place a strong reference to the WeakReference object on the reference queue. On my platform, only one System.gc (); call is necessary to request the garbage collector to run. The first time it runs, all weak references clear, and the next call to q.poll () returns a reference to WeakReference.

  6. After the loop, you can simply remove the WeakReference/Object entry from the hash table and nullify the Object's reference in value.

Note
To save yourself the trouble of automatically removing entries from a hash table, Java supplies the WeakHashMap class. Investigate that class in the SDK documentation and rewrite WeakReferenceDemo to use that class.

Phantom references
The phantomly reachable state manifests itself in Java through the PhantomReference class. When you initialize a PhantomReference object, you store a referent's reference in that object. The object contains a phantom reference to the referent, and the referent is phantomly reachable if there are no other references, apart from phantom references, to that referent. A phantomly reachable referent's finalize() method (assuming that method exists) has already run and the garbage collector is about to reclaim the referent's memory. A program receives notification, by way of a reference queue, when the referent becomes phantomly reachable, and subsequently performs post-finalization cleanup tasks that relate to the referent but do not involve the referent -- because there is no way for the program to access the referent.

Unlike SoftReference and WeakReference objects, you must create PhantomReference objects with a reference queue. When the garbage collector discovers that a PhantomReference object's referent is phantomly reachable, it appends a PhantomReference reference to the associated reference queue -- by calling Reference's enqueue() method. The following code fragment demonstrates the creation of a PhantomReference containing a String referent:

ReferenceQueue q = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (new String ("Test"), q);

There is a second difference between SoftReference, WeakReference, and PhantomReference objects. If either a SoftReference or a WeakReference object has an associated reference queue, the garbage collector places a reference to that object on the reference queue sometime after it clears the referent's soft or weak reference. In contrast, the garbage collector places a reference to a PhantomReference object onto the queue before the phantom reference clears. Also, the program, not the garbage collector, clears the phantom reference by calling PhantomReference's clear() method, inherited from the Reference class. Until the phantom reference clears, the garbage collector does not reclaim the referent. Once the phantom reference clears, however, the referent moves from the phantomly reachable state to the unreachable state. Game over for the referent!

Note
By now, you know a call to get() on a SoftReference or WeakReference object, whose reference returns from a call to poll() or remove(), cannot return a referent's reference. That's not possible because the garbage collector clears the referent's soft or weak reference before placing the SoftReference or WeakReference object's reference on the queue. However, because the garbage collector does not clear the referent's phantom reference, you would expect get() to return a reference to that referent. Instead, get() returns null to prevent the referent from being resurrected.

Listing 3 demonstrates phantom references:

Listing 3. PhantomReferenceDemo.java

// PhantomReferenceDemo.java

import java.lang.ref.*;

class Employee
{
   private String name;

   Employee (String name)
   {
      this.name = name;
   }

   public void finalize () throws Throwable
   {
      System.out.println ("finalizing " + name);
      super.finalize ();
   }
}

class PhantomReferenceDemo
{
   public static void main (String [] args)
   {
      // Create an Employee object that is strongly reachable from e.

      Employee e = new Employee ("John Doe");

      // Create a ReferenceQueue object that is strongly reachable from q.

      ReferenceQueue q = new ReferenceQueue ();

      // Create a PhantomReference object that is strongly reachable from
      // pr. The PhantomReference object encapsulates the Employee object
      // (so the Employee object is phantomly reachable from the
      // PhantomReference object), and associates the ReferenceQueue object,
      // referenced by q, with the PhantomReference object.

      PhantomReference pr = new PhantomReference (e, q);

      // Remove the only strong reference to the Employee object.

      e = null;

      // Poll reference queue until PhantomReference object arrives.

      Reference r;
      while ((r = q.poll ()) == null)
      {
         System.out.println ("Polling reference queue");

         // Suggest that the garbage collector should run.

         System.gc ();
      }

      System.out.println ("Employee referent in phantom-reachable state.");

      // Clear the PhantomReference object's phantom reference, so that
      // the Employee referent enters the unreachable state.

      pr.clear ();

      // Clear the strong reference to the PhantomReference object, so the
      // PhantomReference object is eligible for garbage collection. (The
      // same could be done for the ReferenceQueue and Reference objects --
      // referenced by q and r, respectively.) Although not necessary in
      // this trivial program, you might consider doing such clearing in a
      // long-running loop, so that objects not needed can be collected.

      pr = null;
   }
}

When run, PhantomReferenceDemo produces output similar to the following:

Polling reference queue
finalizing John Doe
Polling reference queue
Employee referent in phantom-reachable state.

The garbage collector has not yet run when the first Polling reference queue message appears. The first call to System.gc (); causes the JVM to try to run the garbage collector. It runs and executes Employee's finalize() method, which prints finalizing John Doe. The second Polling reference queue message indicates that a second call is made to System.gc ();. That call causes the garbage collector to move the Employee referent from the resurrectable state to the phantomly reachable state.

A close look at Listing 3 shows a pr.clear (); method call. That method call clears the phantom reference to the Employee referent in the PhantomReference object. That referent now enters the unreachable state, and the garbage collector can reclaim its memory the next time it runs.

Review
The Reference Objects API gives your programs limited interaction with the garbage collector through the SoftReference, WeakReference, and PhantomReference classes. Objects created from SoftReference contain soft references to their referents. You can use soft references to manage image and other memory-sensitive caches. Objects that you create from WeakReference contain weak references to their referents. You use weak references to obtain notification when significant objects are no longer strongly reachable. Finally, PhantomReference objects contain phantom references to their referents. You can use phantom references to perform post-finalization cleanup on the referents.

I encourage you to email me with any questions you might have involving either this or any previous article's material. (Please, keep such questions relevant to material discussed in this column's articles.) Your questions and my answers will appear in the relevant study guides.

In next month's article, you will learn about nested classes.


Printer-friendly version Printer-friendly version | Send this article to a friend Mail this to a friend

About the author
Jeff Friesen has been involved with computers for the past 20 years. He holds a degree in computer science and has worked with many computer languages. Jeff has also taught introductory Java programming at the college level. In addition to writing for JavaWorld, he has written his own Java book for beginners -- Java 2 By Example, Second Edition (Que Publishing, 2001; ISBN: 0789725932) -- and helped write Special Edition Using Java 2 Platform (Que Publishing, 2001; ISBN: 0789720183). Jeff goes by the nickname Java Jeff (or JavaJeff). To see what he's working on, check out his Website at http://www.javajeff.com.


'java core' 카테고리의 다른 글

Java condition variable  (0) 2005.02.18
Threads from aritma  (0) 2005.02.12
Garbage collection from javaworld  (0) 2005.01.28
자바 코드 컨벤션  (0) 2005.01.24
J2ME쪽 JSR모음  (0) 2005.01.24
Posted by '김용환'
,

FROM : http://www.javaworld.com/javaworld/jw-12-2001/jw-1207-java101.html?

Trash talk, Part 1

Java recycles its memory through garbage collection

Summary
One feature that distinguishes Java from other computer languages is its garbage collection abilities. In this article, Jeff Friesen introduces you to garbage collection and shows how Java's optional support for it affects your programs. While studying garbage collection, you will also learn about reachability, finalization, and resurrection.

For answers to last month's homework, for this month's homework, and for additional material related to this article, visit the associated study guide. (4,400 words; December 7, 2001)

By Jeff Friesen


Printer-friendly version
Printer-friendly version | Send this article to a friend Mail this to a friend


Page 1 of 3

Advertisement

Take out the trash! When it's applied to computer languages, that command conjures up a different meaning than the one you're used to. In Java, trash, or garbage, is heap memory that a program allocates for objects but no longer references; such memory serves no useful purpose. Just as real-world garbage clogs a trash bin, as Java's garbage piles up, it reduces the total amount of heap memory. If the JVM does not remove that garbage, the JVM eventually runs out of heap memory and can't fulfill future program requests to allocate memory for new objects. To prevent that from happening, the JVM takes out the trash through its use of garbage collection.

In this article, I introduce you to garbage collection, a term computer developers commonly use to refer to memory recycling -- that is, the reuse of heap memory. After learning important details about garbage collection and its various algorithms, you'll learn the practical side of garbage collection from Java's perspective: how to ask the JVM to run the garbage collector, how to finalize objects, and how to resurrect finalized objects (and why that is a no-no!). We start exploring garbage collection by defining that term.

Read the whole series on garbage collection:

Note
Java's garbage collection activity is JVM-specific. As a result, the output from various programs that appear in this article will not necessarily match your output. Keep that in mind when you start reading the section entitled "Run Java's Garbage Collector."

What is garbage collection?
From a source code perspective, you use the keyword new to create an object. After you compile that source code into a classfile, the JVM eventually encounters an equivalent byte code instruction to create that object and initialize the object's instance fields to default values. If the object is a nonarray object, such as a single String object, the JVM executes a new byte code instruction to allocate memory for that object in the JVM's object heap, a pool of JVM memory that stores objects. However, if the object is an array object, the JVM executes one of the newarray, anewarray, or multianewarray byte code instructions to perform the same tasks. In any case, the JVM reserves memory for the new object in its object heap. As long as that object exists, the JVM does not give that memory to some other object.

Each of the aforementioned byte code instructions returns a reference to the just-allocated memory chunk. That reference might be a C++-style pointer or some arbitrary value identifying that memory. What constitutes the reference depends on the JVM implementation; you do not need to know about it for the purposes of this discussion.

Typically, you assign the just-returned reference to some kind of reference variable whose type is either the same as or similar to the type of the just-created object. Alternatively, you might create a temporary object (making it temporary by not assigning its reference to any reference variable) to call one of that object's methods. For example, you might execute the following code fragment to print a circle's area by first creating a Circle object with (10.0, 10.0) as the center and 25.0 as the radius, and then calling Circle's area() method via the newly returned reference:

System.out.println ("Area = " + new Circle (10.0, 10.0, 25.0).area ());

The code fragment creates a Circle object, calls its constructor to initialize that object to a specific center and radius, returns a Circle reference, and uses that reference to call Circle's area() method, whose return value subsequently prints. After area() returns, the Circle's reference disappears from the program. That is why Circle is known as a temporary object; without the reference, you can no longer call that object's instance methods.

What happens to an object when you lose its reference? In a language like C++, you have just tied up heap memory. Without the object's reference, that memory remains occupied until the program exits. In Java, however, the reference becomes eligible for garbage collection -- the process by which some executable, such as the JVM, automatically frees an object's memory when a single reference to that memory no longer exists.

When a Java program runs, a portion of most JVMs known as the garbage collector runs as a background thread to perform garbage collection. Because that thread runs occasionally at nondeterministic times, you do not know when it will run. (I will cover threads in a future article.)

To carry out garbage collection, the garbage collector thread performs at least the first of the following two main tasks:

  • It frees an object's heap memory for later use by a subsequently created object. After all, heap memory is not infinite, and a moment comes when the JVM either needs to free object memory or shut down because all available heap memory has run out.

  • It defragments the heap. As the JVM creates objects and its garbage collector frees the memory of unreferenced objects, the heap fragments. Free memory holes appear among blocks of memory assigned to objects. When the JVM attempts to create a new object, enough free memory, from the sum total of all holes, might be available to accommodate the object. However, there might not be a free memory hole large enough to hold the object's instance fields. Defragmentation moves all occupied objects' memory to one end of the heap. That memory serves as one large hole that the JVM can use to allocate memory for new objects.

Note
The Java Language Specification (JLS), which is the official word on the Java language, does not state that a JVM implementation must support a garbage collector. For example, a JVM implementation on a smart card -- a plastic card with an embedded processor -- might require all Java programs to fit within the confines of that card's memory. As a result, the smart card JVM would not need a garbage collector. However, most JVMs need garbage collectors.


Next page >
Page 1 Trash talk, Part 1
Page 2 Garbage collection algorithms
Page 3 Run Java's garbage collector

Printer-friendly version Printer-friendly version | Send this article to a friend Mail this to a friend



'java core' 카테고리의 다른 글

Threads from aritma  (0) 2005.02.12
Reference object model from java world  (0) 2005.01.28
자바 코드 컨벤션  (0) 2005.01.24
J2ME쪽 JSR모음  (0) 2005.01.24
Java 코딩 지침  (0) 2005.01.24
Posted by '김용환'
,

자바 코드 컨벤션

java core 2005. 1. 24. 19:24

'java core' 카테고리의 다른 글

Threads from aritma  (0) 2005.02.12
Reference object model from java world  (0) 2005.01.28
Garbage collection from javaworld  (0) 2005.01.28
J2ME쪽 JSR모음  (0) 2005.01.24
Java 코딩 지침  (0) 2005.01.24
Posted by '김용환'
,

J2ME쪽 JSR모음

java core 2005. 1. 24. 19:20

The J2ME Universe Today

The current universe of configurations, profiles and optionalpackages is shown in the diagram below. The tables immediately followingprovide more details about the abbreviations in the figure.

J2ME Overview
J2ME Overview
Configurations
JSR 30 CLDC 1.0 Connected, Limited Device Configuration
JSR 139 CLDC 1.1 Connected, Limited Device Configuration 1.1
JSR 36 CDC Connected Device Configuration
JSR 218 CDC 1.1 Connected Device Configuration 1.1
Profiles
JSR 37 MIDP 1.0 Mobile Information Device Profile
JSR 118 MIDP 2.0 Mobile Information Device Profile 2.0
JSR 75 PDAP PDA Profile
JSR 46 FP Foundation Profile
JSR 219 FP 1.1 Foundation Profile 1.1
JSR 129 PBP Personal Basis Profile
JSR 217 PBP 1.1 Personal Basis Profile 1.1
JSR 62 PP Personal Profile
JSR 215 PP 1.1 Personal Profile 1.1
JSR 195 IMP Information Module Profile
JSR 228 IMP-NG Information Module Profile - Next Generation
Optional Packages
JSR 75 PIM PDA Optional Packages for the J2ME Platform
JSR 82 BTAPI Java APIs for Bluetooth
JSR 120 WMA Wireless Messaging API
JSR 205 WMA 2.0 Wireless Messaging API 2.0
JSR 135 MMAPI Mobile Media API
JSR 164   JAIN SIMPLE Presence
JSR 165   JAIN SIMPLE Instant Messaging
JSR 172   J2ME Web Services
JSR 177 SATSA Security and Trust Services API for J2ME
JSR 179   Location API for J2ME
JSR 180 SIP SIP API for J2ME
JSR 184 3D Mobile 3D Graphics API for J2ME
JSR 186   JAIN Presence
JSR 187   JAIN Instant Messaging
JSR 190   Event Tracking API for J2ME
JSR 209   Advanced Graphics and User Interface Optional Package for J2ME Platform
JSR 211 CHAPI Content Handling API
JSR 213   Micro WSCI Framework for J2ME
JSR 214   Micro BPSS for J2ME Devices
JSR 226   Scalable 2D Vector Graphics API
JSR 229   Payment API
JSR 230   Data Sync API
JSR 232   Mobile Operational Management
JSR 234   Advanced Multimedia Supplements
JSR 238   Mobile Internationalization API
JSR 239   Java Bindings for OpenGL ES
JSR 246   Device Management API
JSR 253   Mobile Telephony API (MTA)

As the diagram shows, J2ME has two main branches.The first is based on the Connected, Limited Device Configuration (CLDC). This configuration is for small wireless devices with intermittent network connections, like pagers, mobile phones, and Personal Digital Assistants (PDAs).The Mobile Information Device Profile (MIDP), which is based on CLDC, was the first finished profile and thus the first finished J2ME application environment.MIDP-compliant devicesare widely available.

The other major branch of the J2ME tree is based on the Connected Device Configuration (CDC). This configuration is for larger devices (in terms of memory and processingpower) with robust networkconnections. Set-top boxes and internet appliances are good examples of CDCdevices, although high-end PDAs like the Sharp Zaurus also fit thisconfiguration well. The Foundation Profile extends CDC and serves as the basis for several other profiles. It provides fundamental APIs gleaned from J2SE, including classes and interfaces from java.lang, java.io, java.security, java.util, and more. For a list of J2ME terms and definitions, see our glossary.

Optional packages bubble like a froth above the CLDCand CDC branches of J2ME. These provide all sorts of capabilitiesranging from Bluetooth communication through web services and instantmessaging. Look in the table for links to the specifications themselves.

For a thorough look at J2ME, see:

The world of wireless Java technology also includes Java Card, for smart cards. For more information on Java Card, see:


About Stacks and JSR 185

Devices implement a complete software stack, which usually consists of aconfiguration, a profile, and optional APIs. First generation J2ME mobile phonesusually implemented the following software stack:

Example J2ME Stack
Example J2ME Stack

Given the plethora of configurations, profiles, and especially optionalpackages, how does a developer know what to expect on a device? JSR 185,Java Technology for the Wireless Industry, addresses this question byassembling other building blocks into a complete applicationenvironment. JSR 185 mandates CLDC 1.0 or 1.1, MIDP 2.0, and WMA. Supportfor MMAPI is optional. This helps nail things down for developers; on aJTWI device, the developer has a clear understanding of what APIs willbe available. The following figure shows a JSR 185 stack:

JSR 185 Stack
JSR 185 Stack

For more information on JSR 185, see:


The Scope of Wireless Java Technology

Wireless Java technology is the intersection of two vast worlds, wireless data communications and the Java platform. Wireless Java technology spans parts ofJava Card, J2ME, J2SE, and J2EE. That said, some commonmisconceptions about wireless Java technology need clearing up:

  • Wireless Java technology and J2ME are not the same thing. On the onehand, J2ME encompasses more than just wireless devices. While some parts of J2ME are expressly designed for wireless devices, other parts are not--CDC devices are likely to have standard Ethernet connections. On the flip side, wireless Javatechnology isnot confined to J2ME alone. You could have a laptop or palmtop computer running J2SE applications, connecting to other computers via an 802.11 LAN.

  • MIDP is not all of J2ME.MIDP is the first finished profile and has the first installed base of devices out in the world, so people sometimes assume that you are talking about MIDP whenever you talk about J2ME. As you can see from the diagram above, though, J2ME has many facets; MIDP just happened to cross the finishline first.

  • MIDP is not all of wireless Java technology.The Java platform offers plenty of choices forwireless programming: Personal Profile, J2SE on wireless devices, even thePDA Profile.

For other perspectives on wireless Java technology, select:


Why Use the Java Platform for Wireless Development?

The Java platform is an excellent choice for wireless development for many reasons. Here are three compelling advantages:

  • The Java platform is safe. Java code always executes within the confines of theJava Virtual Machine1, which provides a safe environment for executing downloaded code. A binary application could freeze a device or crash it (imagine a blue screen on your mobile phone!) By contrast, at worst a Javaapplications can bring down only the Virtual Machine, not the device itself.
  • The Java language encourages robust programming. The garbage collector saves programmers countless hours of hunting down memory leaks. Likewise, the Javalanguage's exception mechanisms encourage programmers to create robust applications.
  • Portability is a big win for wireless Java technology. A single executablecan run on multiple devices. For example, when you write a MIDlet (a MIDP application) it will run on any device that implements the MIDP specification. Given the dizzying profusion of wireless devices, not havingto maintain a plethora of implementationsis a big advantage. Even if a Javaapplication makes use of vendor specific APIs, applications writtenusing the Java programming language are inherently easier to modify foranother device than applications written in C or C++.

    A second benefit ofportability is the ease of delivering applicationsto a device over the wireless network (sometimes called Over-the-air, or OTA, provisioning). Binary applications can be moved from a server onto a device, too, but not safely.Because Java code runs inside the Java Virtual Machine1, code that is downloaded from the network can be run safely. Binary code cannot be contained at execution time andis much less safe.
For more information on the motivations for using the Java platform for wirelessdevelopment, select:

The Market for Wireless Services

The market for wireless applications and services is huge. This market can be divided into two segments:

  • The consumer segment consists of games, location-based services, productivity applications, and other generally useful applications. In the near term, these applications will be controlled and distributed by wireless carriers.

  • The custom business segment will require custom development ofwireless clients to connect to existing enterprise applications. Many of these will be wireless applications that use middleware to access company databases.

Where to Go Next

Perhaps the best next step toward getting a good grip on wireless Javatechnology is to create some:

  • Wireless Development Tutorial Part I
    Learn how to set up a development environment for buildingJ2ME client applications. You'll install the tools, then build andrun a simple MIDlet. This article has all the information you need to getstarted with J2ME development.
  • Wireless DevelopmentTutorial Part II
    In this article, you'll learn how to set up a Java servlet developmentenvironment, either Tomcat or the J2EE Reference Implementation server.You'll write a servlet and create a MIDlet that makes a networkconnection to the servlet.

'java core' 카테고리의 다른 글

Threads from aritma  (0) 2005.02.12
Reference object model from java world  (0) 2005.01.28
Garbage collection from javaworld  (0) 2005.01.28
자바 코드 컨벤션  (0) 2005.01.24
Java 코딩 지침  (0) 2005.01.24
Posted by '김용환'
,

Java 코딩 지침

java core 2005. 1. 24. 19:17

Java 코딩 지침

본 문서는 Geotechnical Software 에서 작성한 2004 년 1월 버전의 Geosoft 의 Java Programming Style Guidelines 문서를 근간으로 하여 Sun 의 코딩 표준 권고안 및 기타 표준안들을 참고하여 작성한 것입니다. 개선된 사항을 반영하기 위하여 예고없이 변경될 수 있으며 본 문건을 통해 발생한 문제에 대해 책임을 지지 않습니다.


목 차


1 들어가기

본 문서는 Java 개발자 커뮤니티 내에서 보편적인 Java 코딩 권고안들을 나열하고 있습니다. 이 권고안들은 이미 널리 표준으로 받아들여지고 있는 몇몇 문건들(예를 들면, [1], [2], [3], [4], [5])에 기반을 두고 있으며, 전세계의 수 많은 소프트웨어 전문가들의 피드백을 받아들여 작성되었습니다. 기존 지침들이 갖는 주된 단점은, 지침들이 너무 일반적이다보니 보다 구체적이고 상세한 규칙들(예를 들면, 명명 규칙:naming rule)이 확립되어야 할 필요가 있다는 것입니이다. 따라서, 본 지침은 여타의 지침들에 비하여 프로젝트의 코드 검토 단계에 사용하기 용이하도록 각 항목마다 주석과 예제를 두었습니다. 추가적으로, 일반적으로 프로그래밍 권고안들은 코딩 스타일 이슈와 언어 고유한 기술적 이슈들을 혼합하여 기술함으로써 혼란을 초래하는 경향이 있기 때문에, 본 문서에서는 Java 언어의 기술적인 권고안에 대해서 일절 언급하지 않고 오로지 프로그래밍 스타일에 대해서만 다루고자 합니다. IDE 도구를 사용하는 개발 환경에서는 접근제한자, 키워드/문법 하일라이팅, 자동 포맷팅 등을 제공하여 가독성을 향상시킬 수 있을 것입니다. 하지만 프로그래머라면 절대로 그러한 기능에 종속되어서는 안됩니다. 소스 코드를 단순히 IDE 도구 내에서 개발되는 코드뿐 아니라 더 큰/다양한 범위로 확대하여야 하며, 어떠한 IDE 도구를 사용하는지와 상관없이 가독성을 극대화할 수 있도록 소스 코드를 작성하여야만 합니다.

1.1 권고안 레이아웃

권고안들은 주제별로 그룹지어져 있으며, 각각의 권고안들은 코드 검토 시 손쉽게 참조할 수 있도록 숫자를 부여하였습니다. 본 문서에서 사용할 권고안 레이아웃은 다음과 같습니다:

지침에 대한 간략한 설명
적용이 가능한 경우, 적용사례
동기(motivation), 배경 및 추가적인 정보

동기 섹션은 중요합니다. 코딩 표준과 지침들은 일종의 "성전(聖戰)" 과 같은 성격을 띄기 마련입니다. 따라서 각 권고안의 배경을 언급하는 것은 중요합니다.

1.2 권고안의 중요도

지침 섹션에서 사용되는 반드시 한다(must), 한다(should), 할 수 있다(can) 와 같은 용어들은 특별한 의미를 가집니다. 반드시 한다는 필히 준수해야 한다는 것을, 한다는 강한 권고를, 할 수 있다는 일반적인 지침을 의미합니다.

2 일반적인 지침들

1. 가독성을 증진시킬 수 있는 타당한 이유가 있다면 본 지침을 어길 수도 있다.
 
본 지침의 궁극적인 목표는 가독성을 향상시키는 것입니다. 가독성이 향상되면 이해가 용이해지고 유지보수가 수월해지며 일반적으로 코드의 품질도 좋아집니다. 일반적인 지침을 거론하는 본 지침서에서 모든 상황들을 감안한 지침을 제공해 드릴 수 없기 때문에, 프로그래머의 판단에 의하여 가변적으로 활용하실 수 있습니다.

3 명명 관례(Naming Conventions)

3.1 일반적인 명명 관례

2. 패키지를 표현하는 이름은 모두 소문자를 사용한다.
mypackage, com.company.application.ui
이 패키지 명명 관례는 Java 핵심 패키지들의 명명에 Sun 社 가 사용하고 있는 것입니다. 패키지 이름의 시작부는 반드시 도메인 이름으로 소문자를 사용해야만 합니다.
3. 타입을 표현하는 이름은 대소문자를 혼용할 수 있지만, 반드시 명사를 사용하고 시작 글자를 대문자로 지정한다.
Account, EventHandler
Java 개발자 커뮤니티에서 사용하는 일반적인 관습이며, Sun 社 가 Java 핵심 패키지들의 타입 명명에 사용하고 있는 것입니다.
4. 변수의 이름은 대소문자를 혼용할 수 있지만 반드시 소문자로 시작한다.
account, eventHandler
Java 개발자 커뮤니티에서 사용하는 일반적인 관습이며, Sun 社 가 Java 핵심 패키지들의 변수이름 명명에 사용하고 있는 것입니다. 변수와 타입을 손쉽게 구별할 수 있도록 하면, 이름의 충돌이 발생하는 것을 효과적으로 해결할 수 있게 됩니다. 예) Account account; // 선언문
5. 상수(final 변수)를 표현하는 이름은 반드시 모두 대문자로 지정하되 '_' 를 사용하여 단어들을 구분한다.
MAX_ITERATIONS, COLOR_RED
Java 개발자 커뮤니티에서 사용하는 일반적인 관습이며, Sun 社 가 Java 핵심 패키지에 사용하고 있는 것입니다. 일반적으로, 이러한 상수의 사용은 최소화해야만 합니다. 대다수의 경우 상수 변수를 메소드로 구현하는 것이 더 낫습니다:
int getMaxIterations()     // NOT: MAX_ITERATIONS = 25
{
  return 25;
}

이러한 양식이 보다 읽기에 편하며, 클래스의 값을 참조하는 일관된 인터페이스를 제공할 수 있다는 장점을 가집니다.
 
6. 메소드의 이름은 대소문자를 혼용할 수 있지만 반드시 동사를 사용하며 소문자로 시작한다.
getName(), computeTotalWidth()
Java 개발자 커뮤니티에서 사용하는 일반적인 관습이며, Sun 社 가 Java 핵심 패키지에 사용하고 있는 것입니다. 이 방식은 변수의 이름을 지을 때 사용하는 관례와 동일합니다. 하지만, Java 에서는 이미 특별한 양식(getter/setter, 동사로 시작하는 이름, JVM Spec, 등)에 의거하여 메소드는 변수를 구분합니다.
7. 축약형(Abbreviation) 과 두문자어형(頭文字語: Acronym) 을 이름에 사용할 경우에는 전부 대문자로 지정하지 않는다.
exportHtmlSource();    // exportHTMLSource(); 가 아님
openDvdPlayer();       // openDVDPlayer(); 가 아님
축약형 혹은 두문자어형 이름을 모두 대문자로 지정하게 되면 앞서 기술한 명명 지침들과 충돌이 발생하게 됩니다 (대표적으로 상수에 대한 명명 지침과 혼동될 수 있습니다). 그렇다고 이 유형의 이름을 dVD, hTML 등 과 같이 다양한 형태로 지정할 경우에는 가독성이 떨어집니다. 또 다른 문제점으로는 위의 예제에서 살펴볼 수 있는 바와 같이, 이 유형의 이름이 다른 이름과 결합되는 경우 가독성이 극도로 나빠진다는 것입니다. 후속하는 단어가 있을 경우는 더욱 각별히 주의해야 합니다.
8. private 접근 제한자를 갖는 클래스 변수에 '_' 접미사를 사용한다.
class Well
{
  private int  depth_;
  ...
}
변수의 이름이나 타입과는 별개로, 변수의 범위(scope)는 매우 중요한 특성입니다. 접미사 '_' 를 사용함으로써 클래스 범위의 변수(로컬 변수가 아닌)임을 쉽게 구별할 수 있게 됩니다. 클래스 변수는 메소드 내에서 선언되는 로컬 변수에 비해 중요도가 높기 때문에 프로그래머가 각별히 주의해야 합니다. '_' 를 사용하는 명명 규칙은 부가적으로 setter 메소드에서의 이름 충돌문제를 깔끔하게 해결해 줍니다: void setDepth (int depth)
{
  depth_ = depth;
}
제기될 수 있는 이슈거리는 '_' 를 변수의 접두사로 쓸 것인가 접미사로 쓸 것인가에 대한 것입니다. 두 가지 모두 일반적으로 널리 사용되는 방법이기는 합니다만, 이름을 읽기 편하다는 측면에서 후자의 것을 권고합니다. 변수의 이름에 볌위를 식별할 수 있도록 하자는 제안은 한 동안 많은 논쟁을 불러일으켰습니다. 그런데 현재에는 이러한 방법이 수용되어 전문 개발자 커뮤니티에서도 일반적인 관례로 점차 정착이 되고 있는 것 같습니다.
 
9. 일반적인 변수의 이름은 타입의 이름과 동일하게 지정한다.
void setTopic (Topic topic)      // void setTopic (Topic value) 이 아님
                                 // void setTopic (Topic aTopic) 이 아님
                                 // void setTopic (Topic x) 이 아님

void connect (Database database) // void connect (Database db) 가 아님
                                 // void connect (Database oracleDB) 가 아님
용어나 이름의 수를 줄이는 것이 코드의 복잡도를 줄여줍니다. 또한 변수의 이름만으로도 그 타입을 손쉽게 유추할 수 있게 해준다는 장점도 있습니다. 만약 어떠한 이유에선가 이러한 관례가 맞지 않는 것처럼 느끼신다면, 이는 분명 타입이름 자체를 잘못 지정한 것입니다. 일반적이지 않은 변수들은 각기 나름의 역할(role)을 가지고 있습니다. 이러한 변수들은 역할과 변수의 타입을 결함하여 이름을 짓곤 합니다. Point startingPoint, centerPoint;
Name  loginName;

 
10. 모든 이름은 영어로 작성한다.
fileName;    // filNavn 나 파일이름 이 아님
국제적인 개발에 있어서 영어가 선호되기 때문입니다.
11. 넓은 범위에 영향을 미치는 변수는 긴 이름을 부여하고, 좁은 범위의 변수는 짧은 이름을 부여한다 [1].
 
임시 저장공간이나 인덱스로 사용되는 Scratch variable (주: 의미있는 값을 갖지 않고 그때그때 상황에 따라 값들을 잠시 보관해 두기 위한 변수로, 대개 보유한 값이 얼마 후에 의미가 없어지거나 삭제됨)들은 매우 짧은 이름을 부여하십시요. 프로그래머가 그러한 변수들을 읽음과 동시에, 이 변수는 몇 라인 뒤에 그 값이 유효하지 않을 것임을 짐장할 수 있게 해야 합니다. 보편적인 scratch variable 로는 정수를 저장하는 i, j, k, m, n 가 있고 문자를 저장하는 c, d 가 있습니다.
12. 호출하려는 객체의 이름을 통해 의미를 짐작할 수 있다면, 메소드의 이름을 간략화할 수 있다.
line.getLength();    // line.getLineLength(); 가 아님
클래스 선언 시에는 후자의 것이 자연스럽지만, 사용할 때에는 위 예에서 볼 수 있듯이 중언부언하는 느낌을 줄 수 있습니다.

3.2 특수한 명명 관례

13. get/set 이라는 용어는 반드시 애트리뷰트에 직접 접근하는 메소드에 사용한다.
employee.getName();
matrix.getElement (2, 4);
employee.setName (name);
matrix.setElement (2, 4, value);
이 접근 메소드 명명 관례는 Java 핵심 패키지들의 명명에 Sun 社 가 사용하고 있는 것입니다. 실제 자바빈즈를 작성할 때에는 이 명명규칙을 준수해야 합니다.
14. is 접두사를 불리언 변수와 메소드에 사용한다.
isSet, isVisible, isFinished, isFound, isOpen
이 불리언 메소드와 변수에 대한 명명 관례는 Java 핵심 패키지들의 명명에 Sun 社 가 사용하고 있는 것입니다. 실제 자바빈즈를 작성할 때에는 이 명명규칙을 준수해야 합니다. is 접두사를 사용함으로써 일반적으로 statusflag 와 같은 좋지 않은 불리언 이름을 선택하는 문제를 해결할 수 있습니다. isStatusisFlag 는 간결하지만 프로그래머에게 의미를 풍부하게 전달할 수 없다는 점에서 바람직하지 못합니다. 일부 상황에서는 is 접두사가 아닌 보다 더 적합한 접두사를 사용할 수도 있습니다. has, can, should 접두사들을 그런 대안으로 활용하실 수 있을 것입니다: boolean hasLicense();
boolean canEvaluate();
boolean shouldAbort = false;

 
15. compute 라는 용어는 무엇인가를 계산(시간이 소요되는)하는 메소드에 사용할 수 있다.
valueSet.computeAverage();  matrix.computeInverse()
프로그래머로 하여금 보는 즉시 이 메소드는 잠재적으로 시간이 소요되는 작업을 수행하고 있다는 것을 주지심으로써, 이 메소드의 결과를 반복해서 사용하고자 할 경우에 프로그래머는 계산된 값을 캐시해 둘 것을 고려하게 만들 수 있습니다. 용어를 일관성있게 사용하는 것은 가독성을 높이는 지름길입니다.
16. find 라는 용어는 무엇인가를 찾는 메소드에 사용할 수 있다.
vertex.findNearestVertex();   matrix.findMinElement(); 
프로그래머로 하여금 보는 즉시 이 메소드는 최소한의 계산을 통해 요청하는 결과를 찾을 수 있는 메소드라는 것을 알려주십시오. 용어를 일관성있게 사용하는 것은 가독성을 높이는 지름길입니다.
17. initialize 라는 용어는 객체나 개념이 확립되어지는 곳에 사용할 수 있다.
printer.initializeFontSet();
미국식 initialize 가 영국식 initialise 보다 바람직합니다. init 라는 축약형 이름은 절대로 사용하지 마십시오.
18. JFC (Java Swing) 변수들은 각 컴포넌트의 타입을 접두사로 사용한다.
widthScale, nameTextField, leftScrollbar, mainPanel, fileToggle, minLabel, printerDialog
변수의 이름 자체가 자신이 어떤 타입의 변수인지 알려주기 때문에 객체가 어떤 자원인지 쉽게 알 수 있습니다.
19. 컬렉션의 이름은 반드시 복수형으로 사용한다.
vertex (one vertex),   vertices(a collection of vertices)
account (one account),    accounts(a collection of accounts)
 
여기서의 컬렉션은 java.util.Collection 변수와 단순 배열과 같은 Collection 의 자손들을 의미합니다.
20. n 접두사는 객체의 개수를 나타내는 변수에 사용한다.
nPoints, nLines
이 표기법은 개체의 개수를 나타내는 수식의 표기법에서 차용한 것입니다. Sun 社 는 Java 핵심 패키지에서 이런 용도로 num 접두사를 사용한다는 것에 주목해 보겠습니다. 이는 필경 number of 에 대한 축약어를 의미할 것입니다만, 그 보다는 number 를 연상시킴으로써 변수 이름을 이상하게 만들거나 혼동을 불러일으킬 수 있습니다. 만약 "number of" 를 더 선호하신다면, n 대신에 numberOf 접두사를 사용하실 수도 있을겁니다. num 접두사는 사용하시지 않기를 강력하게 권합니다.
 
21. No 접미사는 엔터티 번호를 나타내는 변수에 사용한다.
tableNo, employeeNo
이 표기법은 엔터티의 번호를 나타내는 수식의 표기법에서 차용한 것입니다. 또 다른 우아한 표기 방법으로는 i 을 접두사로 사용하는 것입니다: iTable, iEmployee. 이 방법은 효과적으로 변수에 iterator 로의 역할을 부여하게 됩니다.
 
22. Iterator 변수들은 i, j, k 등 과 같은 이름을 사용한다.
while (Iterator i = pointList.iterator(); i.hasNext(); ) {
  :
}

for (int i = 0; i < nTables; i++) {
  :
}
 
이 표기법은 iterator 를 나타내는 수식의 표기법에서 차용한 것입니다. j, k 등과 같은 이름의 변수들은 중첩된 반복문 내에서만 사용합니다.
 
23. 대응하는 단어가 있는 이름은 반드시 함께 사용한다 [1].
get/set, add/remove, create/destroy, start/stop, insert/delete, increment/decrement, old/new, begin/end, first/last, up/down, min/max, next/previous, old/new, open/close, show/hide
대칭을 이루도록 함으로써 복잡도를 줄일 수 있습니다.
24. 축약형 이름의 사용은 피한다.
computeAverage();     // compAvg(); 가 아님
고려해 볼 두 종류의 단어들이 있습니다. 먼저 프로그래밍 언어를 구사하는 데에 있어 빈번하게 사용되는 일반적인 단어들이 있습니다. 이들 단어는 절대로 축약형을 사용하지 말아야 합니다: command    대신 cmd
copy      
대신
cp
point     
대신
pt
compute   
대신
comp
initialize
대신
init
등등.
그 다음, 도메인에서 사용하는 특수한 두문자어나 축약어들은 축약형을 그대로 사용합니다. 이들 구문은 굳이 풀어 나열하지 않고 축약형을 사용하십시오. 다음과 같이 사용하시면 안됩니다: html 대신
HypertextMarkupLanguage
cpu 
대신
CentralProcessingUnit
pe  
대신
PriceEarningRatio
등등.
 
25. 불리언 변수 이름은 절대로 부정적인(거짓인) 이름을 사용하지 않는다.
boolean isError;    // isNotError 가 아님
boolean isFound;    // isNotFound 가 아님
문제는 불리언 변수에 부정적인 이름을 사용할 경우, not 연산자를 사용할 경우 부정의 부정을 계산해야 하는 혼동을 초래한다는 것입니다. 예를 들어, !isNotError 에서 처럼, 신속하게 이것이 무엇을 의미하는지 인지할 수 없게 됩니다.
26. 관련있는 상수(final 변수)들은 공통 타입의 이름을 접두사로 사용하여 그룹핑한다.
final int COLOR_RED   = 1;
final int COLOR_GREEN = 2;
final int COLOR_BLUE  = 3;

final int MOOD_HAPPY  = 1;
final int MOOD_BLUE   = 2;
 
이 표기방법은 상수들이 어디에 속해있는지 상수가 무엇을 나타내고 있는지 식별하는데 도움이 됩니다.
27. 예외(Exception) 클래스들은 Exception 이라는 접미사를 사용한다.
class AccessException
{
  :
}
 
사실 예외 클래스들은 프로그램의 핵심 디자인에 해당되지 않기 때문에, 이와 같은 방식으로 이름을 지정함으로써 상대적으로 클래스들 중에서 눈에 띄게(구분을 용이하게) 해두는 것이 좋습니다. 이 명명 관례는 Java 기본 라이브러리들에 Sun 社 가 사용하고 있는 표준입니다.
28. 디폴트 인터페이스 구현은 Default 라는 접두사를 사용할 수 있다.
class DefaultTableCellRenderer
implements TableCellRenderer
{
  :
}
 
이 방법은 인터페이스 메소드들에 대한 디폴트 처리작업을 제공하는 구현물에, 간단하게 이름을 부여하는 방법으로 널리 사용되고 있는 방법입니다. Default 접두사를 클래스의 이름에 부여하는 이 명명 관례는, Java 기본 라이브러리들에 Sun 社 가 채택한 방식입니다.
29. 함수(객체/결과를 리턴하는 메소드)의 이름은 '처리 후 무엇을 리턴하는지'를 의미하고, 프로시저(void 메소드)의 이름은 '무엇을 처리하는지'를 의미한다.
 
메소드가 무엇을 하는지. 의도하지 않는 것들이 무엇인지를 명확하게 해주기 때문에, 가독성을 높여줍니다. 또한 이 방법은 예기치 못한 부작용(side effect)들로부터 코드를 명쾌하게 유지시켜 줄 수 있습니다.

4 파일

30. Java 소스 파일의 확장자는 .java 이다.
Point.java
Java 개발툴에 의해 강제적으로 준수해야 하는 규칙입니다.
31. 클래스는 각각의 파일에 선언하며, 클래스의 이름과 파일의 이름이 동일해야 한다. 이너(inner) 클래스나 private 으로 선언된 secondary 클래스들은 굳이 별도의 파일로 구분하지 않고도 primary 클래스와 같은 파일 내에서 선언할 수 있다.
 
Java 개발툴에 의해 강제적으로 준수해야 하는 규칙입니다.
32. 파일의 내용물은 반드시 80 컬럼을 벗어나지 않는다.
 
80 컬럼은 다양한 편집기, 프린터, 터미널 에뮬레이터, 디버거에서도 일반적인 길이로, 각 라인이 80 컬럼을 넘지 않도록 소스코드를 작성한다면 다양한 환경의 여러 개발자들과 파일을 공유해서 사용할 수 있습니다. 이는 프로그래머들 간에 파일을 전달하였을 때 의도하지 않은 들여쓰기나 개행문자가 포함되어 가독성이 떨어지는 것을 예방합니다.
33. 탭(TAB) 문자나 페이지 구분(page break) 문자와 같은 특수한 문자들은 절대로 사용하지 않는다.
 
이러한 문자들은 프로그래머가 여럿이거나 다수의 플랫폼에서 개발하는 환경에서, 편집기 별, 프린터 별, 터미널 에뮬레이터 별, 디버거 별로 지정된 설정에 따라 소스코드를 들쭉날쭉하게 만들어 가독성을 떨어뜨리게 됩니다.
34. 여러 라인으로 분리한 문장들은 반드시 명확하게 만든다 [1].
totalSum = a + b + c +
           d + e);
function (param1, param2,
          param3);
setText ("Long line split" +
         "into two parts.");
for (tableNo = 0; tableNo < nTables;  
     tableNo += tableStep)
한 문장이 80 컬럼을 벗어날 때 라인을 분리해서 작성하게 됩니다. 어떻게 라인을 분리해야 하는지에 대한 엄격한 규칙을 제시하기는 어렵습니다만, 위 예제들은 일반적인 힌트를 제공해줄 것입니다: 일반적으로:
  • 콤마(,) 뒤에서 분리한다.
  • 연산자 뒤에서 분리한다.
  • 이전 라인에서의 표현식 시작 부분에 맞추어 정렬한다.

5 문장

5.1 package 와 import

35. package 문은 반드시 파일의 첫번째 라인에 나타나야 하며, 모든 파일은 특정 패키지에 소속된다.
 
package 문의 위치는 Java 언어 코딩 표준에서 권고하고 있는 것입니다. 모든 파일들을 실제 패키지(디폴트 패키지보다는 이름이 있는 패키지)에 두는 것은 Java 객체 지향 프로그래밍 기법에 도움을 줍니다.
36. import 문은 반드시 package 문 뒤에 나와야 한다. import 문은 가장 기본이 되는 패키지들 부터 순차적으로 정렬하며, 관련있는 패키지들은 함께 묶어 두고 빈 라인을 삽입하여 일목요연하게 정리한다.
import java.io.*;
import java.net.*;

import java.rmi.*
import java.rmi.server.*;

import javax.swing.*;
import javax.swing.event.*;

import org.linux.apache.server.*;
 
import 문의 위치는 Java 언어 코딩 표준에서 권고하고 있는 것입니다. 많은 import 문들을 정렬해두면 편리하게 import 문들을 검토할 수 있고, 이를 통하여 현재 이 패키지가 어떤 용도로 설계되었는지를 쉽게 파악할 수 있습니다. 또한 import 문을 그룹핑하면 관련된 정보들을 공통의 유닛으로 관리할 수 있기 때문에 복잡도를 줄일 수 있습니다.
37. import 문을 사용할 때에는 와일드 카드 문자(*)를 사용하지 않는다.
import java.io.*;

대신:

import java.io.File;
import java.io.FileWriter;
import java.io.BufferedWriter;
import java.io.PrintWriter;
import java.io.FileNotFoundException;
 
사실 #36 의 예제와 같이 import 문에 와일드 카드 문자인 * 를 사용하는 것은, 프로그래머에게는 편리할지 모르지만 성능에 얼마간 영향을 미치게 됩니다. 더구나 import 문만 보고도 정확하게 현재 클래스/인터페이스가 어떤 작업을 할 것인지 예측할 수 있도록 하려면, 명시적으로 사용하는 클래스와 인터페이스들에 대해서만 import 하시는 것이 필요합니다.

5.2 클래스와 인터페이스

38. Class 와 Interface 의 선언은 다음과 같은 방식으로 조직화하여 사용한다:
  1. Class/Interface 문서(javadoc 주석)
  2. classinterface 선언문
  3. 클래스 변수(static으로 선언된) 들을 public, protected, package (접근제한자가 없는), private 순서대로 나열한다.
  4. 인스턴스 변수들을 public, protected, package (접근제한자가 없는), private 순서대로 나열한다.
  5. 생성자.
  6. 메소드 (메소드에는 특별한 순서가 없음).
 
각각의 클래스/인터페이스 구성요소들의 등장 위치를 지키게되면, 현재 코드 상에 어떤 요소들이 다음에 등장할 것인지 예측할 수 있게되어 복잡도를 낮추게 됩니다.

5.3 메소드

39. 메소드 지시자는 다음의 순서대로 사용한다:
<access> static abstract synchronized <unusual> final native
만약 <access> 지시자가 존재한다면 반드시 맨 처음에 나타나야 한다.
 
<access> 지시자는 public, protected, private 중 하나이고, <unusual> 부분은 volatiletransient 중 하나가 지정됩니다. 여기서 가장 중요한 점은 접근(access) 지시자가 맨 처음에 나타나야 한다는 것입니다. 사용할 수 있는 다양한 지시자들이 있겠지만, 이는 매우 중요한 사항이기 때문에 반드시 메소드 선언문에 반영되어야 합니다. 기타 지시자들의 순서는 덜 중요하지만 일관된 관례를 따르는 것이 바람직합니다. 여기서 제안한 지시자 나열 순서는 Teach Yourself Java in 21 Days 의 저자인 Charles L. Perkins 씨가 자신의 책에서 제시한 방식을 차용한 것입니다.

5.4 타입

40. 타입 컨버전(변환)은 반드시 명시적으로 한다. 묵시적인 타입 컨버전(변환)은 절대로 사용하지 않는다.
floatValue = (float) intValue;     // floatValue = intValue; 가 아님
위 예제의 경우, 프로그래머가 서로 다른 두 타입을 사용하였으며 그가 의도적으로 두 타입을 혼합해서 사용하고 있음을 알려줍니다. 만약 묵시적으로 floatValue = intValue 라고 하였다면 의도적으로 그러한 것인지, 타입 컨버전을 깜빡 한 것인지 혼동할 수 있습니다.
41. 배열 지시자([])는 변수의 이름 뒤가 아니라 타입의 이름 뒤에 나온다.
int[] daysOfMonth;      // int daysOfMonth[]; 가 아님
배열은 타입의 한 속성이지 변수의 속성이 아니기 때문입니다. 어떤 이유에서인지 Java 에서는 두 가지 모두 문법적으로 허용하고 있습니다만, 하나로 통일하는 것이 좋겠습니다.

5.5 변수

42. 변수는 선언된 지점에서 초기화하며, 가능한 사용범위를 최소화하여 선언한다.
 
이 규칙은 어느 시점에서든 변수가 유효한 값을 가진다는 것을 보장해줍니다. 종종 선언하는 시점에 변수에 유효한 값을 초기화하는 것이 불가능한 경우가 있습니다만, 그러한 경우에도 초기화하지 않은 상태로 내버려두는 것보다는 임의의 값이라도 사용하여 초기화 해두는 것이 좋습니다.
43. 변수는 절대로 하나 이상의 의미를 가져서는 안된다.
 
무의미한 이름을 부여한 변수(scratch 변수)를 가지고 이값 저값을 할당하여 사용하게 되면, 어느 순간 이 변수가 현재 어떤 의미의 값을 가지고 있는지 모호해지는 경우가 발생합니다. 일관되고 유일한 의미를 부여함으로써 가독성을 향상시키고 더불어 부작용까지 예방할 수 있습니다.
44. static 으로 선언된 클래스 변수들은 절대로 public 으로 선언하지 않는다.
 
public 변수들은 Java 의 정보은닉과 캡슐화 컨셉에 위배됩니다. 대신 변수를 private 으로 선언하시고 이 변수를 접근할 수 있는 메소드를 사용하게 하십시오. 한 가지 예외적인 상황은 클래스가 데이터 구조로만 사용될 때입니다. 이 경우 클래스는 어떠한 행위(메소드)도 갖지 않고 오로지 데이터를 보관하는, 즉 C++ 에서의 struct 와 동일한 형태로 사용됩니다. 이 경우에는 클래스의 인스턴스 변수들을 public 으로 선언할 수도 있습니다 [2].
45. 동일한 타입의 변수 중 관련있는 변수들은 하나의 구문에서 선언할 수 있다.
즉, 관련이 없는 변수들을 같은 라인에서 선언하지 않는다.
float  x, y, z;
float  revenueJanuary, revenueFebrury, revenueMarch;
위의 예제를 여러 라인에 나누어 선언하는 것은 바람직하지 않습니다. 변수들을 그룹핑함으로써 가독성을 높일 수 있기 때문입니다. 그러나 여기서 주의할 점은, 대다수의 경우에서는 이 규칙보다 각각의 변수는 선언 시 초기화 한다는 규칙이 우선적으로 적용되어야 한다는 것입니다.
46. 변수의 생존기간을 가능한 짧게 유지한다.
 
변수에 대한 조작을 작은 범위 내에 국한시킴으로써, 영향과 부작용을 통제하는 것이 용이해집니다.

5.6 반복문

47. 반드시 반복을 제어하는데 사용되는 문장들만 for() 구문 내에 포함시킨다.
sum = 0;                   // NOT:     for (i=0, sum=0; i<100; i++)
for (i=0; i<100; i++)      //            sum += value[i];
  sum += value[i];
관리의 용이성과 가독성을 향상시킬 수 있습니다. 무엇이 반복문을 제어하고 무엇이 반복문 내에서 사용되는지 명쾌하게 하십시오.
48. 반복문에 사용하는 변수는 반복문 직전에 초기화한다.
boolean isDone = false;  // NOT:   boolean  isDone = false;
while (!isDone) {        //         :
  :                      //        while (!isDone) {
}                        //          :
                                   }
 
49. do .... while 문의 사용을 피한다.
 
여기에는 두 가지 이유가 있습니다. 첫번째, 제어문에 사용되는 구문 요소(키워드)들이 분산되어 있어 난잡합니다. do .... while 문을 사용한 모든 문장은 while 문이나 for 문을 사용하여 동일하게 바꾸어 작성할 수 있습니다. 사용되는 구문 요소들을 최소화함으로써 복잡도를 줄일 수 있습니다. 두번째 이유는 가독성과 관련되어 있습니다. 반복의 조건을 체크하는 부분이 하단에 위치하는 것은, 상단에서 조건을 체크하는 반복문보다 읽기가 더욱 어렵습니다.
 
40. 반복문 내에서 breakcontinue 의 사용을 자제한다.
 
이 두 문장은 구조화된 반복문보다 훨씬 가독성이 높을 때에만 사용하십시오. 일반적으로 break 문은 case 문 내에서만 사용하시고, continue 문은 반복문이든 case 문이든 어떤 경우라도 사용을 피해주시기 바랍니다.
 

5.7 조건문

51. 복잡한 조건식은 반드시 피한다. 그 대신 임시 불리언 변수를 활용하라 [1].
if ((elementNo < 0) || (elementNo > maxElement)||
    elementNo == lastElement) {
  :
}
다음과 같이 변경한다:
boolean isFinished      = (elementNo < 0) || (elementNo > maxElement);
boolean isRepeatedEntry = elementNo == lastElement;
if (isFinished || isRepeatedEntry) {
  :
}

 
조건식을 불리언 변수에 할당함으로써, 프로그램 그 자체로 의미를 명확하게 전달하고 있습니다. 조건문을 평가하는 구문은 보다 쉽게 해석할 수 있으며, 더불어 디버깅할 때에도 편리해집니다.
52. if 문에서, 일반적인 상황은 if 블록에 위치시키고 예외 상황은 else 블록에 위치시킨다 [1].
boolean isError = readFile (fileName);
if (!isError) {
  :
}
else {
  :
}
일반적인 처리 흐름과 예외상황 처리 흐름을 불명확하지 않게 하십시오. 이 지침은 가독성과 성능 모두에 영향을 미치는 중요한 사항입니다.
53. 조건을 평가하는 문장은 별도의 라인으로 분리한다.
if (isDone)              // NOT:  if (isDone) doCleanup();
  doCleanup();
이는 디버깅을 위한 목적으로 한 것입니다. 한 라인에 이것저것 기술하게 되면, 조건식 테스트가 참인지 거짓인지를 확인하기가 어려워집니다.
54. 절대로 조건식에 실행문을 사용하지 않는다.
file = openFile (fileName, "w");  // NOT:   if ((file = openFile (fileName, "w")) != null) {
if (file != null) {               //         :
  :                               //        }
}
조건식에 실행문을 사용하는 것은 간편하지만 읽기가 매우 어려워집니다. 이 지침은 Java 에 발을 처음으로 들여놓는 개발자들에게 특히 강조하고 싶은 지침입니다.

5.8 기타

55. 코드 상에서 매직 넘버(magic number: constants, array size, character positions, conversion factors와 같이 프로그램내에 등장하는 숫자/문자값)의 사용을 자제한다. 01 이외의 숫자는 차라리 상수로 정의하여 사용하도록 한다.
 
명확한 의미를 갖지 않는 숫자를 사용할 경우, 가독성 향상을 위하여 상수로 정의하여 사용하도록 합니다.
56. 실수값 상수는 항상 소수점과 최소한 소숫점 이하 한 자리 숫자를 사용하여 지정한다.
double total = 0.0;   // double total = 0; 가 아님
double speed = 3.0e8; // double speed = 3e8; 가 아님

double sum;
:
sum = (a + b) * 10.0;
특별한 경우에 정수와 실수가 동일한 값을 갖는다 하더라도 기본적으로 특성이 다르기 때문에 구별하는 것이 좋습니다. 또한 위 마지막 라인의 예제에서 살펴볼 수 있듯이, 값을 할당받은 변수(sum)는 다른 변수의 타입을 알 수 없다고 하더라도 명백하게 실수값을 계산해 낸다는 것을 보장할 수 있습니다 (* 10.0 을 통해서)
 
57. 실수값 상수는 항상 정수부에 숫자를 사용하여 지정한다.
double total = 0.5;   // double total = .5; 가 아님
Java 에서 사용하는 숫자와 표현식은 수학식 표기법에서 차용하였으므로, 프로그래머는 가능한 수학적 표기법을 준수하는 것이 바람직합니다. 실제로 .5 (간편한 표기) 보다 0.5 (수학적 표기) 가 더 읽기에 쉽습니다. 이 수식에서 정수 5 가 함께 사용되는 경우에는, .5 와 정수 5 를 혼동하여 잘못된 연산을 초래할 가능성이 높아지게 됩니다.

6 레이아웃과 주석

6.1 레이아웃

58. 기본 들여쓰기(indentation)는 4 자로 한다.
for (i = 0; i < nElements; i++)
  a[i] = 0;
1 자짜리 들여쓰기는 너무 작아서 논리적인 코드의 레이아웃에 사용합니다. 한편 4 자 이상 들여쓰기를 할 경우 중첩 들여쓰기한 코드를 읽기가 어려워지며, 한 문장을 여러 라인으로 쪼개야하는 상황이 자주 발생하게 됩니다. 2, 3, 4 글자 중에서 선택하시는 것이 바람직합니다만, 2 자와 4자가 가장 보편적입니다. 2 자로 하였을 경우에는 한 문장을 여러 라인으로 쪼개야할 상황을 최소화할 수 있습니다. Sun 社 에서는 4 자 들여쓰기를 권고하고 있습니다. 이 지침은 본래 2 자였던 것을 Sun 표준인 4 자로 변경한 것입니다.
59. 블록 레이아웃은 아래 예제 1 (권고안) 나 예제 2 를 따르되, 예제 3 의 형태는 결코 취해서는 안된다. 클래스, 인터페이스, 메소드 블록은 예제 2 레이아웃을 따른다.
while (!isDone) {
  doSomething();
  isDone = moreToDo();
}
 
while (!isDone)
{
  doSomething();
  isDone = moreToDo();
}
 
while (!isDone)
  {
    doSomething();
    isDone = moreToDo();
  }
 
예제 3은 추가적인 들여쓰기를 사용하고 있으나 예제 1 와 예제 2 를 사용한 것보다 나을 것이 없습니다.
60. classinterface 선언문은 다음과 같은 형태를 따른다:
class SomeClass extends AnotherClass
  implements SomeInterface, AnotherInterface
{
  ...
}
 
이 지침은 위에서 언급한 #59 의 일반적인 블록 규칙에 의거한 것입니다. Java 개발자 커뮤니티에서는 클래스를 선언하는 문장의 끝에서 중괄호를 여는 것이 일반적입니다. 실제로 이 괄호 스타일은 모든 종류의 블록에 대하여 사용할 수 있는 방식입니다. 개인적인 선호의 차이가 있겠지만, 이 방식은 C/C++ 에서 클래스와 메소드 블록은 다른 블록들과 구분할 수 있도록 하는 것을 차용한 것입니다.
61. 메소드 선언문은 다음과 같은 형태를 따른다:
public void someMethod()
  throws SomeException
{
  ...
}
 
class 문장에 대한 설명을 참고하십시오 (#59).
62. if-else 문장은 다음과 같은 형태를 따른다:
if (condition) {
  statements;
}

if (condition) {
  statements;
}
else {
  statements;
}

if (condition) {
  statements;
}
else if (condition) {
  statements;
}
else {
  statements;
}
 
이 지침은 위에서 언급한 #59 일반적인 블록 규칙에 의거한 것입니다. 하지만, else 절이 직전의 ifelse 절의 닫히는 중괄호와 동일한 라인에 있어야 한다고 한다면 논의를 해 볼 수도 있을 것입니다:

if (condition) {
  statements;
} else {
  statements;
}

이 방식은 Sun 社의 코딩 표준 권고안에서 제시한 것과 동일한 것입니다. 여기서 제시한 방법은 if-else 문장을 별도의 행에 분리되어 있기 때문에, else 절을 쉽게 식별할 수 있고 (직전 ifelse 절의 닫히는 중괄호 때문에 else 절이 잘 드러나지 않는 경우도 있습니다) 심지어 else 절을 이동한다거나 하는 경우에 손쉽게 if-else 문장을 조작할 수 있다는 점에서 조금 더 낫다고 할 수 있겠습니다.
63. A for 문장은 다음과 같은 형태를 따른다:
for (initialization; condition; update) {
  statements;
}
 
이 지침은 위에서 언급한 #59 일반적인 블록 규칙에 의거한 것입니다.
64. 비어있는 for 문장은 다음과 같은 형태를 따른다:
for (initialization; condition; update)
  ;
이 방식은 프로그래머가 의도적으로 for 문장을 비워두었음을 분명히할 수 있습니다.
65. while 문장은 다음과 같은 형태를 따른다:
while (condition) {
  statements;
}
 
이 지침은 위에서 언급한 #59 일반적인 블록 규칙에 의거한 것입니다.
66. do-while 문장은 다음과 같은 형태를 따른다:
do {
  statements;
} while (condition);
 
이 지침은 위에서 언급한 #59 일반적인 블록 규칙에 의거한 것입니다.
67. switch 문장은 다음과 같은 형태를 따른다:
switch (condition) {
  case ABC :
    statements;
    // Fallthrough 혹은 '계속'
  case DEF :
    statements;
    break;
  case XYZ :
    statements;
    break;
  default :
    statements;
    break;
}
 
이 방식은 Sun 社의 코딩 표준 권고안과는 들여쓰기와 공백문자 처리에 있어서 약간 다릅니다. 특히, 각각의 case 키워드들은 모두 switch 문에 소속되어 있음을 부각시키기 위하여 switch 키워드를 기점으로 들여쓰기를 수행하였습니다. 이로써 switch 문이 강조되었습니다. 한 가지 더 주목해서 보셔야 할 것은 casedefault 키워드들과 : 문자 사이에 추가 공백이 들어있다는 점입니다. break 문을 사용하지 않는 case 문에는 반드시 명시적으로 Fallthrough계속 이라는 주석을 달아주십시오. break 문을 빠뜨리는 것은 일반적인 실수이기 때문에, 이와 같은 주석을 명시함으로써 실수가 아니라 의도적으로 break 문을 생략했다는 것을 분명히 할 수 있습니다.
2004-04-14 08:43:56.0 (wgshim 222.108.45.193) D
68. try-catch 문장은 다음과 같은 형태를 따른다:
try {
  statements;
}
catch (Exception exception) {
  statements;
}

try {
  statements;
}
catch (Exception exception) {
  statements;
}
finally {
  statements;
}
 
이 지침은 위에서 언급한 #59 일반적인 블록 규칙에 의거한 것입니다. 이 형태 역시 #61 의 if-else 문이 그랬던 것처럼, Sun 社의 코딩 표준 권고안에서 제시한 것과는 다릅니다. #61 을 참고하시기 바랍니다.
69. 단일 문장의 if-else, for, while 문은 중괄호 없이 작성할 수도 있다.
if (condition)
  statement;

while (condition)
  statement;

for (initialization; condition; update)
  statement;

 
일반적인 코딩 권고안은 Sun 社의 Java 코딩 표준 권고안에도 포함되어 있는 것과 같이, 모든 경우에 항상 중괄호를 사용하여 블록화 하라는 것입니다. 하지만 블록은 여러 문장을 그룹핑하기 위한 용도로 사용되기 때문에, 단일 문장을 묶는 것은 오히려 번거로울 수 있습니다. #52 와 같이 조건을 평가하는 부분과 실행문을 분리해서 기록하며 들여쓰기를 제대로 한다면 크게 문제되지는 않습니다.

6.2 공백문자

70.
- . 를 제외한 모든 binary 연산자(conventional operator)는 피연산자들 사이에 스페이스로 분리한다.
   예외적으로 Unary operator(++, --) 와 피연산자 사이에는 공백을 두지 않는다.
- Java 예약어/키워드 뒤에 공백문자 하나를 추가한다.
- 콤마(,) 뒤에 공백문자 하나를 추가한다.
- 콜론(:) 의 앞뒤에는 공백문자를 추가한다.
- for 문 내의 세미콜론(;) 문자 뒤에 공백문자 하나를 추가한다.
a = (b + c) * d;            // NOT:   a=(b+c)*d
while (true) {              // NOT:   while(true) ...
doSomething (a, b, c, d);   // NOT:   doSomething (a,b,c,d);
case 100 :                  // NOT:   case 100:
for (i = 0; i < 10; i++) {  // NOT:   for (i=0;i<10;i++){
이 방법은 문장의 각 구성요소들을 부각시켜 가독성을 높여줍니다. 여기서 제안하는 Java 코드 상에서의 공백문자 사용에 관한 모든 예제를 다 나열하기는 어렵겠습니다만, 위에 제시한 예제로도 충분히 그 의도를 제시해주고 있습니다.
71. 메소드의 이름과 메소드의 여는 괄호 사이에 공백문자를 사용하지 않는다.
(참고: 본래 이 문서의 원본에는 Sun 社의 권고안과는 달리 '메소드의 이름과 여는 괄호 사이에도 공백문자를 넣어 모든 이름을 부각시키자' 라고 언급하고 있었으나 변경함)
doSomething(currentFile);    // doSomething (currentFile); 가 아님
#69 지침에 의해 Java 의 예약어/키워드 뒤에 공백문자를 추가하기 때문에, 메소드의 이름과 여는 괄호 사이에 공백을 없앰으로써 메소드 호출과 키워드를 구별하는데 도움을 줍니다.
72. 블록 내의 논리적인 유닛들은 빈 라인을 하나 삽입하여 구분한다.
 
블록 내 논리적 유닛들 사이에 공백을 삽입함으로써 가독성을 향상시키게 됩니다.
73. 메소드 선언들 간에는 3-5 개의 빈 라인을 삽입하여 구분한다.
 
메소드 내에서 사용하는 빈 라인보다 많은 라인을 삽입함으로써 클래스 내 각각의 메소드들을 부각시키게 됩니다.
74. 선언문에서의 변수는 타입과 구분하여 변수끼리 좌측정렬 한다.
AsciiFile  file;
int        nPoints;
float      x, y;
타입과 변수를 구분하여 정렬함으로써 변수를 부각시켜 가독성을 높이게 됩니다.
75. 문장은 가독성을 높이기 위하여 정렬한다
if      (a == lowValue)    compueSomething();
else if (a == mediumValue) computeSomethingElse();
else if (a == highValue)   computeSomethingElseYet();


value = (potential        * oilDensity)   / constant1 +
        (depth            * waterDensity) / constant2 +
        (zCoordinateValue * gasDensity)   / constant3;


minPosition     = computeDistance (min,     x, y, z);
averagePosition = computeDistance (average, x, y, z);


switch (value) {
  case PHASE_OIL   : phaseString = "Oil";   break;
  case PHASE_WATER : phaseString = "Water"; break;
  case PHASE_GAS   : phaseString = "Gas";   break;
}
일반적인 지침에 위배될 수도 있겠지만 위 예제에서는 가독성을 높이기 위하여 코드 사이에 공백문자를 넣었습니다. 공백문자를 넣을 때에는 코드의 정렬방식을 준수합니다. 번거로울 수 있겠지만 코드를 정렬함으로써 코드를 한 눈에 들어오게 만들 수 있습니다. 이렇게 공백문자를 넣어 정렬하는 코드정렬에 대해 일반적인 지침을 제시하기는 어렵겠습니다만, 위에 제시한 예제로도 충분히 그 의도를 제시해주고 있습니다.

6.3 주석

76. 난잡한 코드에는 주석을 다는 것보다는, 코드를 구조화하여 재작성하고 명료한 코드에 주석을 다는 것이 낫다 [1] .
 
일반적으로 적절한 이름 선정과 명쾌한 논리 구조를 바탕으로, 코드 자체만으로도 충분히 이해가 되도록 작성하는 것이 우선이고 그 후 주석을 다는 것이 바람직합니다. 주석을 달지 않는 프로그래머를 나쁜 습관을 가졌다고 하지만, 역설적으로 코딩을 정말 잘하는 프로그래머는 주석을 많이 달 필요가 없는 것입니다.
77. 모든 주석은 영어로 작성한다.
 
국제적인 제품을 개발하고자 할 때에는, 주석도 자국어를 사용하는 것 보다는 영어를 사용하는 것이 바람직합니다.
78. JavaDoc 주석이 아닌 모든 주석과 여러 줄을 사용하는 주석에 // 를 사용한다.
// 한 줄 이상의
// 주석이랍니다

// /*
   여러 라인의 주석을 해제합니다.
// */
Java 에서 멀티 레벨 주석을 지원하지 않기 때문에, 디버깅이나 기타 목적으로 사용한 /* */ 블럭의 주석을 해제하는데 // 를 사용하기도 합니다.
79. 주석은 설명하려는 코드와 같은 위치에서 들여쓰기하여 작성한다 [1].
while (true) {          // NOT:    while (true) {
  // Do something       //         // Do something
  something();          //             something();
}                       //         }
이 방법은 주석이 프로그램의 논리적인 구조를 깨뜨리지 않게 해줍니다.
80. collection 변수를 선언할 때에는, 컬렉션에 담겨질 요소들의 공통 타입에 대한 주석을 작성한다.
private Vector  pointList_;  // Point 객체에 대한 Vector
private Set     shapeSet_;   // Shape 객체에 대한 Set
 
이러한 추가적인 주석이 없다면 컬렉션이 어떤 요소들로 구성되어 있는지 파악하기가 어려워지기 때문에, 컬렉션의 요소들을 어떻게 다뤄야 하는지 혼동이 되는 경우가 발생합니다. 컬렉션 변수를 입력 인자로 받아들이는 메소드에서는, JavaDoc 주석을 사용하여 컬렉션 입력 인자의 요소들의 공통 타입을 기술하십시요.
81. 모든 public 클래스와 public 클래스의 public, protected 로 선언된 메소드들에 JavaDoc 방식의 주석을 작성한다.
 
이 방식은 간편하게 온라인 코드 API 문서를 항상 최신내용으로 유지시킬 수 있도록 합니다.
82. 각 파일에는 package 선언문 상단에 다음과 같은 헤더를 삽입한다:
/*
 * (@)# <<파일의 이름>>.java
 *
 * $Header$
 * $Revision$
 * $Date$
 *
 * ====================================================================
 *
 * <<회사 이름>>., Software License, Version 1.0
 *
 * Copyright (c) 2002-2004 <<회사 이름>>.,
 * <<회사 주소>>  * All rights reserved.
 *
 * DON'T COPY OR REDISTRIBUTE THIS SOURCE CODE WITHOUT PERMISSION.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL <<회사 이름>> OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * For more information on this product, please see
 * <<회사 웹 사이트 URL 주소>>
 *
 */
각 파일의 상단에 코드의 소유자(혹은 조직)와 라이센스 및 책임의 한계에 대한 정보를 기술합니다.
83. class, interface 및 메소드 선언문 위에는 다음과 같은 JavaDoc 주석을 삽입한다:
/**
 * 클래스, 인터페이스 메소드에 대한 설명을 기술합니다.
 *
 *   메소드의 경우는 다음 항목들을 기록하십시오:
 *      - 메소드의 작성의도
 *      - 메소드를 사용하기 위한 사전조건(pre condition) 과 사후조건(post condition)
 *      - 부작용
 *      - 종속성 (종속되는 라이브러리, 클래스, 메소드 등)
 *      - 사용시 주의해야 할 점
 *      - 누가 이 메소드를 호출하는가
 *      - 언제 이 메소드를 재정의해야 하는가
 *      - 메소드 재정의 시 어디서 부모 메소드를 호출해야 하는가(super 키워드를 사용하여)
 *      - 제어 흐름 및 상태 전이에 관련된 내용들
 *
 * @author (클래스와 인터페이스에서만 사용합니다)
 * @version (클래스와 인터페이스에서만 사용합니다)
 *
 * @param (메소드와 생성자에서만 사용합니다)
 * @return (메소드에서만 사용합니다)
 * @exception (Javadoc 1.2 의 @throws 와 같은 표현입니다)
 * @see
 * @since
 * @serial (또는 @serialField 나 @serialData)
 * @deprecated (deprecated 된 클래스, 인터페이스, 메소드에 대해 어떻게 대응해야 하는지 기록합니다)
 */

public class Example { ...
 
JavaDoc에 대한 자세한 최신 정보는 이곳을 참고하시기 바랍니다.

7 참고문헌

[1] Steve McConnel, "Code Complete," Microsoft Press. [2] Sun, "Java Code Conventions," http://java.sun.com/docs/codeconv/html/CodeConvTOC.doc.html [3] Netscape, "Software Coding Standards for Java," http://developer.netscape.com/docs/technote/java/codestyle.html [4] NASA, "C / C++ / Java Coding Standards," http://v2ma09.gsfc.nasa.gov/coding_standards.html [5] AmbySoft, "Coding Standards for Java," http://www.ambysoft.com/javaCodingStandards.html

'java core' 카테고리의 다른 글

Threads from aritma  (0) 2005.02.12
Reference object model from java world  (0) 2005.01.28
Garbage collection from javaworld  (0) 2005.01.28
자바 코드 컨벤션  (0) 2005.01.24
J2ME쪽 JSR모음  (0) 2005.01.24
Posted by '김용환'
,