2011/09/25 01:54

Java 의 철학 - Write Once, Run Evrywhere

과연 이 철학이.....



- The Java Programming Language

객체지향... 이놈은 자바 언어 Software 기술중에 하나.
하지만 가장 큰 특징은 생산성의 극대화, 동적인면임!
Multi-Threding
구조화된 에러 핸들링
Garbage Collection
Dynamic Linking
Dynamic Extension


- The Java Class File Format

compact 한 형태
bytecode 로의 변경
platform 독립적
network byte order 사용

class 파일은 bytecode 를 binary 형태로 담아놓은 것.
bytecode 는 JVM 이 읽을 수 있는 언어.

JVM 은 Class 를 로딩한 후 여기서 Bytecode 를 읽어들여 실행이 가능하도록 Interpret 하는 과정을 거침. Class 가 Load 된 후 JIT Complier 나 Hotspot Compiler 와 같은 Execution Engine 을 거쳐 실행이 된다.

ByteCode 는 Source code 를 단순히 JVM 의 언어로 번역해 놓은 것이기 때문에 Source 파일과 비슷한 크기를 가지고 있다. C++, Delphi 와 같은 언어에 비해 아주 작은 크기. 이렇게 작은 크기를 유지할 수 있는 이유는 Class 파일에는 실제로 참조하는 라이브러리를 포함하고있지 않고, 단지 Symbolic Reference 만을 가지고 있기때문...

Symbolic Reference 는 참조하고자 하는 대상의 이름만으로 참조관계를 구성한 것을 의미. 참조하는 객체의 특정 메모리 번지로 참조관계를 구성한것이 아니고 참조하는 대상의 이름만을 지칭한 것.

Class 파일이 JVM 에 올라가게 되면 Symbolic Reference 는 그 이름에 맞는 객체의 주소를 찾아서 연결하는 작업을 수행함. 이를 Dynamic Linking 이라고 한다. Java 는 이 Dynamic Linking 때문에 Class 파일은 compact 한 형태를 유지할 수 있다.

Class File Format 은 Network Byte Order 를 사용한다. 서로 다른 계열의  CPU 끼리 데이터를 전송 받을 때의 문제점을 해결하기 위해 정해준 일족의 약속임. Big Indian 을 사용.



- The Java Application Programming Interface (Java API)

Runtime Library의 집합. 말 그대로 Java 실행 환경. 여기에는 Java Virtual machine 과 Java API, 그리고 Native Method 등이 포함되어있다.

Java API 는 OS 시스템과 Java 프로그램 사이를 이어주는 가교의 역할을 한다. Native Method 를 통해 OS 자원과 연계되어있고 다른 한 편으로는 Java 프로그램과 맞닥뜨리고 있다. Interface 의 역할을 하고있는 셈.


- The Java Virtual Machine (JVM) 

JVM 은 하나의 스펙.







 
Posted by 개발자 용이~
2009/08/24 21:50

실행가능한 JAR  만들기.

컴파일된 파일들이 classes 디렉토리에 들어있다고 했을 경우.

실행가능한 파일을 만들기 위해서는 main() 메소드가 어떤 클래스에 들어잇는지를 알려주는 manifest파일이 필요합니다.

다음과 같이 manifest파일을 만들어 줍니다.

manifest.txt
Main-Class: MyApp

jar도구를 실행시켜서 classes 디렉토리에 들어있는 모든 파일이 저장된 jar파일을 만듭니다.

$cd classes
$jar -cvmf manifest.txt myapp.jar *.class


jar파일을 실행시킬경우의 명령
$java -jar myapp.jar



패키지를 가지고 만들기~

manifest.txt
Main-Class: com.yoyojy.MyApp

역시 classes디렉토리로 이동후에 명령어들을 써줍니다.
$jar -cvmf manifest.txt myapp.jar com
com 디렉토리만 지정하면 필요한 것들이 모드 jar파일로 들어갑니다.



이 외의 옵션들

-tf : 목록을 출력하고 파일의 내용을 풀어놓는다.
-xf : 파일들을 추출한다.

jar {ctxu}[vfm0M] [jar-file] [manifest-file] [-C dir] files ...
옵션:
    -c  새 아카이브를 만듭니다.
    -t  아카이브에 대한 목차를 나열합니다.
    -x  아카이브에서 명명된 (또는 모든) 파일을 추출합니다.
    -u  기존의 아카이브를 업데이트합니다.
    -v  표준 출력에 대한 자세한 정보 출력을 생성합니다.
    -f  아카이브 파일 이름을 지정합니다.
    -m  지정된 증명 파일에서 증명 정보를 포함시킵니다.
    -0  저장만 수행하며 ZIP 압축을 사용하지 않습니다.
    -M  입력 항목에 대한 증명 파일을 만들지 않습니다.
    -i  지정된 jar 파일에 대한 색인 정보를 생성합니다.
    -C  지정된 디렉토리로 변경하고 다음 파일을 포함시킵니다.
