자바를 이용해서 프로젝트를 진행하면 사용하는 클래스에 대해서는 거의 모르는 게 없게 된다.

하지만 프로젝트를 할 때에는 잘하다가도 손 놓으면 잊어버리게 되는 것 중 하나가 바로 쓰레드를 사용하는 것이다.

그래서 오늘은 쓰레드를 사용하는 방법에 대해 정리해 보자.

먼저 쓰레드를 사용하기 위해서는 Runnable 인터페이스와 Thread 클래스가 필요하다.

Thread 클래스는 새로운 작업을 맡게되는 일꾼이라고 생각하면 되고, Runnable 인터페이스를 구현하는 객체가 실질적인 일이 되겠다.
또 Runnable 인터페이스를 구현하는 클래스는 run()함수에 쓰레드에서 작업할 일을 넣으면 된다.

그렇다면 간단한 예제를 살펴보도록 하자.

public class Job implements Runnable  {
   public void run() {
      System.out.println("new job");
   }
}

class Main {
   public static void main(String[] args) {
      Runnable job = new Job();
      Thread thread = new Thread(job);
      thread.start();
   }
}


정말 간단한 예제이다. Job이라는 클래스는 Runnable 인터페이스를 구현하는데 run()함수에서는 "new job"이라는 문자열을 한번 출력하게 되어 있다.
Main클래스를 보면 Runnable을 구현한 Job객체를 만들고 이를 Thread 객체의 인자로 넣어 주었다.
그리고 그 쓰레드를 시작하기 위해 thread.start()를 해주었다.
여기서 주의할 것은 Thread객체의 start()메소드를 호출하지 않으면 쓰레드가 절대로 수행되지 않는다는 것이다.
Posted by Code-Moon

댓글을 달아 주세요

  1. BlogIcon pooha302 2010.03.11 17:18 Address Modify/Delete Reply

    제목에 오타. 수정하면 이 댓글은 지워주삼 ㅋ

안드로이드 역사이다.
한번 둘러보는 것도 괜찮을 듯.

사용자 삽입 이미지
출처 : http://itviewpoint.com/153275
Posted by Code-Moon

댓글을 달아 주세요

  1. BlogIcon repair iphone 2011.06.15 22:11 Address Modify/Delete Reply

    좋은 글 잘 보고 갑니다. 다음에도 또 좋은 글 기대 할께요. 퍼가도 되죠?

자바에서는 직렬화를 통해서 객체를 손쉽게 저장하고 읽어올 수 있다.
하지만 이를 사용할 때에는 한가지 유의해야 할 점이 있다.

한 상황을 생각해보자.

자동차를 관리하는 프로그램을 만들었다고 하자.
이 프로그램은 자동차 객체를 생성하여 직렬화 한 다음에 이를 파일에 저장해서 자동차 목록을 관리한다.
사용자는 아무 불편없이 프로그램을 잘 사용하고 있었다. 그런데 어느날 유명한 자동차 회사에서 자동차에 하늘을 날 수 있는 기능을 추가했다. 그래서 내가 만든 자동차 관리 프로그램에서 자동차가 하늘을 날 수 있는지 없는지를 나타내는 속성이 필요해졌다.
그래서 한치의 망설임도 없이 자동차 클래스에 canFly라는 boolean형의 변수를 추가했다. 그리고 나서 고객에게 업데이트 된 프로그램을 재배포했다.
그런데 배포를 완료한 이후로 고객들에게 전화가 왔다.
프로그램이 제대로 동작하지 않는다는 것이었다.
어떻게 동작하지 않는지 물어보니 새로운 자동차를 등록하는 것은 잘 되는데, 기존 자동차의 목록을 가져오는 것이 안된다고 하는 것이었다.

도대체 문제가 무엇일까? 왜 잘되던 프로그램이 갑자기 제대로 동작하지 않는 것일까?
바로 직렬화 때문이다.
직렬화를 하게 되면 쉽게 객체를 저장할 수 있다는 장점이 있지만 반면에 그 클래스가 바뀔 경우 예외가 발생하는 단점이 있다.

