• 1_Class 정보
     
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // Reflection(Introspection)
    // 개념 : Class 이름만으로도 클래스의 정보(필드, 메소드)를 찾거나
    //       객체를 생성할 수 있는 기능.
    
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    
    public class Example1 {
      public static void main(String[] args) throws Exception {
        // 1. Class 얻기
        // a. 객체를 통해서 얻는 방법.
        // Class personClass = new Person().getClass();
    
        // b. Class type으로 얻어내는 방법.
        // Class personClass = Person.class;
    
        // c. 문자열로 얻어내는 방법.
        Class personClass = Class.forName("kr.co.ioacademy.Person"); // 패키지 정보까지 정확히 넣어야된다.
    
        // 2. Class 이름
        System.out.println(personClass.getName());       // kr.co.ioacademy.Person
        System.out.println(personClass.getSimpleName()); // Person
        System.out.println(personClass.getCanonicalName());
    
        int mods = personClass.getModifiers();
    
        // 3. Class 속성
        System.out.println("public : " + Modifier.isPublic(mods));
        System.out.println("final : " + Modifier.isFinal(mods));
        System.out.println("abstract : " + Modifier.isAbstract(mods));
    
        // 4. Method 속성
        Method[] methods = personClass.getMethods();
        for (Method m : methods) {
          System.out.println(m.getName());
          System.out.println(m.getParameterCount());
          Class[] params = m.getParameterTypes();
          for (Class p : params) {
            System.out.println("\t" + p.getName());
          }
        }
      }
    }
    
    final class Person {
      private String name;
      private int age;
    
      public Person() {
        this.name = "";
        this.age = 0;
      }
    
      public Person(String name, int age) {
        this.name = name;
        this.age = age;
      }
    
      @Override
      public String toString() {
        return "Person{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
      }
    }
    

  • 2_객체 read / write
    : public field vs. private field
     
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.lang.reflect.Field;
    
    // 객체의 필드를 읽거나 쓰는 방법.
    public class Example2 {
      public static void main(String[] args) throws Exception {
        Point point = new Point();
    
        Class pointClass = point.getClass();
    
        // public field read / write
        Field xField = pointClass.getField("x");
        Field yField = pointClass.getField("y");
    
        System.out.println(xField.get(point) + ", " + yField.get(point));
    
        xField.setInt(point, 10);
        // xField.set(point, 10);
        yField.set(point, 20);
        System.out.println(xField.get(point) + ", " + yField.get(point));
    
        Field[] fields = pointClass.getDeclaredFields();
        for (Field f : fields)
          System.out.println(f.getName());
    
    
        // private field read / write
        Field zField = pointClass.getDeclaredField("z");
        zField.setAccessible(true);
        zField.set(point, 100);
    
        System.out.println(zField.get(point));
      }
    }
    
    class Point {
      public int x;
      public int y;
    
      private int z;
      public int z() {
        return z;
      }
    }
    


  • 3_객체 동적 생성
     
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.lang.reflect.Constructor;
    
    // Reflection 을 통해서 객체를 생성하는 방법. - 동적 생성
    // Spring - DI(Dependency Injection)
    // iOS
    public class Example3 {
      public static void main(String[] args) throws Exception {
        Class personClass = Person.class;
    
        // 1. 기본 생성자를 통한 객체 생성
        Person person = (Person) personClass.newInstance();
        System.out.println(person);
    
        // 2. 사용자 정의 생성자를 통한 객체 생성
        Class[] paramTypes = {
          String.class, int.class
        };
    
        // Constructor constructor = personClass.getConstructor(paramTypes);
        Constructor constructor = personClass.getDeclaredConstructor(paramTypes);
        System.out.println(constructor);
    
        Object[] cargs = {
            "Tom", 42
        };
    
        constructor.setAccessible(true);
        Person person2 = (Person) constructor.newInstance(cargs);
        System.out.println(person2);
      }
    }
    
    class Person {
      private String name;
      private int age;
    
      public Person() {
        this.name = "";
        this.age = 0;
      }
    
      private Person(String name, int age) {
        this.name = name;
        this.age = age;
      }
    
      @Override
      public String toString() {
        return "Person{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
      }
    }
    

  • 3_Refilection 활용
    - 1_Key-Value Coding

     
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.lang.reflect.Field;
    
    // Reflection 활용 1.
    // Key-Value Coding : setter / getter
    // Objective-C 기본으로 제공하고 있는 기능.
    public class Example4 {
      public static void main(String[] args) {
        Person person = new Person();
    
        String input = "name";
        String value = "Tom";
    
        person.setValue(input, value);
        person.setValue("phone", "010-1111-2222");
        person.setValue("age", 200);
    
        System.out.println(person.getValue("name"));
        System.out.println(person.getValue("phone"));
        System.out.println(person.getValue("age"));
    
    
        // 일반적인 getter / setter 사용
        /*  
        if (input.equals("name"))
          person.setName(value);
        else if (input.equals("phone"))
          person.setPhone(value);
        */
      }
    }
    
    class Person {
      private String name;
      private String phone;
      private int age;
    
      public Object getValue(String key) {
        Class clazz = this.getClass();
        Field field;
    
        try {
          field = clazz.getDeclaredField(key);
          return field.get(this);
        } catch (NoSuchFieldException | IllegalAccessException e) {
          e.printStackTrace();
        }
    
        return null;
      }
    
      // Reflection을 사용하지 않고 key-value coding
      // but, field가 많아진다면??
      /*  
      public void setValue(String key, Object value) {
      switch (key) {
        case "name":
          this.name = (String) value;
          break;
        case "phone":
          this.phone = (String) value;
          break;
        case "age":
          this.age = (int) value;
          break;
       }
     }*/
      
      public void setValue(String key, Object value) {
        Class clazz = this.getClass(); // Person.class
        try {
          Field filed = clazz.getDeclaredField(key);
          filed.set(this, value);
        } catch (NoSuchFieldException | IllegalAccessException e) {
          e.printStackTrace();
        }
      }
    
      @Override
      public String toString() {
        return "Person{" +
            "name='" + name + '\'' +
            ", phone='" + phone + '\'' +
            '}';
      }
    }
    

    - 2_팩토리
     
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import org.hamcrest.Factory;
    
    // Reflection 활용 2.
    // 팩토리
    // 개념 : 객체를 생성하는 객체
    // 장점 : 객체 생성에 관한 코드를 한곳에 모아서 중앙집중적으로 관리하는 것이 가능하다.
    // 단점 : 도형의 종류가 늘어남에 따라 팩토리의 코드는 수정되어야만 한다.
    //        OCP(Open Closed Principle) 를 만족할 수 없다.
    
    abstract class Shape {
    	abstract void print();
    }
    class Rect extends Shape {
    	@Override
    	void print() {
    		System.out.println("Rect");
    	}
    }
    class Circle extends Shape {
    	@Override
    	void print() {
    		System.out.println("Circle");
    	}
    }
    class Triangle extends Shape {
    	@Override
    	void print() {
    		System.out.println("Triangle");
    	}
    }
    
    class ShapeFactory {
      public Shape createShape(String name) {
        Class clazz = null;
        try {
          clazz = Class.forName(name);
          return (Shape) clazz.newInstance();
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
          e.printStackTrace();
        }
    
        return null;
      }
    
    //  public Shape createShape(String name) {
    //    if (name.equals("Rect"))
    //      return new Rect();
    //    else if (name.equals("Circle"))
    //      return new Circle();
    //    else if (name.equals("Triangle"))
    //      return new Triangle();
    //
    //    return null;
    //  }
    }
    
    public class Example5 {
    	public static void main(String args[]) {
    		ShapeFactory factory = new ShapeFactory();
    		Object r = factory.createShape("kr.co.ioacademy.Rect");
    		Object c = factory.createShape("kr.co.ioacademy.Circle");
    		Object t = factory.createShape("kr.co.ioacademy.Triangle");
    		
    		if(r instanceof Rect)
    			((Rect)r).print();
    		if(c instanceof Circle)
    			((Circle)c).print();
    		if(t instanceof Triangle)
    			((Triangle)t).print();
    	}
    }
    

  • 4_Reflection 성능 고찰

    package kr.co.ioacademy; //iocademy 윤찬식 강사님 // Reflection 성능. // 1. Refelction 은 염려할만큼의 성능 저하는 없다. // 2. 잘 설계된 Reflection은 객체 지향의 철학을 어긋나지 않으면서, // 더 좋은 코드를 만들어 낼 수 있다. public class Example6 { public static void doRegular() throws Exception { long start = System.currentTimeMillis(); for (int i = 0; i < 1000000; i++) { Point p = new Point(); p.print(); } System.out.println(System.currentTimeMillis() - start); } public static void doReflection() throws Exception { long start = System.currentTimeMillis(); Class clazz = Class.forName("kr.co.ioacademy.Point"); for (int i = 0; i < 1000000; i++) { Point p = (Point) clazz.newInstance(); p.print(); } System.out.println(System.currentTimeMillis() - start); } public static void main(String[] args) throws Exception { doRegular(); doReflection(); } } class Point { private int x; private int y; public void print() { } }



  • Reflection에 대한 오해와 진실
    (출처: https://kmongcom.wordpress.com/2014/03/15/%EC%9E%90%EB%B0%94-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98%EC%97%90-%EB%8C%80%ED%95%9C-%EC%98%A4%ED%95%B4%EC%99%80-%EC%A7%84%EC%8B%A4/)

    사용처

    현장에서 자바 개발자들이 Reflection을 직접 사용하는 것은 극히 드문 일이다. 그것은 Reflection이 적용될 수 있고 또한 적용되어야 할 곳은 라이브러리 클래스, 공통 컴포넌트 클래스, 그리고 프레임워크와 같이 Reflection을 통해 얻는 이득(재사용성, 확장성, 생산성, 유연성 등)이 극대화될 수 있는 곳이어야 하기 때문이다.

    1. Java Serialization

     
    객체를 직렬화(Serialization) 해야 할 경우 Serializable 인터페이스를 구현한다. 그리고, 직렬화된 객체를 읽기 위해서는 java.io.ObjectInputStream 클래스의 readObject() 메서드를 이용한다. 필요에 따라 Serializable 인터페이스를 구현한 클래스가 readObject() 메서드를 구현할 수도 있다. 이때, java.io.ObjectInputStream 클래스의 readObject() 메서드는 내부적으로 Reflection을 이용하여 직렬화된 객체의 readObject() 메서드를 호출한다.

    a4

    그림 4. Reflection을 사용한 Serialization

    2. Apache Commons BeanUtils library

    Struts 프레임워크를 적용한 프로젝트에서 개발한 경험이 있다면, Apache Commons 프로젝트의 BeanUtils 라이브러리 사용을 고민해 본 경험 또한 있을 것이다. Struts 프레임워크는 HttpRequest 객체의 파라미터를 이용하여 ActionForm 클래스의 객체를 생성한다. 이 ActionForm 클래스의 객체를 생성하는 곳에서도 Reflection이 적용되었다.

    Struts를 이용할 경우, 가장 성가신 부분은 ActionForm 클래스의 객체를 대응하는 VO 클래스의 객체로 변환하는 작업이다. 이 작업은 서비스 레이어를 Struts에 종속되지 않게 하기 위해 또는, 레이어 분리를 위해 반드시 수행되어야 한다. 만일 지금까지 ActionForm 객체를 서비스 레이어로 바로 넘겼다면 다시 한번 생각해 보라. VO 클래스 사용에 따른 레이어의 분리와, VO를 사용하지 않음으로써 얻는 개발 생산성 증가, 둘 중 어느 한가지를 택한 것인지.

     이때, 사용할 수 있는 것이 Commons BeanUtils 라이브러리이다. Beanutils.copyProperties(Object dest, Object orig)를 이용하여 간단히 ActionForm 객체를 VO 객체로 변환할 수 있다. 규칙은 ActionForm 클래스의 인스턴스 변수명과 VO 클래스의 인스턴수 변수명이 같아야 한다는 것이다. 이 규칙을 따른다면, Reflection의 마술이 여러분을 위한 모든 작업을 수행해 줄 것이다.

    이슈

    1. Reflection을 사용한 코드는 느리다

    개발자들 사이에 공공연히 진실로 받아들여지는 이 말은 사실이 아니다. 적절히 사용한 Reflection은 오히려 성능을 향상시킬 수 있으며, 또한 많은 이득을 제공한다. 뿐만 아니라, 성능만을 고려한 구현이 객체 지향의 설계 원칙들을 역행한다면, 오히려 이는 더욱 나쁜 결과를 낳게 된다.

    2. Reflection을 이용하여 개발한 프로그램은 에러가 발생하기 쉽고 디버깅이 어렵다

    Reflection은 컴파일 시 타입 체킹을 할 수 없다. 따라서, 런타임시 잘못된 파라미터로 인해 런타임 에러가 발생하기 쉽다. 이는 사실이다. 하지만, 적절히 사용된 런타임 에러 메시지를 이용해 충분히 디버깅이 쉬운 환경으로 만들 수 있다.

    3. Reflection을 사용한 코드는 복잡하다

    Reflection을 사용한 코드는 일반적인 객체 생성, 메서드 호출 코드에 비교하면 복잡한 것이 사실이다. 하지만 클래스의 타입을 비교하여 객체를 생성하는 코드의 경우, 대량의 if/else문을 사용하는 것보다 Reflection을 이용하여 재사용 가능한 컴포넌트로 만든다면, 오히려 코드를 단순화한다.

    4. 성능(Performance) vs. 유연성(Flexibility)

    앞서 말한 바와 같이 “Reflection을 사용한 코드는 느리다”는 사실은 사실이 아니다. 이 말은 Reflection을 사용할 경우 성능이 떨어지지 않는다라는 얘기가 아니다. 오히려, 성능이 떨어진다는 결과가 다수 존재한다. 아래는 Dennis Sosnoski(5. Java Programming dynamics, Part 2: Introducing reflection)가 측정한 Reflection에 관한 성능 결과이다. 그림에서 알 수 있듯이, Reflection을 사용할 경우, 직접(Direct) 또는 참조(Reference)의 경우에 비해 2 ~ 4배 정도 느리다.


    a5

    그림 5. 필드 변수 Access 시간

    a6

    그림 6. 메서드 호출 시간

    이 결과를 통해 알 수 있는 사실은 “Reflection에 따른 성능 저하”가 아니라 “성능 측정 결과, Reflection을 사용한 
    지금 이 경우에는 성능이 저하되는 것을 검증했다”라는 것이다. 최적화 또는 성능 개선(Optimization)시 유의해야 할 점은 반드시 최적화 이전과 이후의 성능을 측정하여, 성능개선이 가시적으로 보일 때에만 적용해야 한다는 것이다. 만일 최적화가 필요하다고 느낀다면, 아래 규칙을 따르라.

    a7

    그림 7. 최적화 규칙

    Reflection과 관련된 성능에 관한 논쟁은 오해에서 비롯된 것이다. 이것은 JDK 초기 버전(1.3.0 이전 버전)의 경우 Reflection의 성능이 현저히 떨어졌다. 하지만 이후의 JDK 버전에서는, Reflection의 중요성을 인식한 Sun의 지속적인 노력으로 성능이 개선되고 있으며, 앞으로도 개선의 여지가 남아 있다. 뿐만 아니라 잘 적용한 Reflection은 많은 이득을 제공한다.

    Reflection을 통해 얻을 수 있는 가장 큰 이득은 시스템 유연성(Flexibility)이다. “그림 3″에서 보는 바와 같이 Mouse 컨트롤러는 미래의 어떤 마우스와도 동작할 수 있다. 이처럼 성능보다 유연성이 더 중요한 상황이 많다

    물론 Reflection 적용에 따른 가시적인 성능 저하를 확신한다면, 다른 대안을 생각해 볼 수 있다. 대안에는, Reflection 대신 interface를 통한 메서드 호출, 코드 자동 생성(Code Generation), 또는 최악의 경우 하드 코딩이 있다.

    5. Compile vs. Run-time Type Checking

     자바의 경우 컴파일 단계에 강력한 Type Checking을 지원한다. 아래와 같이 두 개의 클래스가 틀릴 경우, 컴파일이 에러가 발생한다.

    a8

    그림 8. 컴파일 에러 예

    Reflection은 실제 클래스 없이 클래스의 이름 또는 메서드의 이름만을 이용한다. 따라서, 아래와 같이, 개발 단계 또는 컴파일 단계에는 Type Checking을 하지 않는다.

    a9

    그림 9. Checked Exception 예

    대신, 실행시 발생할 수 있는 Exception을 처리하기 위한 try/catch문을 추가해야 한다.

    자바가 제공하는 Exception의 종류에는 Checked Exception, Run-time Exception 그리고 error가 있다. 이중 Checked Exception의 경우, 위와 같이 컴파일 단계에 catch하거나 throw해야만 컴파일 오류가 발생하지 않는다. 이는 Checked Exception은 예외 상황에서 프로그램적으로 복구할 수 있는 방법이 존재하는 경우를 위한 Exception이기 때문이다.

    하지만 Reflection 사용에 따른 Exception으로부터 복구할 수 있는 상황이란 거의 없다. 예를 들어, 위의 Class Not Found Exception이 발생한다면, 이는 클라이언트가 잘못된 클래스 명을 넘겨주었거나 또는 해당 클래스가 없을 2가지 경우다. 이는 모두 프로그램 에러 상황으로 오히려 Run-time Exception에 가깝다.

     결국, Reflection을 사용함으로써, 개발단계에서 조기에 발견될 수도 있었을 프로그램 에러들이 런타임시에 발생하게 되는 것이다. 이처럼 에러 상황이 늦게 발견하면 디버깅이 어려워지게 된다. 이와 같은 경우를 위해서, 런타임시 디버깅을 위해 상세한 에러 메시징 기능을 포함하는 것이 좋다.

    Drug heals the pain, Overdose kills the gain

    Reflection의 사용에 관한 사실들은 사실 사실이 아니다. 성능, 디버깅, 그리고 복잡성과 관련된 내용들은 잘못 사용된 예에서 파생한 오해들이다.

    Reflection은 염려할 만큼의 성능저하를 가져오지 않는다. 대량의 if/else문이나 switch문 대신, 잘 설계된 Reflection은 객체지향 철학을 어기지 않으면서도 더 좋은 성능을 발휘할 수도 있다.

    또한 디버깅이 어려운 것은 컴파일 단계에 처리할 수 있는 오류들이 실행 단계에 발생하기 때문이 아니다. 더 근본적인 이유는 Reflection을 사용할 경우에 발생할 런타임 에러 메시지를 최대한 상세하게 그리고 친절하게 표시하도록 Exception 전략을 설계하지 못했기 때문이다. 잘 설계된 Exception 처리 전략은 Reflection 뿐만 아니라 시스템의 전체적인 디버깅을 쉽게 만든다.

    Reflection의 복잡성은 사실 개발자 개개인의 초점에 맞추었을 때의 얘기다. 하지만, Reflection의 사용은 개발자의 관점이 아닌 아키텍트 중심으로 설계되어야 한다. Reflection이 가장 유용한 곳이 바로 시스템의 아키텍처를 이루는 컴포넌트들이기 때문이다. 오히려 잘 설계된 Reflection이 제공하는 서비스는 개발을 더욱 단순화 한다.

    하지만 지나친 사용은 화를 부를 수 있다. Reflection을 적절히 사용했고 많은 이득이 따른다 하더라도, 더 간단한 해결책이 존재한다면, Reflection을 사용하지 말 것을 권한다. 단순한 해결책은 언제 어느 경우에나 최상의 선택이다.

    Reflection의 사용은 양날의 검과 같다. 잘 사용한다면, 이름을 불러주었을 때, 여러분의 꽃이 되어 줄 것이다.


'Programing > Java' 카테고리의 다른 글

Effective Java - Exception  (0) 2016.03.09
Effective Java - Annotation  (0) 2016.03.08
Effective Java - 스레드(2)  (0) 2016.03.07
Effective Java - 스레드(1)  (0) 2016.03.07
Effective Java - 불변 객체  (0) 2016.03.07
  • Atomic
    : 원자적 연산은 '원자적인'방법 즉, 중간에 어떠한 방해도 받지 않고 어셈블리어 명령어 하나로 실행할수 있다. 
    : 메모리에 접근하지 않거나 한번 접근하는 어셈블리어 명령은 원자적이다.
    : inc나 dec같이 메모리에서 데이터를 읽고, 이를 갱신하고, 갱신한 값을 메모리에 다시 쓰는 읽기/수정/쓰기 어셈블리어 명령은 읽기와 쓰기 사이에 다른 프로세서가 메모리버스를 점유하지 않는 한 원자적이다. 유니프로세서 시스템에서는 같은 프로세서에서 메모리 접근이 일어나므로 메모리버스를 훔치는일은 발생하지 않는다.

    ∴ 윈도우 프로그램에서 동기화
     : 커널 모드 동기화 vs. 유저모드 동기화
     : 유저모드는 Atomic Operation 과 CRITICAL_SECTION 으로 구분
     : 커널 모드는 세마포어와 뮤텍스로 구분된다. 뮤텍스(두 프로세스간)는 세마포어 기법(멀티 프로세스간)의 일부분으로 포함되는 개념이다.

    : java 1.5 부터 java.util.concurrent.atomic에 Atomic 관련 타입들 존재
    : 속도는 perf3 > perf1 > perf2
    : Atomic이 무조건 좋다는 법은 없다. -> 적절하게 사용하면 된다.

    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // 1 억 만들기
    
    import org.junit.Test;
    
    import java.util.concurrent.atomic.AtomicLong;
    
    // value += 2; -> Atomic 하지 않다.
    // 1. value 를 메모리부터 로드
    // 2. 2를 더하고
    // 3. 메모리에 저장
    
    public class Example5 {
        private static final int THREAD_COUNT = 2;
        // 1. Atomic 사용
        private static AtomicLong value1 = new AtomicLong(0);
        // private static volatile long value = 0;
    
        private final static Runnable TASK = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000000 / THREAD_COUNT; i++)
                    value1.addAndGet(2);
                    // value += 2;
            }
        };
    
        @Test 
        public void perf1() throws InterruptedException {
            Thread[] threads = new Thread[THREAD_COUNT];
            for (int i = 0; i < THREAD_COUNT; i++)
                threads[i] = new Thread(TASK);
    
            for (Thread t : threads)
                t.start();
    
            boolean alive = true;
            while (alive) {
                for (Thread t : threads) {
                    t.join(10);
                    if (alive = t.isAlive())
                        break;
                }
            }
    
            System.out.println("value : " + value1.get());
        }
        
        // 2. volatile 사용 - for문 연산 자체에 동기화
        private static volatile long value2 = 0;
        private final static Runnable TASK2 = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50000000 / THREAD_COUNT; i++)
                    synchronized (Example5.class) {
                        value2 += 2;
                    }
            }
        };
        @Test
        public void perf2() throws InterruptedException {
            Thread[] threads = new Thread[THREAD_COUNT];
            for (int i = 0; i < THREAD_COUNT; i++)
                threads[i] = new Thread(TASK2);
    
            for (Thread t : threads)
                t.start();
    
            boolean alive = true;
            while (alive) {
                for (Thread t : threads) {
                    t.join(10);
                    if (alive = t.isAlive())
                        break;
                }
            }
    
            System.out.println("value : " + value2);
        }
    
        // 3. volatile 사용 - for문 연산 종료 후 동기화
        private static volatile long value3 = 0;
        private final static Runnable TASK3 = new Runnable() {
            @Override
            public void run() {
                long local_sum = 0;
                for (int i = 0; i < 50000000 / THREAD_COUNT; i++)
                    local_sum += 2;
    
                synchronized (Example5.class) {
                    value3 += local_sum;
                }
            }
        };
        @Test
        public void perf3() throws InterruptedException {
            Thread[] threads = new Thread[THREAD_COUNT];
            for (int i = 0; i < THREAD_COUNT; i++)
                threads[i] = new Thread(TASK3);
    
            for (Thread t : threads)
                t.start();
    
            boolean alive = true;
            while (alive) {
                for (Thread t : threads) {
                    t.join(10);
                    if (alive = t.isAlive())
                        break;
                }
            }
    
            System.out.println("value : " + value3);
        }
    }
    

  • 가짜 공유 (False Sharing)
    멀티 코어 CPU에서 발생할 수 있는 문제다. 멀티 코어 CPU에서는 데이터를 word 단위로 읽어오는 대신 메모리 I/O 효율성을 위해서 cache-line로 읽어오는데, 이때 문제가 생길 수 있다. 두개의 메모리 연산이 동일한 캐쉬라인에서 실행될 경우, CPU<->Memory 버스 사이에서 하드웨어적인 락이 걸리는데, 이때 하드웨어적인 병목현상이 발생한다.
    : 멀티코어에서는 A스레드와 B스레드에서 인접 메모리를 접근할때, 캐시에 있던 내용을 메모리에 반영하려 시도. 인접 메모리를 읽고 있는 상태이기에 병행 수행시 데이터의 유효성을 조금이라도 높이기 위해 메모리에 반영하는 과정에서 속도 저하가 발생하는 것.
    캐시 라인이라 함은 지역성에 근거해 인접한 데이터를 미리 읽어옴으로써 속도향상을 노리는 것

    지역성(locality)은 아래 추정에 근거합니다.
    1. 지금 읽힌 데이터는 이후에도 자주 사용될 가능성이 높다.
    2. 지금 읽힌 데이터와 인접한 데이터는 이어서 사용될 가능성이 높다.

    패딩(padding)를 통한 해결 -> 메모리를 손해보더라도 속도에서 이득을 보자
    : p1, p2, p3, p4, p5, p6(64byte)를 선언하여 캐쉬라인에 대한 패딩 확보 (annotation으로도 존재한다.)

    public final class X {
        public volatile int f1;
        public volatile int f2;
    }
    




    참고 : http://www.smallake.kr/?p=2271, http://elky.tistory.com/318, http://daniel.mitterdorfer.name/articles/2014/false-sharing/

    package kr.co.ioacademy; //iocademy 윤찬식 강사님 import org.junit.Test; import sun.misc.Contended; class C { public final static int NUM_THREADS = 8; public final static long ITERATIONS = 50L * 1000L * 1000L; } // 가짜 공유 문제(False Sharing) // 개념 : 두 스레드간의 공유가 발생하지 않음에도 캐시라인의 공유에 의해서 // 서로의 캐시를 무효화함으로 성능이 저하되는 문제. public class Example6 { @Test public void runTest() throws InterruptedException { Thread[] threads = new Thread[C.NUM_THREADS]; for (int i = 0; i < threads.length; i++) { threads[i] = new Task(i); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } } } class Task extends Thread { static class Long { public volatile long value = 0L; public long p1, p2, p3, p4, p5, p6; // 64byte padding } private static Long[] longs = new Long[C.NUM_THREADS]; static { for (int i = 0; i < longs.length; i++) longs[i] = new Long(); } private int index; public Task(int index) { this.index = index; } @Override public void run() { long i = C.ITERATIONS + 1; while (0 != --i) { longs[index].value = i; } } }