디렉토리인 파일이 하나라도 있으면 재귀적으로 처리됩니다.
'm' 및 'f' 플래그가 지정된 순서대로 증명 파일 이름과 아카이브 파일 이름을 지정해야 합니다.





Posted by 개발자 용이~
2009/08/04 13:39
이글의 거의 대부분은
자바가상기계 메모리 할당을 보여주는비주얼 도구의 개발(경남대학교 컴퓨터공학과 한국전자통신연구원 실시간시스템연구팀) 이라는 자료를 참조 하였습니다.

자바 가상 머신의 구조

클래스 로더 서브시스템(class loader subsystem), 실행 엔진(execution engine), 런타임 데이터영역(runtime data areas)으로 구성되어 있다.

클래스 로더 서브시스템
클래스(class)나 인터페이스(interface)의 타입을 위한 이진 데이터를 찾아 적재(loading)하고, 정확성을 검증한다. 또한 클래스 변수에 대한 메모리 할당과 디폴트 값으로 초기화를 수행하고, 그 타입에 대한 심볼릭 참조를 직접 참조로 변환한다. 그리고 적당한 시작값에 대한 클래스 변수를 초기화하는 자바 코드를 호출한다.

런타임 데이터 영역
자바 가상기계가 프로그램을 실행시키는데 필요한 메모리를 구성하고, 적재된 클래스 파일로부터 추출된 바이트 코드를 포함한 많은 정보들이 저장되는 메모리 영역이다. 세부적으로 메소드 영역(method area), 자바 스택영역(jaa stack area), 힙 영역(heap), PC 레지스터(PC register), 원시 메소드 스택(native method stack)으로 구성된다.

- 메소드 영역
적재된 이진 데이터의 타입에 관한 정보를 저장한다. 즉, 이진 데이터에서 타입에 관련된 정보를 추출해서 저장하는 영역이다. 가상 기계는 호스팅하는 응용 프로그램을 실행시 이 영역에 저장된 타입정보를 찾아 사용한다.

메소드 영역에 저장되는 정보

타입 정보( type information )
------------------------------------------------
     타입의 완전한 이름
     타입의 슈퍼클래스의 완전한 이름
     타입 식별 정보 : 클래스 또는 인터페이스
     타입의 식별자
     슈퍼인터페이스의 완전한 이름에 대한 정렬 리스트
------------------------------------------------
상수 풀( constant pool )
------------------------------------------------
필드 정보( field information )
------------------------------------------------
     필드의 이름
     필드의 타입
     필드의 수식어
------------------------------------------------
메소드 정보( method information )
------------------------------------------------
      메소드 이름
     메소드 반환 타입(또는 void)
     메소드 파라메터의 타입과 개수
     메소드의 수식어
     메소드의 바이트코드
     피연산자 스택, 지역변수 섹션 크기
     예외 테이블
------------------------------------------------
클래스 변수( class variable)
------------------------------------------------
     클래스 로더 클래스
     클래스 클래스


- 힙
프로그램이 실행될 때 자바 가상머신은 프로그램이 인스턴스화한 모든 객체를 힙에 할당한다. 자바 가상머신 인스턴스 내부에는 단 하나의 힙이 존재하기 때문에 런타임시 모든 쓰레드는 힙을 공유하고, 이러한 객체(힙 데이터)에 접근하기 위해서 멀티쓰레드의 적당한 동기화와 관계한다

- 프로그램 카운터
실행되는 프로그램의 각 쓰레드는 자신의 PC레지스터와 자바 스택을 가지는데 이것은 쓰레드가 시작될 때 생성되고, PC레지스터의 값은 실행할 다음 명령어를 가리킨다.

- 자바스택
새로운 쓰레드가 생성되면 자바가상머신은 그 쓰레드를 위한 자바 메소드 호출의 상태(지역변수, 매개변수, 리턴 값, 중간계산)을 저장하고, 현재 클래스와 현재 상수 풀의 트랙을 유지한다. 또한 쓰레드의 스택에 새로운 프레임(frame)을 형성해 푸시(push)와 팝(pop) 두가지 연산을 수행한다.

-스택 프레임
스택 프레임은 지역변수(local variable), 오퍼란드 스택(operand stack), 프레임 데이터(frame data)를 구성한다.
자바 가상머신이 자바메소드를 생성할 때 스택 프레임은 지역변수와 오퍼란드 스택에서 메소드에 의해서 요구되는 워드 수를 결정하기 위재서 클래스 데이터를 검사한다. 자마그택프레임의 지역변수는 위드의 배열로 구성되고, 메소드의 매개변수와 지역변수를 포함한다.컴파일러는 선언되어진 순서에 따라 지역변수 배열에 매개변수와 지역변수를 배치한다.
오퍼란드 스택도 워드의 배열로 구성되어있다. 하지만 지역변수와는 달리 배열 인덱스를 통해서 접근된다. 오퍼란드 스택은 값을 푸시하고 팝하므로써 접근되어지고, 오퍼란드 스택에 값을 푸시했다면, 이후의 명령어는 팝해서 그 값을 사용한다.

