예전에 한 친구와 프로젝트를 같이 진행한 적이 있었다. 그때 svn을 사용해서 개발하려고 subclipse를 설치해서 사용했다.
그리고 열심히 프로젝트를 진행하려고 하는데 크나큰 문제점이 닥쳤다.

revision1 인 파일을 나와 내 친구가 update를 통해 받은 상태에서 각자의 작업을 진행하고 있었고, 내 친구가 먼저 수정을 끝내고 commit을 했다. 나도 얼마있지 않아서 작업을 끝내고 commit을 하려고하는데 error가 발생했다.
이유는 revision 때문이었다.

친구가 commit을 한 순간 그 파일은 revision2가 HEAD로 된다. 그런데 나는 revision1을 가지고 수정을 한 상태이니, 당연히 commit을 못하도록 막는 것이다.

그런데 오늘에서야 그 해결책을 찾아냈다.


방법은 간단하다.

먼저 앞에서 말한 상황과 같은 상황을 만들어보자.
아래의 Test.java파일은 revision182이다.




여기에서 수정을 약간 가해보자.


그리고 수정된 파일을 commit하면 이제 revision183이 된다. 이 상태가 내 친구가 commit을 한 상태이다.



그런데 나는 아직 update가 되지 않은 상태이므로 그 이전 상태로 돌아가야 한다.

Test.java에서 오른 클릭하여 Team - Switch to another Branch/Tag/Revision을 눌러서 revision182로 돌아가도록 하자.




다시 revision182로 돌아가서 여기에서 친구가 수정한 부분과 다르게 수정을 해보자.



이렇게 수정을 한 뒤에 team - commit을 해보면 에러가 발생한다는 것을 알 수 있다.



이제 본격적으로 우리가 알아봐야 할 부분이다.
Test.java에서 오른클릭하여 team - update to HEAD를 눌러보자. 그러면 다음과 같은 화면을 볼 수 있을 것이다.
(이때 Test.java의 아이콘에 별표가 아닌 사각형이 생김에 유의하자.)



subclipse에서 친절하게도 HEAD버전와 충돌이 일어나는 부분을 다 표시해 주었다.
뿐만 아니라, revision이 183으로 올라갔다!!!

<<<<<<< .mine에서부터 ========== 까지가 내가 수정한 부분이고,
=========에서부터 >>>>>> .r183까지가 revision183버전의 상태이다.
(즉, ========를 경계로 어떻게 바꿀지 확인을 하면되는 것이다.)


여기에서는 a의 값은 내가 수정한대로 바꾸고, 주석 부분은 친구가 수정한 부분으로 두고 싶다.
그러면 직접 손으로 그 부분을 수정하면 된다.



이렇게 한 뒤에 Test.java에서 오른 클릭 - Team - Mark Resolved를 누르면 아래와 같은 화면이 뜬다.
여기에서 첫번째를 선택하자.(파일의 충돌을 해결했다는 뜻)


그리고 나서  Package Ex탭을 보면 Test.java의 아이콘에 별표가 뜰 것이다.



이렇게 한 뒤에 Test.java에서 오른 클릭 - Team - Commit을 해보자.
아무런 에러없이 잘 올라갔고 버전도 revision184로 올라간 것을 볼 수 있을 것이다.!!

신고
Posted by Code-Moon

댓글을 달아 주세요

멀티 쓰레드(multi-thread) 기반의 프로그래밍을 하게 되면 중요한 것이 바로 원자성의 보장이다.

그렇다면 원자성이 무엇인가?

원자성이란 여러개의 쓰레드가 있을 때 특정 시점에 어떤 메소드를 두개 이상의 쓰레드가 동시에 호출 못한다는 것이다.

간단한 예를 들어보자.
class Job implements Runnable {
   public void run() {
      while(true) {
         go();
         stop();
      }
   }
   public void go() {
      /* 매우 중요한 작업이다 */
   }
   public void stop() {
      /* 그냥 일반적인 작업이다. */
   }
}

위의 Job 클래스를 보면 run()메소드에서 go()와 stop()메소드를 무한 호출하고 있다.
그런데 여기에서 go()메소드가 아주 중요한 메소드라고 하자.

메인 클래스에서 Job객체를 이용해서 여러개의 쓰레드를 만들었고, 각 쓰레드를 수행시켰다고 하면 각 쓰레드는 start()메소드를 언제 호출했든지에 상관없이 멀티 쓰레드 정책에 따라 수행될 것이다.

그런데 잘 생각해보면 1번 쓰레드에서 go()메소드를 수행하는 중에 2번 쓰레드가 go()메소드를 수행할 수도 있다.
그렇게 되면 우리가 예상치 못한 문제가 발생할 수 있는 부분이라고 가정하자. (그런 상황은 여러가지 상황이 있을 수 있다. 특히, 서로 다른 쓰레드의 go()메소드에서 같은 변수의 값을 변화시키는 부분일 경우를 생각해 보면 된다.)

어쨌든 go()메소드가 그렇게 중요한 부분이라서 절대로 함수 호출이 끝나기 전에 또 다시 go()메소드가 호출되는 일이 없어야 한다면, 이런 경우가 바로 원자적으로 수행되어야 하는 부분이다.

그래서 이러한 부분을 원자적으로 (atomic) 수행되는 것을 보장해 주는 것이 바로 synchronized 키워드이다. 그래서 go()메소드의 선언 부분을 다음과 같이 바꿔주면 된다.

public synchronized void go() 



신고
Posted by Code-Moon

댓글을 달아 주세요

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

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

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

먼저 쓰레드를 사용하기 위해서는 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

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

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

한 상황을 생각해보자.

자동차를 관리하는 프로그램을 만들었다고 하자.
이 프로그램은 자동차 객체를 생성하여 직렬화 한 다음에 이를 파일에 저장해서 자동차 목록을 관리한다.
사용자는 아무 불편없이 프로그램을 잘 사용하고 있었다. 그런데 어느날 유명한 자동차 회사에서 자동차에 하늘을 날 수 있는 기능을 추가했다. 그래서 내가 만든 자동차 관리 프로그램에서 자동차가 하늘을 날 수 있는지 없는지를 나타내는 속성이 필요해졌다.
그래서 한치의 망설임도 없이 자동차 클래스에 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

댓글을 달아 주세요

티스토리 툴바