Java 관련 기초 지식들

Enum은 관련이 있는 상수들의 집합이다

C / C++ 은 #ifdef, #ifndef로 특정 코드를 선택적으로 컴파일할 수 있는 전처리기가 있는 반면, 자바는 없다

Java의 String class는 Immutable 한 클래스이다. 따라서 한 번 문자열을 생성하면 문자열의 값을 바꾸는 것은 불가능하다
따라서 replace, replaceAll, toLowerCase등의 메소드는 모두 기존의 문자열은 그대로 두고 새로운 문자열을 리턴한다.

문자열 병합은 StringBuilder (혹은 StringBuffer) 클래스의 append 메소드를 통해 이루어진다

StringBuffer는 멀티 쓰레드 세이프하고, 이때문에 초창기 자바는 StringBuffer를 사용했는데, .append 메소드는 매번 락을 잡았다 풀었다 하였기 때문에 심각한 속도 저하의 원인이 되기도 했었다

Java static 은 '딱 한번만' 메모리 공간에 할당한다

싱글톤 패턴이란 어떤 클래스가 오직 하나만의 객체를 갖도록 하고 프로그램 전반에 그 객체 하나만 사용하는 것이다

멀티쓰레드 환경에서의 올바른 싱글톤
public class Singleton {
    private SingleTon() {}
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}
- 싱글톤 클래스에는 LazyHolder 클래스의 변수가 없기 때문에 Singleton class 로딩 시 LazyHolder 클래스를 초기화하지 않는다
- Lazyholder 클래스는 Singleton 클래스의 getInstance() 메서드에서 Lazyholder.INSTANCE를 참조하는 순간 Class 가 로딩되며 초기화가 진행된다
- Class 를 로딩하고 초기화하는 시점은 thread-safe 를 보장하기 때문에 volatile 이나 synchronized 같은 키워드가 없어도 thread-safe 하면서 성능도 보장한다

자바는 동적으로 클래스를 읽어온다. 즉, 런타임에 모든 코드가 JVM에 링크된다. 모든 클래스는 그 클래스가 참조되는 순간에 JVM에 링크되며, 메모리에 로딩된다.

JVM이 시작되면, 부트스트랩 클래스로더를 생성하고, 그 다음에 첫 번쨰 클래스인 Object를 시스템에 읽어온다

런타임에 동적으로 클래스를 로딩한다는 것은 JVM이 클래스에 대한 정보를 갖고 있지 않다는 것을 의미한다. 즉, JVM은 클래스의 메소드, 필드, 상속관계에 대한 정보를 알지 못한다.
따라서, 클래스로더는 클래스를 로딩할 떄 필요한 정보를 구하고, 그 클래스가 올바른지를 검사할 수 있어야 한다.
JVM은 내부적으로 클래스를 분석할 수 있는 기능을 갖고 있으며, JDK1.1 부터는 개발자들이 리플렉션(Reflection)을 통해서 이러한 클래스의 분석을 할 수 있도록 하고 있다.

클래스를 로딩하는 방식에는 load-time dynamic loading과 run-time dynamic loading 이 있다.

Load-time dynamic loading
public class Helloworld {
    public static void main(String[] args) {
        System.out.println("안녕하세요!");
    }
}
이렇게 하면, JVM이 시작되고 부트스트랩 클래스로더가 생성된 후에 모든 클래스가 상속받고 있는 Object 클래스를 읽어온다. 그 이후에 클래스로더는 명령행에서 지정한 Helloworld 클래스를 로딩하기 위해 Helloworld class 파일을 읽는다.
이 때 Helloworld 클래스를 로딩하는 과정에서 java.lang.String 과 java.lang.System 클래스가 필요하다.
이 두 클래스는 Helloworld 클래스를 읽어오는 과정에서, 즉 로드타임에 로딩된다.
이처럼 하나의 클래스를 로딩하는 과정에서 동적으로 클래스를 로딩하는 것을 로드타임 동적 로딩이라고 한다.