- 원시 메소드 스택
쓰레드가 자바로 작성된 메소드가 아닌 다른 언어로 작성된 메소드를 호출하면 이 메소드에 대한 데이터 영역이 생성된다.

실행엔진
적재된 클래스의 메소드들에 포함된 명령어들을 실행한다. 실행 엔진이 명령어들을 실행할 때 자바 가상머신은 현재의 클래스의 상수 풀, 현재 프레임의 지역변수, 현재 프레임의 오퍼랜드 스택의 탑(top)에 놓은 값들을 사용한다.
Posted by 개발자 용이~
2009/06/14 22:51
오랜만에 글을 쓰게되는군요..
예전 I/O공부하던 것에 이어서 자바 I/O에 관련된 내용들을 계속해서 공부해 보겠습니다. 참고서적은 자바I/O & NIO 네트워크 프로그래밍(한빛미디어)입니다.

이 책에나오는 IO클래스를 사용할 때 반드시 지켜야 할 내용은
- try문에서 사용할 IO클래스를 선언한다. 보통 null값을 할당한다. (그러니까 IO클래스의 레퍼런스를 선언할 때 에러처리를 하는 try구문위에 선언을 한다는 말입니다.)
- try블록안에서 IO클래스 객체를 생성한다. (new 키워드는 try블록에서!!)
- finally블록안에서 IO클래스의 close()메소드를 호출한다. 

파일의 내용을 읽어오는 예제입니다. Stream형식(바이트 단위)으로 읽어오기

package ch04;


import java.io.*;


public class FileView2 {

  public static void main(String[] args) {

    if (args.length != 1) {

      System.out.println("사용법 : java FileView2 파일명");

      System.exit(0);

    }

    

    FileInputStream fis = null;

    try {


      fis = new FileInputStream(args[0]);


      int readcount = 0;

      byte[] buffer = new byte[512];

      while ( (readcount = fis.read(buffer)) != -1 ) {

        System.out.write(buffer, 0, readcount);

      }

      

    } catch (Exception e) {

      e.printStackTrace();

    } finally {

      try {

        fis.close();

      } catch (IOException e) {

        e.printStackTrace();

      }

    }

  }

}


중간쯔음의 굵게 표시된부분을

int i = 0;

while ((i = fis.read()) != -1) {

  System.out.write(i);

}

이렇게 바꾸어도 되지만... 자바프로그래밍으로 1바이트를 읽어오라고 실행하면, 운영체제는 실제로 1바이트를 읽어오지않고 256바이트나 512바이트를 읽어온다고 합니다. 굵게표시된부분말고 밑에처럼 프로그래밍하게되면 1000바이트 파일이라고 할 때, 내부적으로는 512바이트씩 1000번을 읽어온다네요. 이게 파일의 크기가 작으면 상관이 없지만 커질경우에는 문제가 있겠죠. 그러므로 운영체제가 읽어올수있는 크기로 버퍼의 크기를 정해주면 빠르게~ 낭비도 없이 짜여진 프로그램이 되겠죠^^


다음은 파일복사의 예제입니다. Stream형식(바이트 단위)

package ch04;


import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;


public class FileStreamCopy {

  public static void main(String[] args) {

    if (args.length != 2) {

      System.out.println("사용법 : java FileStreamCopy 파일1 파일2");

      System.exit(0);

    }

    

    FileInputStream fis = null;

    FileOutputStream fos = null;

    

    try {

      fis = new FileInputStream(args[0]);

      fos = new FileOutputStream(args[1]);

      byte[] buffer = new byte[512];

      int readCount = 0;

      while ( (readCount = fis.read(buffer)) != -1 ) {

        fos.write(buffer, 0, readCount);

      }

      System.out.println("복시가 완료되었습니다.");

      

    } catch (Exception e) {

      e.printStackTrace();

    } finally {


      try {

        fis.close();

      } catch (IOException e) {

        e.printStackTrace();

      }

      try {

        fos.close();

      } catch (IOException e) {

        e.printStackTrace();

      }

      

    }

  }

}

Posted by 개발자 용이~
2009/06/14 22:26
다음에 나오는 글은 자바I/O & NIO 네트워크 프로그래밍(한빛미디어)에 나오는 내용입니다.
저도 잘 모르고 쓰고 있었군요...