왜냐하면 객체를 직렬화하게 되면 그 객체가 속한 클래스의 버전 ID가 생기게 되는데 이 버전 ID는 클래스의 구조에 따라 다른 값을 가지게 된다.
이 ID를 serialVersionUID라고 하고 static final long으로 할 것을 권하고 있다.
이클립스에서는 Serializable을 구현하게 되면 그 클래스 명에 노란색 밑줄이 생기게 되는데 마우스를 가져가보면 serialVersionUID를 생성하도록 되어있다.

아래의 그림은 Car클래스에 Serializable을 구현했는데 serialVersionUID를 만들지 않았을 경우 발생하는 노란 밑줄을 보여주고 있다.
사용자 삽입 이미지

위와 같은 상황에서 Car에 마우스를 가져가면 아래와 같은 그림이 나온다.
사용자 삽입 이미지
위의 그림에서 2번째 Add generated serial version ID를 클릭하게 되면 자동으로 클래스 구조에 맞게 변수 값을 할당하게 된다.

만약 Serializable을 구현한 클래스에서 serialVersionUID를 생성하지 않는다면 내부적으로 serialVersionUID를 가지고 있게 된다. 그래서 JVM에서 역직렬화를 할 때에 역직렬화하는 객체의 serialVersionUID와 이를 담을 객체의 serialVersionUID를 비교하게 되는데 이 둘의 값이 다르다면 예외를 발생시키게 된다.

그래서 이러한 문제를 해결하기 위해 serialVersionUID를 클래스 안에서 명시하여 특정 값으로 고정시키면 클래스가 바뀌더라도 예외를 발생시키지 않는 것이다. 하지만 이렇게 serialVersionUID의 값을 명시하게 되면 예외가 발생하지는 않지만 프로그래머가 클래스를 변경할 때에 문제가 생기지 않도록 세심한 주으를 기울여야 한다. 이 부분은 철저히 프로그래머가 책임을 져야 하는 부분이다.
Posted by Code-Moon

댓글을 달아 주세요

자바에서는 객체를 손쉽게 저장하거나 전송할 수 있는데, 이를 가능하게 하는 기술이 바로 직렬화입니다.

원래 객체는 힙 안에 존재하게 되는데 직렬화를 하게 되면 그 모양이 약간 변형되게 됩니다. 이렇게 하게 되면 자바에서 저장 및 전송에 아주 편리하고 유리하다는 장점이 있지만, 동시에 다른 프로그램에서는 이를 분석하기 힘들다는 단점이 있습니다. (물론 직렬화를 통해 어떤식으로 변형이 되는지 그 원리만 잘 알고 있다면 다른 프로그램에서도 얼마든지 읽고 쓰는 것이 가능할 것입니다.)


1.Serializable
직렬화를 하기 위해서는 저장하거나 전송할 객체에 Serializable이라는 인터페이스를 구현해 주어야 합니다. 이 Serializable 인터페이스는 구현할 메소드가 하나도 없기 때문에 그냥 implements Serializable만 써주면 됩니다.

아래의 코드가 Serializable을 구현한 Car클래스입니다.
public class Car implements Serializable {
   /* Car 클래스의 내용 */
}


위와 같이 Serializable을 구현해 줘야 그 객체를 직렬화해서 저장하거나 전송이 가능하지, 구현하지 않을 경우에는 런타임 에러가 발생할 것입니다.

객체가 직렬화되면 그 객체 내부에 있는 모든 객체들도 직렬화되게 됩니다. 그런데 Car객체 내부에 있는 다른 객체가 있다 하더라도 전송이나 저장하기 싫은 변수도 있을 수 있습니다. 이럴 경우에는 transient라는 키워드를 사용하면 됩니다.
예를 들어 Car 클래스에 비밀번호를 담고 있는 password 변수가 있어서 이는 전송하고 싶지 않다고 하겠습니다. 그러면 아래와 같이 코드를 작성할 수 있을 것입니다.

public class Car implements Serializable {
   transient private String password;
   /* Car 클래스의 내용 */
}
위와 같이 코드를 작성하면 직렬화를 하더라도 password의 내용은 직렬화되지 않게 됩니다. 이 transient 키워드는 primitive 변수에도 사용이 가능하고, non-primitive변수에도 사용이 가능합니다.


2.스트림(Stream)
자바에서는 여러종류의 스트림(Stream) 클래스가 있는데 이 스트림 클래스를 이용해서 직렬화된 객체를 파일로 저장하거나 전송하는 등 여러가지 작업을 할 수 있는 것입니다.