Run-time dynamic loading
public class Helloworld1 implements Runnable {
    public void run() {
        System.out.println("안녕하세요, 1");
    }
}
public class Helloworld2 implements Runnable {
    public void run() {
        System.out.println("안녕하세요, 2");
    }
}
public static void main(String[] args) {
    try {
        if (args.length < 1) {
            System.out.println(" .. ");
            System.exit(1);
        }
        Class klass = Class.forName(args[0]);
        Object obj = klass.newInstance();
        Runnable r = (Runnable) obj;
        r.run();
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}
이처럼 클래스를 로딩할 때가 아닌 코드를 실행하는 순간에 클래스를 로딩하는 것을 런타임 동적 로딩이라고 한다.

Java delegate 패턴이란 한 객체가 모든 일을 수행하는 것이 아니라 어떤 일 중 일부를 다른 객체에게 위임 (delegate) 하는 것이다

어떤 프로세스 안에서 생성된 쓰레드들은 같은 메모리 공간을 공유하는데, 이 때 Java는 공유자원에 대한 접근을 제어하기 위한 동기화 방법으로 synchronized 와 Lock 을 제공한다

synchronized 키워드는 메서드에 적용할 수도 있고 (public, private 등 다음에), 특정 코드 블록에 적용할 수도 있다.

synchronized 키워드는 하나의 데이터에 대해서 동시에 여러개의 쓰레드가 사용하려고 할 때 한 시점에서 하나의 쓰레드만 사용할 수 있게 하는것이다
- 하나의 객체에 여러개의 쓰레드가 접근하여 처리하고자 할 때
- static 으로 선언한 객체에 여러 쓰레드가 동시에 사용할 때


Java volatile은 멀티 쓰레딩 환경에서 동기화를 해주는 키워드로, 읽기 쓰기시에 어떤 쓰레드가 값을 변경하든 항상 최신값을 읽어갈 수 있게 해준다

작업시 그 변수의 접근에 대해 같은 값을 읽어갈 수 있게 해주는 것은 volatile이나 synchronized나 동일하다

Java volatile은 변수의 가시성(visibility)를 보장한다. 다시 말해, volatile 변수를 읽어들일 때 CPU 캐시가 아니라 컴퓨터의 메인 메모리로부터 읽고, 쓸 때도 마찬가지 (?)

volatile 변수들의 읽고 쓰기 연산은 JVM에 의해 재배치되지 않는다.
non-volatile  변수의 읽기와 쓰기는 volatile 변수의 읽기 / 쓰기 전과 후에 위치하면 재배치 되지 않는다

어떤 쓰레드에서 특정 변수를 읽고 쓰고, 다른 쓰레드에서는 읽기만 하는 경우 volatile 이 적절하나, 모든 쓰레드가 읽기 / 쓰기가 가능한 경우 synchronized로 원시성을 보장해야 하고, volatile 만으로는 충분하지 않다.

final
- 변수 : final 이 적용된 변수의 값은 초기화된 이후에는 변경할 수 없다
- 메서드 : final 로 선언된 메서드는 하위 클래스에서 오버라이드 할 수 없다
- 클래스 : final 로 선언된 클래스는 계승 대상이 될 수 없다 (하위 클래스를 만들지 못한다)

finally
- try / catch 와 함꼐 사용되며 예외가 던져지거나 return 문이 실행되더라도 실행된다. 단, 해당 쓰레드가 죽거나 jvm이 종료되면 실행되지 않을 수 있다.

finalize
- gc는 객체를 삭제하기 전에 finalize() 메서드를 호출한다. 따라서 객체가 삭제되기 직전에 실행되어야 하는 동작이 있다면 Object 클래스에 정의된 finalize() 메서드를 오버라이드하여 정의할 수 있다

Java collection
- ArrayList : 동적으로 크기가 조정되는 배열
- Vector : Arraylist 와 비슷하지만 다중 쓰레드에서 안전하도록 synchronized 된다
- LinkedList : iterator() 사용이 가능하다
- HashMap : ...

Java의 모든 쓰레드는 java.lang.Thread 클래스로 만들고 제어한다.

독립적인 응용 프로그램의 경우, main 함수를 호출하면 자동적으로 하나의 사용자 쓰레드가 만들어지는데, 이 쓰레드를 주 쓰레드 (main thread) 라고 한다.

Java 에서 쓰레드를 구현하는 방법은 다음의 두 가지이다.
- java.lang.Runnable 인터페이스를 구현
- java.lang.Thread 클래스를 계승

lock
- 모니터라고 하기도 한다
- 좀더 세밀하게 동기화를 제어하고 싶다면, 락을 이용한다
- 락을 공유 자원에 붙히면 해당 자원에 대한 접근을 동기화할 수 있다
- 쓰레드가 해당 자원을 접근하려면 우선 그 자원에 붙어있는 락을 획득하여야 한다
- 특정 시점에 락을 쥐고 있을 수 있는 쓰레드는 하나뿐이다
- 따라서 해당 공유자원은 한 번에 한 쓰레드만이 사용할 수 있다

교착상태가 발생하려면, 다음의 네 가지 조건이 모두 충족되어야 한다
1. mutual exclusion : 한번에 한 프로세스만 공유 자원을 사용할 수 있다
2. hold and wait : 공유 자원에 대한 접근 권한을 갖고 있는 프로세스가
                             그 접근 권한을 양보하지 않은 상태에서
                             다른 자원에 대한 접근 권한을 요구할 수 있다
3. preemption impossible : 한 프로세스가 다른 프로세스의 자원 접근 권한을 강제로 취소할 수 없다
4. circular wait : 두 개 이상의 프로세스가 다른 프로세스가
                          자원 접근 권한을 개방하기를 기다리는데, 그 관계의 사이클이 존재한다.

댓글

이 블로그의 인기 게시물

Java language specification 정리