다음 소스의 실행 결과를 맞춰봅시다.

package ch03;


public class InheritanceTest {

  public static void main(String[] args) {

    

    FirstChild fc = new FirstChild();

    System.out.println(fc.read());

    

    SecondChild sc = new SecondChild();

    System.out.println(sc.read());


    ThirdChild tc1 = new ThirdChild(fc);

    System.out.println(tc1.read());


    ThirdChild tc2 = new ThirdChild(sc);

    System.out.println(tc2.read());

  }

}



class Parent {

  public String read() {

    return "Parent 입니다.";

  }

}


class FirstChild extends Parent {

  @Override

  public String read() {

    return super.read() + ": FirstChild";

  }

}


class SecondChild extends Parent {

  @Override

  public String read() {

    return super.read() + ": SecondChild";

  }

}


class ThirdChild extends Parent {

  Parent p;

  

  public ThirdChild(Parent p) {

    this.p = p;

  }

  

  @Override

  public String read() {

    return p.read() + ": ThirdChild";

  }

  

}


결과는!!

더보기



다음문제입니다.

package ch03;


class Parent2 {

  int i = 7;

  public int get() {

    return i;

  }

}


class Child2 extends Parent2 {

  int i = 5;

  @Override

  public int get() {

    return i;

  }

}


public class ChildTest {

  public static void print(Parent2 p) {

    System.out.println(p.i);

    System.out.println(p.get());

  }

  

  public static void main(String[] args) {

    Parent2 p = new Parent2();

    System.out.println("----- 1 -----");

    System.out.println(p.i);

    System.out.println(p.get());

    

    Child2 c = new Child2();

    System.out.println("----- 2 -----");

    System.out.println(c.i);

    System.out.println(c.get());

    

    

    Parent2 p2 = new Child2();

    System.out.println("----- 3 -----");

    System.out.println(p2.i);

    System.out.println(p2.get());

    

    System.out.println("----- 4 -----");

    print(c);

    print(p2);

  }

}


결과는

더보기


다 맞추셨나요??
이 문제들은 상속과 오버라이에 관한 문제입니다.

여기서 오버라이드란(override) 직역을 한다면 "올라타다"라는 뜻이라고 하네요.
----- 3 -----, ----- 4 ----- 부분이 중요한부분이죠.

여기서 살펴보변 Child2객체가 메모리에 올라가게 되면, Child2에 있는 필드i와 Parent2에 있는 i가 모두 메모리에 올라가게됩니다. 이 경우에 객체를 가리키는 참조변수가 Parent2라면 필드는 Parent2의 것을 사용하게 됩니다. 
하지만 메소드가 오버라이딩 되는 경우, 부모의 메소드는 사라지고 자식에서 선언된 메소드가 사용된다고 합니다.

여기서 정리1
- 부모는 자식을 가리킬 수 있다. 조상은 자손을 가리킬수 없다.
- 만약, 자식이나 자손이 메소드를 오버라이딩 하고 있으면, 메소드의 기능은 자식이나 자손이 구현한 것을 따른다.

다음으로 public static void print(Parent2 p) 메소드의 정의를 살펴봅시다.
이것을 초보들은 "메소드 print는 인자로 Parent2를 받아들인다." 라고 해석한다고 하네요; 이 말은 반은 맞고 반은틀린데 
"메소드 print는 인자로 Parent2와 Parent2의 자손을 받아들인다." 라고 해석되어야 맞는 말이랍니다. 

Posted by 개발자 용이~
2009/04/15 00:14
접근 제어자는 멤버 또는 클래스에 사용되어, 해당하는 맴보 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 합니다.
만약에 클래스, 멤버변수, 메소드, 생성자에 접근제어자가 지정되어있지 않다면 접근 제어자는 default(friendly) 임을 뜻합니다.

private : 같은 클래스 내에서만 접근이 가능하다. 이 접근 권한으로 지정된 변수 또는 메소드를 다른 객체에서 참조하거나 사용하는 것이 불가능. 자신의 클래스 내에 있는 메소드에서만 참조하거나 사용할수 있음.

default : 같은 패키지 내에서만 접근이 가능하다. 하위클래스에서는 접근이 불가

protected : 같은 패키지 내에서, 그리고 다른 패키지의 하위클래스에서 접근이 가능하다 

public : 접근 제한이 없다.
Posted by 개발자 용이~
2009/03/19 22:19
자바의 IO란 말 그대로 자바 프로그램에서의 입력 작업과 출력 작업을 의미합니다. java.io 패키지에 존재하는 클래스를 사용할줄 알아야 합니다.
읽고 쓸 수 있는 클래스들은 크게 두 가지로 나뉘어 지며, 바이트 단위로 읽고 쓸수있는 바이트 스트림 클래스(8비트)와 문자 단위로 읽고 쓸수있는 문자 스트림 클래스(16비트)로 나뉩니다.