다음의 코드는 car객체를 data.dat라는 파일에 넣기 위한 코드입니다.
Car car = new Car();
try {
   FileOutputStream fos = new FileOutputStream("data.dat");
   ObjectOutputStream oos = new ObjectOutputStream(fos);
   oos.writeObject(car);
   oos.close();
} catch(Exception e) {
}

코드를 잘 보면 먼저 FileOutputStream 객체를 생성합니다. 이는 파일에 출력하기 위한 스트림입니다.
그리고 ObjectOutputStream 객체인 oos를 생성하는데 이는 객체를 연결하는 스트림이라 생각하면 되겠습니다.
oos가 객체를 저장할 수 있게 하는데 혼자서는 파일이나 소켓에 넘기는 것이 불가능합니다. 그래서 oos를 생성할 때 fos를 인자로 넣음으로써 객체를 파일에 저장할 수 있도록 하는 것입니다.
그러고나면 oos.writeObject(car)를 하게 되는데 이렇게 하면 실제로 파일에 car객체를 저장하는 것입니다. 저장이 끝나면 oos를 닫아줘야 합니다. oos를 닫아주면 그 아래에 있는 fos는 자동으로 닫히기 때문에 신경쓰지 않아도 됩니다.

이를 그림으로 살펴보면 아래와 같습니다.
사용자 삽입 이미지
위 그림에서 보면 car객체를 data.dat라는 파일에 넣기 위해서 먼저 ObjectOutputStream을 거치고 그 다음에 FileOutputStream을 거쳐서 data.dat파일에 들어간다는 것을 알 수 있습니다.

아래의 코드는 data.dat라는 파일에서 car객체에 값을 읽어보는 코드입니다.
Car newCar = new Car();
try {
   FileInputStream fis = new FileInputStream("data.dat");
   ObjectInputStream oos = new ObjectInputStream(fis);
   ois.readObject(car);
   ois.close();
} catch(Exception e) {
}

코드는 크게 바뀐 부분이 없습니다. Ouput대신에 모두 Input으로 바뀌었고, 객체를 쓸 때에는 writeObject()라는 메소드가 사용되었는데 객체에서 읽어올 때에는 readObject()라는 메소드가 사용됩니다.

이에 대한 그림도 아래에 나와 있습니다.
사용자 삽입 이미지
위의 그림을 잘 보면 data.dat라는 파일에서 newCar라는 객체에 불러오는데, 먼저 FileInputStream에서 ObjectInputStream을 거쳐서 newCar에 불러 온다는 것을 알 수 있습니다.


Posted by Code-Moon

댓글을 달아 주세요

  1. BlogIcon 하늘빠 2010.08.17 16:42 Address Modify/Delete Reply

    좋은 글 잘 보았습니다.
    해설이 넘 명쾌하네요.

    • BlogIcon Code-Moon 2010.08.18 19:37 신고 Address Modify/Delete

      오~ 명쾌하다는 칭찬을 해주시다니^^
      감사합니다.
      글 솜씨가 없어서 다른 분들이 이해를 못하실까봐 내심 걱정했었는데, ^^

  2. 창민 2010.09.17 14:06 Address Modify/Delete Reply

    너무 잘쓰셨네요. 이토록 깔끔한 글이라니.. ㅎㅎ
    블로그에 담아갑니다.

  3. 불꽃란 2013.05.27 14:10 Address Modify/Delete Reply

    좋은 설명 감사합니다
    퍼갈게요 ^^

스택과 힙

프로그래밍/JAVA 2010.01.06 14:43 |

1. 스택(Stack)
스택은 지역변수(메소드 내에 존재하는 변수, 메소드의 인자)가 존재하는 공간이다.
메소드를 호출하면 호출 스택(call stack)의 맨 위에 stack frame(코드, 지역 변수 값)이 쌓이게 된다. 함수호출이 끝나야만 호출 스택에서 해당 스택 프레임을 pop하게 된다.


2. 힙(Heap)
힙은 객체가 존재하는 공간이고, 당연히 인스턴스 변수(클래스 내에 선언되어 있는 변수)도 이 안에 존재한다. 여기에서 힙은 일반적인 힙과는 약간 다르다. 왜냐하면 garbage collection을 하는 힙이기 때문이다.


