else if (개발)2009/12/05 16:47
요새 회사에서 하는 일은 주로 파이썬으로 데이터 주무르는 노가다.

오늘 오랜만에 자바로 알고리즘 퀴즈를 풀었는데, 몇 줄 되지도 않는 코드가 컴파일 에러가 주루룩난다.

public class RSABreaker {
    // x < y
    public isSharePrime(int x, int y) {   ----> return 타입을 빼먹었다.
        for (int i = 2; i <= x; i++) {
            if (x % i == 0 && y % i == 0)
                return true         ----> 세미콜론을 빼먹었다.
        }
        return false;
    }
   
    public int decrypt(int e, int n, int b) {
        int m = 1;
        for (int i = 2; i < n; i++) {
            if !isSharePrime(i, n)    ----> if 문에 괄호를 빼먹었다.
                m++;
        }
        d = 1                ----> 자료형을 빼먹었다. 세미콜론을 또 안썼다.
        while ((d*e) % m != 1)
            d++             ---> 세미콜론을 자꾸 자꾸 빼먹는다.
        int res = 1;
        for (int i = 0; i < d; i++) {
            res = (res * b) % n;
        }
        return res;
    }
}

Posted by 심보준
else if (개발)2008/11/15 01:09
최근에 C라이브러리와 C라이브러리를 JNI로 래핑한 라이브러리의 성능을 비교하다가 C와 자바의 표준출력 차이에 대해서 알게되었습니다.

아래의 두 코드는 Hello World를 여러번 출력하는 초간단 자바와 C 코드입니다.

//test.c
#include <stdio.h>
int main() {
    int i;
    for (i = 0; i < 1000000; i++){
        printf("Hello World!\n");
    }
    return 0;
}

//Test.java
public class Test {
    public static void main(String[] args) {
        for (int i = 0; i < 1000000; i++) {
            System.out.println("Hello World!");
        }
    }
}

이렇게 간단한 두개의 코드를 컴파일하고 각각 실행시간을 time으로 재는데 단, 출력을 파일로 리다이렉션 합니다. 그 결과 두 프로그램의 실행시간 차이는 무려 다음과 같습니다.
$ time ./test > output1.txt
real    0m0.103s
user    0m0.050s
sys     0m0.050s

$ time java Test > output2.txt
real    0m9.885s
user    0m2.660s
sys     0m7.340s

이렇게 차이가 나는 이유는 출력 버퍼링의 차이 때문입니다.
C에서 표준출력은 터미널과 같은 대화형 장치에는 \n을 기준으로 flush를 하는 line buffering을 하지만, 파일로 리다이렉션 할 때는 full buffering으로 전환됩니다. 하지만, 자바의 System.out.println은 항상 line buffering을 하게 됩니다.
언어의 성능의 문제가 아니라 buffering 방식이 다르기 때문에 애초에 공정한 게임이 아닌 것입니다. 공정한 게임을 위해서는 자바 코드를 다음과 같이 수정해야 합니다.
import java.io.*;
public class Test {
    public static void main(String[] args) throws IOException {
        //표준출력 full buffering
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
        for (int i = 0; i < 1000000; i++) {
            bw.write("Hello World!\n");
        }
        bw.close();
    }
}
$ javac Test.java
$ time java Test > output2.txt
real    0m0.294s
user    0m0.260s
sys     0m0.020s

자바와 C 프로그램의 성능을 비교하는데 표준출력을 파일로 리다이렉션 하면서 이 차이를 무시하면  아주 엉뚱한 판단을 하게 되겠죠? 제가 그랬습니다. T.T
Posted by 심보준
else if (개발)2008/10/16 00:31
자바에서 부모 클래스의 private 메소드를 overriding(재정의) 할 수 있을까요? 결론 부터 말하자면 안됩니다. 더 정확히 얘기하자면 그런게 없습니다.
JLS(Java Language Specification)에 보면 overriding에 대해서 다음과 같이 정의되어 있습니다.

8.4.8.1 Overriding (by Instance Methods)
An instance method m1 declared in a class C overrides another instance method, m2, declared in class A iff all of the following are true:
1. C is a subclass of A.
2. The signature of m1 is a subsignature (§8.4.2) of the signature of m2.
3. Either
 ◆ m2 is public, protected or declared with default access in the same package as C, or
 ◆ m1 overrides a method m3, m3 distinct from m1, m3 distinct from m2, such that m3 overrides m2.


노랗게 칠한 부분을 보면 부모 클래스의 메소드인 m2가 public, protected, 또는 default access 일 경우에 overriding 이라고 얘기하고 있습니다.

아래 코드를 한번 보겠습니다.

class Parent {
 public void method1(){
  System.out.println("parent method1");
 }
 public void method2(){
   method1();
 }
}

class Child extends Parent {
 public void method1(){
  System.out.println("child method1");
 }
}

public class Test {
 public static void main(String[] args) {
  Child c = new Child();
  c.method2();
 }
}


overriding의 정의에 맞게 Parent 클래스의 public method1을 Child 클래스에서 정의했습니다.
c 객체의 method2를 호출하는데 Child 클래스에 없으므로 Parent 클래스의 method2를 실행합니다. method2에서 method1을 호출하는데, 현재 이 객체는 Child Type의 객체 이므로, Child 클래스에서 재정의한 mehod1이 호출됩니다. 따라서,

> child method1

과 같이 출력이 됩니다. 그럼 코드를 좀 바꿔 보겠습니다.

class Parent {
 private void method1(){
  System.out.println("parent method1");
 }
 public void method2(){
   method1();
 }
}

class Child extends Parent {
 public void method1(){
  System.out.println("child method1");
 }
}