일단 스트림에 대해서 먼저 알아보겠습니다.

스트림 이란?


- 데이터를 소스로부터 입력 받거나 또는 목적지로 출력할 수 있도록 해주는것.

- 자바의 입출력을 도와주는 매개체.


스트림의 특징

- 단방향이다. (입력, 출력 스트림이 따로 존재)

- fifo 구조이다.

- 스트림끼리의 연결이 가능 (Filter Chain)



데이터 종류에 따른 분류

 데이터 종류  입력 출력 
 바이트(byte)  InputStream  OutputStream
 문자(char)  Reader  Writer

※그 때 그 때의 상황에 맞게 스트림을 사용하면 됩니다. 문자 텍스트파일 같은 경우 문자스트림 클래스를 쓰면 좋겠죠?? 한국어 같은경우 1바이트로 처리를 하게되면 깨져서 나오게 됩니다!!


자바 IO클래스명에 사용된 단어의 의미  (출처 : 자바 I/O & NIO 한빛미디어)
 클래스에 사용된 단어  의미
 Stream으로 끝나는 클래스 바이트 단위 IO 클래스 이다. 
InputStream으로 끝나는 클래스  바이트 단위로 입력을 받는 클래스이다. 
OutputStream으로 끝나는 클래스  바이트 단위로 출력하는 클래스이다.
Reader로 끝나는 클래스  문자 단위로 입력을 받는 클래스이다. 
 Writer로 끝나는 클래스 문자 단위로 출력을하는 클래스이다. 
File로 시작할 경우
(File클래스 제외) 
파일로 부터 입력이나 출력하는 클래스다. 
ByteArray로 시작할 경우  입력 클래스의 경우, 바이트 배열로 부텅 읽어 들이고, 출력 클래스의 경우, 클래스 내부의 자료구조에 출력한후 출력된 결과를 바이트 배열로 반환하는 기능이 있다. 
 CharArray로 시작할 경우 입력 클래스의 경우, char배열로부터 읽어 들이고,
츨력클래스의 경우, 클래스 내부의 자료구조에 출력한 후 출력된 결과를 char배열로 반환하는 기능이 있다. 
 Filter로 시작할 경우 Filter로 시작하는 IO클래스는 직접 사용하는 것보다는 상속을 박아 사용하며, 사용자가 원하는 내용만 필터링할 목적으로 사용된다.
 Data로 시작할 경우 다양한 데이터형식을 입출력할 목적으로 사용한다. 특히 기본형 값(int, float, double 등)을 출력하는 데 유리하다. 
Bufferd로 시작할 경우  프로그램에서 Buffer라는 말은 메모리를 의미한다. 입출력 시에 병목현상을 줄이고 싶을 경우에 사용한다. 
RandomAccessFile  입력이나 출력을 모두 할 수 있는 클래스로써, 파일에서 임의 위치의 내용을 읽거나 쓸 수 있는 기능을 제공한다. 



BufferdReader


키보드로부터 한 줄 입력받아 출력하기

import java.io.*;


public class BufferdReaderTest {

  public static void main(String[] args) {

    InputStreamReader isr = new InputStreamReader(System.in);

    BufferedReader br = new BufferedReader(isr);

    String line;

    try {

      line = br.readLine();

      System.out.println("키보드로 부터 입력받은 문자열 : " + line);

    } catch (IOException e) {

      e.printStackTrace();

    }


  }

}



InputStreamReader isr = new InputStreamReader(System.in);

BufferedReader br = new BufferedReader(isr);


위의 두줄을 밑에처럼 하나로 할 수도 있습니다.


BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

Posted by 개발자 용이~
2009/03/19 16:58
컴파일 처리방식에 따른 분류.

- Checked Exception
   컴파일러가 명시적으로 예외처리를 요구함.
   예외처리를 안하면 컴파일 에러가 발생.
   IOException, SQLException

- Unchecked Exception
   컴파일러가 명시적으로 예외처리를 요구하지 않는다.
   예외처리보다 디버깅으로 해결.
   RuntimeException



예외 처리 방법

1. try/catch문 이용 
- 이 건 다 아실꺼라 생각합니다.^^;예외처리시에 catch문은 순차적으로 실행이 됩니다. 
- 따라서 Exception계층구조에서 상위 클래스는 나중에 catch를 해주어야 작은 것부터 catch할 수 있겠죠.
- finally 문은 예외의 발생여부와 상관 없이 반드시 수행됩니다. 빵굽는 기계가 있는데 빵을 다 구우면 전원이 꺼지게 되어있습니다. 그리고 중간에 오류가 나게되면 전원이 꺼지게 합니다. 이럴 때~ finally로 묶어 줄수 있는 경우가 되겠습니다. 
- 실제적으로 JDBC 노가다로 짤 때 쓰이고있구요... 커넥션 닫기...