*만약 메소드 내에서 객체를 생성하면 이는 스택에 들어갈까? 아니면 힙에 들어갈까?
객체가 생성되면 무조건 힙에 생성된다. 그리고 primitive 타입의 변수(int, float, boolean ...)가 아닌 변수에는 객체 자체의 값이 들어가는 것이 아니라 객체에 대한 reference가 들어가게 된다.

즉, 아래 그림과 같은 경우 현재 walk()메소드가 수행되고 있고, walk()메소드에서는 car라는 객체를 사용하게 되는데 이때 car객체는 힙에 생성되어 있고, walk()에서 사용하는 car변수는 힙에 있는 객체의 레퍼런스를 이용하는 것이다.
스택 &

Posted by Code-Moon

댓글을 달아 주세요

cout의 setf함수.
출력의 형식을 정할 수 있다.  인자로는 여러개의 플래그가 |(or)로 같이 들어갈 수 있다.

소수점 2자리까지 표현하기 위해서는 다음과 같이 코딩한다.

cout.setf(ios::fixed);
cout.precision(2); 

'프로그래밍 > C++' 카테고리의 다른 글

cout에서 소수점 둘째자리 표현하기  (0) 2010.01.04
advance()함수  (0) 2010.01.04
Posted by Code-Moon

댓글을 달아 주세요

void std::advance(_InIt & _Where, _Diff _Off)
iterator를 원하는 위치로 옮길 수 있는 함수이다. 컨테이너의 iterator를 i번째 위치로 옮기고 싶을 때 사용할 수 있다.
첫번째 인자로는 iterator가 들어가고 두번째 인자로는 오프셋이 들어간다.
결과로는 현재 iterator를 오프셋만큼 뒤로 이동시킨다.
 vector<int> a;
 vector<int>::iterator it;
 a.push_back(13);
 a.push_back(2);
 a.push_back(3);
 it = a.begin();
 int n;
 n = 0;
 advance(it, n);
 cout << *it << endl; 

이러한 코드를 작성하게 되면 13이라는 결과가 출력된다. 즉, iterator가 전혀 이동하지 않았다.
만약 n에 2를 넣어준다면 iterator가 2만큼 이동하여 3이 들어가는 위치로 이동하게 될 것이다.
벡터에서는 이러한 advance()함수를 사용할 일이 별로 없을지 몰라도 list같은 컨테이너를 사용할 때에는 활용도가 아주 높을 것 같다.

'프로그래밍 > C++' 카테고리의 다른 글

cout에서 소수점 둘째자리 표현하기  (0) 2010.01.04
advance()함수  (0) 2010.01.04
Posted by Code-Moon

댓글을 달아 주세요

swap함수는 대개 다음과 같이 만든다.

void swap(int *a, int *b) {
  int temp = *a;
  *a = *b;
  *b = temp;
} 
이렇게 하는 이유는 포인터가 아닌 그냥 변수를 넣어줄 경우에 원하는 결과가 나오지 않기 때문이다.
이유인 즉, 함수를 호출할 때에 새로운 스택이 잡히게 되고 함수의 인자도 그 스택 안에 새로 할당된 값이다.
그런데 이때 변수 값을 넘겨주게 되면 함수에 있는 인자에 그 값이 들어가게 되는 것이다.

예를 들어
void swap(int a, int b) {
  int temp = a;
  a = b;
  b = temp;
} 
라는 함수를 만들고 main에서 i=2, j=3을 할당한 뒤 swap(i, j);를 호출하면 새로운 스택이 만들어 지는데, 이 스택의 a라는 변수에 i의 값인 2가 들어가고, b라는 변수에 j의 값인 3이 들어간다.

그리고 a와 b는 서로 값을 바꾸게 되는데, 함수를 빠져나가는 순간 스택 또한 사라진다.
그래서 스택에 있던 a, b라는 변수도 사라지게 된다. 결국 swap(i, j)를 하고 나도 i와 j 값은 전혀 변화가 없는 것이다.
하지만 포인터를 이용한 swap함수는 호출을 할 때 swap(&i, &j)와 같이 변수의 주소를 넘긴다.