public class Test {
 public static void main(String[] args) {
  Child c = new Child();
  c.method2();
 }
}


부모 클래스의 method1이 private 인 경우입니다. 혹시 overriding 이 안된다고 하니까 컴파일 에러? 컴파일은 잘 됩니다. 하지만 이 경우 실행 결과는

> parent method1

이렇게 Parent 클래스의 method1이 호출됩니다. Parent 클래스의 method1과 Child 클래스의 method1은 이름만 같을 뿐 overriding 된 것이 아닙니다. overriding 된 것이 아니니, Parent의 method2는 Parent의 method1을 그냥 호출 합니다. Child 클래스에 정의되어 있는 method1은 Child 클래스에서 호출하는 코드가 있다면 사용이 되겠죠. 사실 이치로 따져 봐도 private 메소드이면 자식 클래스에서도 접근 불가이므로 이를 overriding 한다는 것이 맞지 않는 일이죠.

위 내용은 Effective Java를 읽던 중 "상속 가능한 클래스의 생성자에서 overriding 될 수 있는 메소드를 호출하지 마라. final 이나 private 메소드만 호출해라" 이런 말이 있어서 찾아 보고 알게 된 내용입니다.

Posted by 심보준
else if (개발)2008/10/15 01:56

Java Collection Framework의 대부분의 Collection 구현체들은 Thread-Safe하지 않으므로 멀티 스레드 환경이라면 반드시 synchronized block을 적절하게 잡아 주어야 합니다.

그런데, java.util.Collections 클래스의 static 팩토리 메소드인 Collections.synchronizedCollection 메소드를 이용하면 간편하게 Thread-Safe한 Collection 객체를 생성할 수 있습니다.

아래의 코드는 10000개의 스레드에서 1개의 TreeSet에 동시 접근해 Integer 객체의 삽입/삭제를 수행하는 코드로, 심각한 문제를 일으키는 코드입니다.

import java.util.Collection;
import java.util.TreeSet;

public class SynchronizedCollectionEx {
  //TreeSet은 Thread-Safe하지 않습니다.
  static Collection treeSet = new TreeSet();
  public static void main(String[] args){
      Thread[] t = new Thread[10000];
      for (int i = 0; i < 10000; i++){
          t[i] = new Thread(new WriterThread());
          t[i].start();
      }
  }
}

class WriterThread implements Runnable {
  public void run() {
      for (int i = 0; i < 100; i++){
          SynchronizedCollectionEx.treeSet.add(new Integer((int)(Math.random() * 10)));
          SynchronizedCollectionEx.treeSet.remove(new Integer((int)(Math.random() * 10)));
      }
  }
}


 위 코드를 컴파일 - 실행하면 다음과 같은 예외가 줄기차게 발생하고, 급기야 deadlock에 빠지기도 합니다. 단일 프로세서 환경에서 실행했을 때는 그렇게 자주 발생하지는 않더군요. 하지만 듀얼 코어에서 돌리니 거의 항상 발생했습니다.


Exception in thread "Thread-119" java.lang.NullPointerException
        at java.util.TreeMap.fixAfterInsertion(TreeMap.java:2074)
        at java.util.TreeMap.put(TreeMap.java:559)
        at java.util.TreeSet.add(TreeSet.java:238)
        at WriterThread.run(Test.java:34)
        at java.lang.Thread.run(Thread.java:619)
Exception in thread "Thread-732" java.lang.NullPointerException
        at java.util.TreeMap.fixAfterInsertion(TreeMap.java:2074)
        at java.util.TreeMap.put(TreeMap.java:559)
        at java.util.TreeSet.add(TreeSet.java:238)
        at WriterThread.run(Test.java:34)
        at java.lang.Thread.run(Thread.java:619)
...


코드에서 공유 객체를 쓰거나 읽는 부분에 synchronized 블럭을 잘 잡아주어야 해결되는 문제입니다. 그리고, 또 하나의 간편한 해결책이 공유 객체인 treeSet 객체를 Thread-Safe한 객체로 재탄생 시키는 방법입니다. 이때 이용하는 static 메소드가

Collections.synchronizedCollection(Collection c)

입니다. 이 메소드는 인자로 들어온 Collection 객체를 래핑하여 Thread-Safe한 collection 객체를 반환합니다.

import java.util.Collection;
import java.util.Collections;
import java.util.TreeSet;

public class SynchronizedCollectionEx {
 //treeSet은 Thread-Safe 하지 않습니다.
 static Collection treeSet = new TreeSet();
 //synchronizedSet은 Thread-Safe 합니다. 이 객체를 공유 객체로 사용해야 합니다. 
 static Collection synchronizedSet = Collections.synchronizedCollection(treeSet);
 public static void main(String[] args){
     Thread[] t = new Thread[10000];
     for (int i = 0; i < 10000; i++){
         t[i] = new Thread(new WriterThread());
         t[i].start();
     }
 }
}

class WriterThread implements Runnable {
  public void run() {
    for (int i = 0; i < 100; i++){
      SynchronizedCollectionEx.synchronizedSet.add(new Integer((int)(Math.random() * 10)));
      SynchronizedCollectionEx.synchronizedSet.remove(new Integer((int)(Math.random() * 10)));
    }
  }
}


java.util.Collections 클래스에는 synchronizedCollections 뿐만 아니라, SynchronizedSet, SynchronizedMap 등등 많은 Synchronized... 류의 static 팩토리 메소드를 제공합니다. 쓰는 방법은 크게 다르지 않습니다.

이 글은 스프링노트에서 작성되었습니다.

Posted by 심보준