2. throws 이용 
- 예외를 메소드를 호출한 코드로 전달하도록 해줍니다. 즉 예외가 발생한 곳에서 예외처리를 안하고 호출한 곳으로 발생된 예외를 떠넘기는 것이죠. 계속해서 throws로 넘겨버릴 경우 적어도 main메소드에서 try/catch로 처리를 해주어야 합니다.
- RuntimeException은 throws해 줄 필요가 없습니다. 자동으로 넘겨준다네요.



스스로 예외형을 생성하기.


먼저 Exception 하위 클래스를 생성합니다.
그리고 Exception를 던지고싶은 데에서 밑의 내용처럼 던져 주면 됩니다.
throw new 뭐시기Exception([args]);
예외도 객체이기 때문에 new 키워드로 생성시켜 줘야 합니다.
그리구 throw키워드로 예외 객체를 전달합니다.

클래스의 예 

public class StudentNameFormatException extends IllegalArgumentException {

  public StudentNameFormatException(String message) {

    super(message);

  }

}

StudentNameFormatException 클래스가 IllegalArgumentException을 확장하고 있죠. 


throw의 예

...

    if (name == null || name.length() == 0)

      throw new StudentNameFormatException("이름이 읍써열~");

...
Posted by 개발자 용이~
2009/03/12 16:18
쓰레드 때문에 병행성 문제가 유발 될수 있습니다. 쓰레드 두 개 이상이 객체의 데이터 하나에 접근하게 되는 것이죠. 화장실 한칸에서 좌변기를 하나만 쓸수있는데 두 사람이 같이 쓰려고하는 상황입니다.(더러운 비유가 이해가 잘 되는것 같네요.ㅋㅋ) 이런 문제점을 해결하기위해 자바에서 동기화를 해줄 수 있습니다.

일단 운영체제쪽에서 보겠습니다.

임계영역 (Critical Section)
- 두개 이상의 쓰레드에서 동시에 수행되면 안되는 영역.
- 위에서 말했듯 화장실에서 좌변기가 임계영역에 속하고, 좌변기를 이용하려고 하는 사람들이 쓰레드라 할수 있겠죠.

동기화
- 공유영역에 둘 이상의 쓰레드가 접근할 때 발생되는 문제점을 해결.
- 뮤텍스와 세마포어가 있음. 크게 본다면 뮤텍스는 쓰레드 하나만, 세마포어는 1~n개를 접속 할 수있도록 해준다고하네요.

※뮤텍스(Mutex - 상호배제)
- Busy wait : 자원을 사용할 수 있는지 계속 확인(화장실에 계속 노크함)
- Lock / sleep : 자원을 사용할 수 없다면 사용 할 수 있을 때까지 재워둠.(락을 걸기 / 화장실 다쓴 사람이 알려주기)


위에 나열 했듯이 이런 동기화 방법들이 있습니다. 이제 자바에서의 동기화에 관하여 알아보겠습니다.

자바의 동기화

- 모든 객체에 있는 Lock을 이용한다.

- Lock이란 공유객체에 여러 쓰레드가 동시에 접근하지 못하도록 하기위한 것으로 객체가 heap메모리에 생성 될 때 자동으로 만들어진다.

- 쓰레드는 Lock을 가져야 running 될 수 있다.

- synchronized키워드를 이용한다.

- 좌물쇠와 열쇠를 생각해보세요~ 밑에 설명은 헤드퍼스트 자바에 있는 내용입니다. (라이언과 모니카 예제)

1. 계좌 거래(잔고 확인과 인출)와 관련된 락이있습니다. 열쇠는 하나밖에 없고 누군가가 계좌에 접근하기 전까지는 락과 함께 있습니다.
→ 아무도 계좌를 사용하고 있지 안을때는 은행계좌 거래가 열려 있습니다. 

2. 라이언이 은행 꼐좌를 사용하려면 락을 잠그고 열시는 자기가 가지고 있어야 합니다. 그러면 다른 사람들은 그 계좌를 사용할 수 없습니다. 
→ 계좌에 접근 할 때 좌물쇠를 잠그고 열쇠를 가져갑니다.

3. 거래를 씉내기 전까지 라이언은 열쇠를 계속 가지고 있습니다. 하나밖에 없는 열쇠를 라이언이 가지고 있기 때문에 모니카는 라이언이 좌물쇠를 풀고 열쇠를 반납하기 전까지는 계좌를 접근할 수 없습니다.
→ 라이언이 거래를 끝내고나면 좌물쇠를 풀고 열쇠를 반납합니다. 이제 모니카(또는 라이언이 또 쓸수도 있죠~)도 그 열쇠를 쓸 수있습니다.