'Programing > Java' 카테고리의 다른 글

Effective Java - Annotation  (0) 2016.03.08
Effective Java - Reflection  (0) 2016.03.08
Effective Java - 스레드(1)  (0) 2016.03.07
Effective Java - 불변 객체  (0) 2016.03.07
Effective Java - 객체 비교, 복제  (0) 2016.03.04
  • Thread 설계
    - Step 1. Single Thread Model
     AutoCloseable Interface

     
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.Executor;
    import java.util.concurrent.Executors;
    
    // C99, C11(thread) - Android M
    // C++11/14
    // Java 8, 9
    
    public class Example3 {
        /*
       private static void handleRequest(Socket connection) {
          OutputStream os = null;
          try {
              os = connection.getOutputStream();
              os.write("Hello World".getBytes());
              os.flush();
          } catch (IOException e) {
              e.printStackTrace();
          } finally {
              if (os != null)
                  try {
                      os.close();
                  } catch (IOException e) {
    
                  }
          }
      }
      */
    
        // Try with Resource : Java 7 - (C# using block)
        // 1. Connection, Socket, Stream 비 메모리 자원을 해지하는
        //   명시적인 종료 메소드를 호출해야 하는 것은 코드를 어지럽힌다.
        // 2. try 블록에 존재하는 자원을 자동으로 회수해준다.
        // 3. AutoCloseable Interface
        private static void handleRequest(Socket connection) {
            try (OutputStream os = connection.getOutputStream();
                 InputStream is = connection.getInputStream()) {
                os.write("Hello World".getBytes());
                os.flush();
            } catch (IOException e) {
            }
        }
    
        
        // Step 1. Single Thread Model
        // 1) 한번에 하나의 요청을 처리하는 것이 가능하다.
        // 2) 단일 스레드에서 IO 작업 등으로 대기하는 경우, 하드웨어 자원을
        //    효과적으로 사용할 수 없다.
        public static void main(String[] args) throws IOException {
            ServerSocket socket = new ServerSocket(8080);
    
            while (true) {
                Socket connection = socket.accept();
                handleRequest(connection);
            }
        }
    

    - Step 2. Thread per Connection Model
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.Executor;
    import java.util.concurrent.Executors;
    
    // C99, C11(thread) - Android M
    // C++11/14
    // Java 8, 9
    
    public class Example3 {
        // Try with Resource : Java 7 - (C# using block)
        // 1. Connection, Socket, Stream 비 메모리 자원을 해지하는
        //   명시적인 종료 메소드를 호출해야 하는 것은 코드를 어지럽힌다.
        // 2. try 블록에 존재하는 자원을 자동으로 회수해준다.
        // 3. AutoCloseable Interface
        private static void handleRequest(Socket connection) {
            try (OutputStream os = connection.getOutputStream();
                 InputStream is = connection.getInputStream()) {
                os.write("Hello World".getBytes());
                os.flush();
            } catch (IOException e) {
            }
        }
        
    
        
        // Step 2. Thread per Connection Model
        // 순차적인 실행 방법보다 훨씬 더 많은 작업을 수행하는 것이 가능하다.
    
        // 문제점
        // 1. 엄청나게 많은 스레드가 생성된다.
        // 2. Thread 를 만들고 제거하는 작업에도 자원이 소모된다.
        // : 클라이언트의 요청이 작은 단위로 일어나는 경우에는 더욱더 문제가 심하다.
        // 3. 자원 낭비가 심하다.
        //  1) 실행 중인 스레드는 메모리를 많이 소모한다.
        //     Java의 스택은 두 개이다.(Java, Native)
        //  2) 프로세서의 개수 보다 많은 스레드가 생성되면 대부분의 스레드는
        //     대기 상태에 있다.
        // 4. 컨텍스트 스위칭의 비용이 크다.
        // 5. OS 에서 생성할 수 있는 스레드의 개수는 제한되어 있다.
        public static void main(String[] args) throws IOException {
            ServerSocket socket = new ServerSocket(8080);
            while (true) {
                final Socket connection = socket.accept();
                Runnable task = new Runnable() {
                    @Override
                    public void run() {
                        handleRequest(connection);
                    }
                };
    
                new Thread(task).start();
            }
        }
    

    - Step 3. Pool-based Model
     
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.util.concurrent.Executor;
    import java.util.concurrent.Executors;
    
    // C99, C11(thread) - Android M
    // C++11/14
    // Java 8, 9
    
    public class Example3 {
        // Try with Resource : Java 7 - (C# using block)
        // 1. Connection, Socket, Stream 비 메모리 자원을 해지하는
        //   명시적인 종료 메소드를 호출해야 하는 것은 코드를 어지럽힌다.
        // 2. try 블록에 존재하는 자원을 자동으로 회수해준다.
        // 3. AutoCloseable Interface
        private static void handleRequest(Socket connection) {
            try (OutputStream os = connection.getOutputStream();
                 InputStream is = connection.getInputStream()) {
                os.write("Hello World".getBytes());
                os.flush();
            } catch (IOException e) {
            }
        }
        
        // Step 3. Pool-based Model
        // 순차적으로 문제를 해결하는 방법은 응답 속도와 전체적인 성능이 떨어지는
        // 문제점이 있고 작업별로 스레드를 만들어 내는 방법은 자원 관리 측면에서
        // 문제점이 있다
    
        // 해결책)
        // 1) 스레드의 생성을 제한해야 한다 -> Thread pool
        // 2) 생산자 & 소비자 모델
    
        public static final int NTHREADS = 100;
        public static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
        // 1) newFixedThreadPool
        // 작업이 등록되면 제한된 개수까지 스레드를 생성해서 해당 작업을 처리한다.
        // 생성 이후에 더 이상 생성하지 않고 스레드 수를 유지한다.
    
        // 2) newCachedThreadPool
        // 스레드 수가 처리할 작업보다 많아서 쉬는 스레드가 발생하면,
        // 스레드를 종료한다.
    
        // 3) newSingleThreadPool
        // 단일 스레드로 처리한다. 등록된 작업은 다양한 정책에 의해서 수행될 수 있다.
        // (LIFO, FIFO, 우선순위)
        public static void main(String[] args) throws IOException {
            ServerSocket socket = new ServerSocket(8080);
    
            while (true) {
                Socket connection = socket.accept();
                Runnable task = new Runnable() {
                    @Override
                    public void run() {
                        handleRequest(connection);
                    }
                };
    
                exec.execute(task);
            }
    
        }
    }
    

  • 스레드의 중단 및 종료 처리
    - 1_동기화
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // 스레드의 중단 및 종료 처리
    // 1. 스레드를 만들고 시작하는 것은 쉬운 일이다.
    // 2. 스레드를 안전하고 빠르게 멈추게 하는 것은 어렵다.
    // 3. 자바의 스레드를 멈추는 기능이 폐기되었다.
    //   Thread.stop() / Thread.suspend()
    
    // 4. 스레드를 강제로 종료하면 공유되어 있는 여러 가지 상태가
    //    망가질 수 있다.
    // 5. 스레드를 멈춰달라는 요청이 오면 진행 중이던 모든 작업을 정리한 후
    //    스스로 종료하도록 만들어야 한다.
    
    
    import java.util.concurrent.TimeUnit;
    
    
    /*public class Example4 {
        private static boolean stopRequested = false;
        // 문제점 : main 스레드가 변경한 stopRequested 의 새로운 값을 background
        //        thread 가 관측할 수 없다.
        public static void main(String[] args) throws InterruptedException {
            Thread backgroundThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    long i = 0;
                    while (!stopRequested) {
                        i++;
                        //System.out.println("value - "  + i);
                    }
                }
            });
    
            backgroundThread.start();
    
            TimeUnit.SECONDS.sleep(3);
            stopRequested = true;
        }
    
    }
    */
    
    // 문제점 : main 스레드가 변경한 stopRequested 의 새로운 값을 background
    //        thread 가 관측할 수 없다.
    
    // 해결책 1. 동기화(synchronization)
    // 1) 상호 배제 : 다른 스레드가 변경 중인 객체의 상태를 관측할 수 없도록 해준다. - 락
    // 2) 동기화는 동기화 메소드 또는 동기화 블록에 의해 진입한 스레드가 동일한 락의 보호 아래
    //    이루어진 모든 변경을 관측할 수 있다.
    
    // 문제점 : 순환문의 각 단계마다 동기화를 실행하는 비용이 크다.
    
    
    public class Example4 {
        private static synchronized void stop() { stopRequested = true; }
        private static synchronized boolean isStopRequested() {
            return stopRequested;
        }
    
        private static boolean stopRequested = false;
    
        public static void main(String[] args) throws InterruptedException {
            Thread backgroundThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    long i = 0;
                    while (!isStopRequested()) {
                        i++;
                        System.out.println("value - "  + i);
                    }
                }
            });
    
            backgroundThread.start();
    
            TimeUnit.SECONDS.sleep(3);
            stop();
            stopRequested = true;
        }
    }
    

  • - 2_cv 제한자 (const-volatile)
    package kr.co.ioacademy; //iocademy 윤찬식 강사님
    
    // 스레드의 중단 및 종료 처리
    // 1. 스레드를 만들고 시작하는 것은 쉬운 일이다.
    // 2. 스레드를 안전하고 빠르게 멈추게 하는 것은 어렵다.
    // 3. 자바의 스레드를 멈추는 기능이 폐기되었다.
    //   Thread.stop() / Thread.suspend()
    
    // 4. 스레드를 강제로 종료하면 공유되어 있는 여러 가지 상태가
    //    망가질 수 있다.
    // 5. 스레드를 멈춰달라는 요청이 오면 진행 중이던 모든 작업을 정리한 후
    //    스스로 종료하도록 만들어야 한다.
    
    import java.util.concurrent.TimeUnit;
    
    // cv 제한자(const-volatile) : C, C++, C# - Debug / [Release]
    
    // 해결책 2. volatile
    // 정의 : 컴파일러가 최적화를 하지 말라는 지시어 (내부에 cache하여 사용하지 않도록 막음.)
    //   멀티 스레드 상에서 공유되는 변수, 하드웨어와 연결된 변수 사용시
    //   되도록 volatile 붙이자.
    // 효과 : 어떤 스레드건 가장 최근에 기록된 값을 읽을 수 있다.
    public class Example4 {
        private static volatile boolean stopRequested = false;
    
        public static void main(String[] args) throws InterruptedException {
            Thread backgroundThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    long i = 0;
                    while (!stopRequested) {
                        i++;
                        System.out.println("value - "  + i);
                    }
                }
            });
    
            backgroundThread.start();
    
            TimeUnit.SECONDS.sleep(3);
            stopRequested = true;
        }
    }
    


'Programing > Java' 카테고리의 다른 글

Effective Java - Reflection  (0) 2016.03.08
Effective Java - 스레드(2)  (0) 2016.03.07
Effective Java - 불변 객체  (0) 2016.03.07
Effective Java - 객체 비교, 복제  (0) 2016.03.04
Effective Java - 객체 소멸  (0) 2016.03.04

+ Recent posts