그렇게 되면 함수가 호출됐을 때 스택이 만들어지고 그 스택의 두개의 변수인 a, b는 각각 i와 j에 대한 주소를 가지고 있는다. *a, *b는 각각 i와 j의 주소에 있는 값을 의미하므로  이 부분을 변화시키면 당연히 i와 j의 값이 바뀌게 된다.


그렇다면 이중 포인터의 경우를 살펴보자.
어떠한 특별한 경우에 함수의 인자로 이중 포인터를 넘겨줘야 하는 상황이 발생한다.

Shape이라는 클래스가 있다고 하자.
Shape *i, *j; 가 메인에 있다.
이때 i에는 아무런 객체가 들어있지 않고, j에는 특정한 객체가 들어있다고 하자.
i에 j의 객체를 복사한 새로운 객체를 만들어주는 void func(Shape* a, Shape *b)라는 함수를 만들었다고 하자.
이 함수는 아마도 다음과 같이 구성될 것이다.
void func(Shape* a, Shape* b) {
  a = new Shape(*b);
} 


호출 -> func(i, j);

그런데 이렇게 하면 불행히도 우리가 원하는 결과를 얻을 수 없다.
다시 한번 곰곰히 생각해 보면, new Shape(*b)를 하게 되면 Shape 객체에 대한 주소값이 반환된다.
이 주소값을 담을 수 있는 곳은 당연히 Shape*가 된다.

그런데 우리는 이러한 작업이 수행되는 곳이 함수라는 것을 기억해야 한다.
함수란 무엇인가?? 스택이다!! 스택은 들어가는 순간 매개변수가 스택의 변수가 되고, 빠져 나가는 순간 스택이 사라지면서 매개변수 또한 사라진다.

그렇다면 함수가 호출될 때 넘겨지는 값이 무엇인가?
a에는 i를 넘겨준다. i는 Shape* 형이고 아무 곳도 가리키지 않는 무의미한 포인터이다.(자칫 위험할 수도 있는).
그러면 a는 i가 가리키고 있었던 어떤 주소가 될 것이다.(어느 곳인지는 아무도 알 수 없다.)
그리고 b에는 j를 넘겨주는데, j는 Shape객체의 주소이므로 b에는 그 객체에 대한 주소가 넘어간다.
그래서 new Shape(*b)하면 b와 똑같은 객체를 하나 더 생성할 것이다. 그리고 그 주소를 a에게 넘겨준다. 그런데 a는 아무도 모르는 어떤 주소이고 이곳에 새로운 객체를 넣는다는 것은 아주 위험한 행동이다. (결과는 아무도 예측할 수 없다.)
그래서 함수를 새롭게 고쳐야 한다.
void func(Shape** a, Shape* b) {
  *a = new Shape(*b);
} 

호출-> func(&i, j);

a를 보면 Shape** 형이고, 이것이 의미하는 바는 Shape자료형에 대한 주소값을 가지고 있는 변수의 주소값을 가지는 변수라는 뜻이다-_-. 쉽지는 않지만 좀 더 간단하게 말하자면 포인터에 대한 포인터이다-_-;;
앞의 swap함수에서 함수에는 포인터를 사용하고, 함수 호출시에는 변수의 주소값을 넘겨준 것을 생각하면 조금 이해할 수도 있겠다.
*a는 Shape객체에 대한 포인터이므로 new Shape(*b)를 받을 수 있다.
그리고 함수를 호출하는 부분에서 &i를 하게 되면 i라는 변수의 주소를 주게 된다.
즉, 앞에서와의 차이점을 얘기하자면 앞에서는 i가 가리키는 곳의 주소를 전달했는데에 반해 여기에서는 i 자신의 주소를 전달한 것이다.
이 부분이 엄청나게 중요한 부분이다. 포인터라 하더라도 일종의 변수이다.
즉, 자신만의 공간을 가진다는 말이다. int * a라고 하면 a는 int형의 주소를 가리키는 변수이지만 &a는 그러한 자신의 주소가 된다는 것이다.

어쨌든 이렇게 함으로써 할당도 되지 않는 모르는 주소에 새로운 객체를 넣는 행위를 하지 않게 된다. 이해하기 쉽지 않겠지만, 이 부분은 함수를 쓰지 않았을 때 a = new Shape(*b);와 같다.(지극히 정상적이다.)
반면 앞의 이중 포인터를 쓰지 않은 경우는 *a = new Shape(*b);와 같다.(이 경우에는 a가 가리키는 곳의 값에다가 새로운 객체를 넣는 이상한 행동을 하게 된다. 결국 비정상 종료를 하게 될 것이다.)