동기화 전의 라이언과 모니카 예제

// 은행 계좌

class BankAccount {

  private int balance = 100; // 잔고 100달러


  // 잔고 확인

  public int getBalance() {

    return balance;

  }


  // 인출

  public void withdraw(int amount) {

    balance = balance - amount;

  }

}


public class RyanAdnMonicaJob implements Runnable {


  private BankAccount account = new BankAccount();


  public static void main(String[] args) {

    RyanAdnMonicaJob theJob = new RyanAdnMonicaJob(); // Runnable갹체의 인스턴스 만들기

    // 똑같은 통장(똑같은 인스턴스)을 사용하는 두개의 쓰레드를 만듭니다.)

    Thread one = new Thread(theJob);

    Thread two = new Thread(theJob);

    one.setName("Ryan");

    two.setName("Monica");

    one.start();

    two.start();

  }


  public void run() {

    for (int x = 0; x < 10; x++) {

      makeWithdrawal(10);

      if (account.getBalance() < 0)

        System.out.println("Overdrawn!!");

    }


  }


  private void makeWithdrawal(int amount) {

    if (account.getBalance() >= amount) {

      System.out.println(Thread.currentThread().getName()

          + " is about to withdraw");


      try {

        System.out.println(Thread.currentThread().getName()

            + " is going to sleep!!");

        Thread.sleep(1000);

      } catch (InterruptedException ex) {

        ex.printStackTrace();

      }

      System.out.println(Thread.currentThread().getName() + " work up.");

      account.withdraw(amount);

      System.out.println(Thread.currentThread().getName()

          + " completes the withdrawal");

      System.out.println("잔고가 " + account.getBalance() + "달러 남았습니다.");

    } else {

      System.out.println("*Sorry, not enough for "

          + Thread.currentThread().getName());

    }


  }

}

위의 예제에서 makeWithDrawal()메소드는 원자적으로 작동해야 합니다!!
이 메소드 앞에 synchronized  라는 키워드를 추가해준 후에 다시 실행시켜 보세요. 결과는 눈에 뛰게 달라집니다.

동기화 시에 성능상향을 위한 방법으로 wait(), notify() 를 사용하는 방법도 존재 합니다. 이부분은 생략합니다.
잘은 모르지만 여기저기 찾아본 결과로 보면 배열이나 List형식에 데이터를 넣어줄때 / 삭제할 때 쓰도 있는데
삭제메소드에 size가 0일때 this.wait() 인서트메소드에 this.notify() 이런 식으로 들어가네요.
Posted by 개발자 용이~
2009/03/11 17:07
앞의 글에서 보았던 쓰레드의 상태에 대하여 보충하겠습니다. (운영체제 과목과 약간 차이가 날 수있으나 동일한 내용이죠 머~ㅋ) 이글의 내용 출처는 OJT KOREA 동영상 강의쪽을 많이 참고 하였습니다. 거의 그대로내요;
또한 상태에 대하여 알아본 후 쓰레드를 대기 상태로 바꿔줄 수있는 메소드들을 알아보겠습니다.

- Runnable 상태 : 쓰레드가 실행되기위한 준비 단계
- Running 상태 : 스케줄러에 의해 선택된 쓰레드가 실행되는 단계
- Blocked 상태 : 쓰레드가 작업을 완수하지 못하고 잠시 작업을 멈추는 단계

그림으로 보면~

쓰레드를 대기 상태로 전환시키기!!
대기상태로 변경 해주는 sleep(), yield(), join() 메소드가 있습니다.

sleep()
- 뜻 대로 시간 동안 잠을 재워 버립니다.
- 다른 쓰레드에게 running할 기회를 주기 위해서 사용됨.
- 기아상태에 빠지는 것을 방지 할 수 있음.
- Thread.sleep(1000); // 밀리세컨드 동안 대기 상태로 존재하도록 함

yield()
- 이것두 뜻대로 다른 쓰레드에게 양보를 해줍니다.
- 우선순위가 같거나 높은 쓰레드에게 running 할 기회를 줌
- Thread.yield();

join()
- join()메소드를 호출한 쓰레드가 종료될 때까지 현재 running된 쓰레드가 대기 상태로.


1. sleep예제

- 구구단을 출력하는 예제입니다. 실행 결과는 처음에 sleep타임이 찍히게 되고 가장 작은 시간이 찍힌 단부터 출력이 되게 됩니다.
- run()메소드에 sleep()부분

public class GooGoo implements Runnable {


  private int dan;


  public GooGoo(int dan) {

    this.dan = dan;

  }


  public void run() {

    long sleepTime = (long) (Math.random() * 500);

    System.out.println(dan + "단이 " + sleepTime + "만큼 sleep...");


    try {

      Thread.sleep(sleepTime);

    } catch (Exception e) {

    }


    for (int i = 1; i <= 9; i++)

      System.out.println(dan + "단:" + dan + " * " + i + "=" + dan * i);

  }


}