'프로그래밍 > C' 카테고리의 다른 글

포인터(Pointer)와 함수  (0) 2010.01.04
memset() 함수  (0) 2010.01.04
Posted by Code-Moon

댓글을 달아 주세요

memset() 함수

프로그래밍/C 2010.01.04 01:03 |

void  *memset(  void  *dest,  int  c,  size_t  count  );  

대략적인 사용법은 아래와 같다.

 

char a[10]이 있고, c의 각 원소를 0으로 초기화 해주려면

 

memset(a, 0, 10); 
// 또는  memset(a, 0, sizeof(a)); 

 

여기에 두번째 원소에 어떠한 정수를 넣어주냐에 따라 초기화되는 값이 바뀌는 것이다.

그런데 여기서 주의할 점은 무조건 1바이트씩 초기화한다는 것이다.

이것이 무슨 말이냐면,,

char a[10]의 경우에는 아무런 문제도 찾을 수 없다.

하지만 int a[10]의 경우에는 말이 다르다.

memset(a, 0, 10);  하게 되면 문제를 못 느낄지도 모른다.

 

하지만, memset(a, 1, 10);을 하는 순간 경악을 하게 될 지도 모른다.

우리는 a의 각 원소를 1로 초기화하기를 바랬다.

하지만 결과는 각 원소가 16843009로 초기화 되었음을 알 수 있다.

 

왜 그럴까??

 

앞에서 이미 말했듯이 memset은 초기화를 할 때 1바이트씩한다고 했다.

그런데 int의 경우에는 32비트 머신에서 4바이트를 차지하게 된다.

그러면 여기서 하나의 int를 16진수로 뜯어서 memset하게 된 결과를 보면,

16진수 01010101 이 되고,  이는 10진수로 계산하면 16843009가 되는 것이다.

'프로그래밍 > C' 카테고리의 다른 글

포인터(Pointer)와 함수  (0) 2010.01.04
memset() 함수  (0) 2010.01.04
Posted by Code-Moon
TAG memset

댓글을 달아 주세요

  

윈도우 모바일

(WM)

안드로이드

(Android)

아이폰

(iPhone)

개발 OS

Windows Only

Windows,
MacOSX,
Linux

MacOSX Only (include Hackintosh)

개발툴

Visual Studio 2005, 2008

Eclipse 3.3, 3.4

Xcode 3.1,
Interface Builder

프로그래밍 언어

Visual C++, C#,
VB.NET

Java

Objective-C

프레임워크
 or 런타임

MFC,
.NET Compact Framework

Android Application Framework,

Dalvik

Cocoa Touch

바이너리

.exe
(CLR)

.dex
(Dalvik executable)

.app

패키징

.cab

.apk

.zip

표에서 볼 수 있듯이 윈도우 모바일은 윈도우에서 Visual Studio를 사용해서 개발해야 한다.
즉, 윈도우를 OS로 채택하지 않은 개발자는 개발을 할 수 없고 Visual Studio가 없는 개발자 또한 개발에 참여할 수 없다. 우리나라의 입장에서 본다면 이러한 윈도우 모바일의 개발 환경은 그렇게 다가가기 어려운 개발환경은 아니라고 생각한다.


아이폰은 MacOSX에서 작업을 해야 하는데 윈도우가 판을 치는 우리나라에서는 이런 개발 환경은 정말 접하기 힘든 환경이다.


이에 비해 안드로이드의 경우에는 OS가 윈도우, MacOSX, 리눅스 중 하나라면 일단은 개발할 수 있는 환경이 된다. 그리고 개발 툴 또한 무료로 손쉽게 얻을 수 있는 Eclipse를 사용하기 때문에 개발 환경 구축에 아주 용이한 장점을 가지고 있다.


이렇게 개발 환경 상의 막강한 장점을 가지고 있는 안드로이드,

안드로이드는 open되어 있다. 언제든지 개발하고 싶다면 쉽게 뛰어들 수 있다!!!



당신도 이 대열에 참여해보지 않겠는가?!

Posted by Code-Moon

댓글을 달아 주세요