- 핸들링 클래스

public class GooGooTestDrive {

  public static void main(String[] args) {

    Thread t2 = new Thread(new GooGoo(2));

    Thread t3 = new Thread(new GooGoo(3));

    Thread t4 = new Thread(new GooGoo(4));

    Thread t5 = new Thread(new GooGoo(5));

    Thread t6 = new Thread(new GooGoo(6));

    Thread t7 = new Thread(new GooGoo(7));

    Thread t8 = new Thread(new GooGoo(8));

    Thread t9 = new Thread(new GooGoo(9));


    t2.start();

    t3.start();

    t4.start();

    t5.start();

    t6.start();

    t7.start();

    t8.start();

    t9.start();


    System.out.println("main() 종료...");

  }

}



2. yield예제

- 8단일 때 yield() 합니다.
- 맥에서 돌렸을 때는 보고싶은 결과가 잘 나오지 않는 군요;; 비슷하게는 나오는 것 같습니다.
- 일단 결과는 대략 8단 쓰레드가 실행이 되는순간 그보다 높은 우선순위의 단이 먼저 나와야 합니다.

public class GooGooYield implements Runnable {


  private int dan;


  public GooGooYield(int dan) {

    this.dan = dan;

  }


  public void run() {

    if (dan == 8) {

      System.out.println("-8단 yield!!");

      Thread.yield();

    }


    for (int i = 1; i <= 9; i++)

      System.out.println(dan + "단:" + dan + " * " + i + "=" + dan * i);

  }


}


- 핸들링 클래스

public class GooGooYieldTestDrive {

  public static void main(String[] args) {

    Thread t2 = new Thread(new GooGooYield(2));

    Thread t3 = new Thread(new GooGooYield(3));

    Thread t4 = new Thread(new GooGooYield(4));

    Thread t5 = new Thread(new GooGooYield(5));

    Thread t6 = new Thread(new GooGooYield(6));

    Thread t7 = new Thread(new GooGooYield(7));

    Thread t8 = new Thread(new GooGooYield(8));

    Thread t9 = new Thread(new GooGooYield(9));


    t2.setPriority(4);

    t3.setPriority(4);

    t4.setPriority(4);

    t5.setPriority(4);

    t6.setPriority(4);

    t7.setPriority(6);

    t8.setPriority(7);

    t9.setPriority(7);


    t2.start();

    t3.start();

    t4.start();

    t5.start();

    t6.start();

    t7.start();

    t8.start();

    t9.start();


    Thread tt = new Thread(new GooGooYield(10));

    tt.setPriority(9);

    tt.start();


    System.out.println("main() 종료...");

  }

}



3. join예제

- 이 예제는 결과가 잘 나오네요. 실행결과는 "main()종료..." 메시지가 항상 5단 이후에 나와야 합니다. 
main()쓰레드는 priority가 5입니다. 의심이 가시면 한번 찍어보시죠; main()메소드 내에서 Thread.currentThread().getPriority() 의 값을 찍어보세요!!

public class GooGooJoin implements Runnable {


  private int dan;


  public GooGooJoin(int dan) {

    this.dan = dan;

  }


  public void run() {


    try {

      Thread.sleep(500);

    } catch (InterruptedException ie) {

    }


    for (int i = 1; i <= 9; i++)

      System.out.println(dan + "단:" + dan + " * " + i + "=" + dan * i);

  }


}


- 핸들링 클래스

public class GooGooJoinTestDrive {

  public static void main(String[] args) {

    Thread t2 = new Thread(new GooGooJoin(2));

    Thread t3 = new Thread(new GooGooJoin(3));

    Thread t4 = new Thread(new GooGooJoin(4));

    Thread t5 = new Thread(new GooGooJoin(5));

    Thread t6 = new Thread(new GooGooJoin(6));

    Thread t7 = new Thread(new GooGooJoin(7));

    Thread t8 = new Thread(new GooGooJoin(8));

    Thread t9 = new Thread(new GooGooJoin(9));


    t2.setPriority(4);

    t3.setPriority(4);

    t4.setPriority(4);

    t5.setPriority(4);

    t6.setPriority(4);

    t7.setPriority(6);

    t8.setPriority(5);

    t9.setPriority(5);


    t2.start();

    t3.start();

    t4.start();

    t5.start();

    t6.start();

    t7.start();

    t8.start();

    t9.start();


    try {

      t5.join();

    } catch (Exception e) {

    }

    // main쓰레드는 priority가 5이지만 t5쓰레드가 끝나야 종료가 된다. 

    System.out.println("main() 종료...");

  }

}


다음으로 또!! 이어 집니다.
Posted by 개발자 용이~