336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


저는 기술서적을 다른사람한테 추천하는경우가 거이없습니다.

디자인패턴, 리팩토링등의 책을 사서보라고는해도 정확히 어떤책을 사서보라고는 하지않죠


책을사서 보는이유는 개념이해나 신기술등을 미리알고있기 위해서 책을 읽는경우가많고

관련해서 사용할때는 책에서는 이론이나 약간의개념만보고 인터넷이나 API문서 또는 직접해보면서 알게되는게 더많기 때문인데요.

(관련내용도 잘모르고 이해도잘못한사람이 번역만 한책도 많기도하구요..

책내용이 너무부실하면 목자만 보고 인터넷으로 검색해서 따로공부하기도 합니다.)


오랜만에 기본서적을 다시 쭉 읽어보려고 정하고

자주가는 카페에서 판매중인책인 (http://cafe.naver.com/javachobostudy)

JAVA의 정석이란 책을사서 보았는데

꼼꼼하게 정리했을뿐아니라

필자가 독자의 이해를 쉽게하기위해 많은부분을 고민하면서 썻다는게 느껴지더라구요.

얼마나 많은 시간을 이책을 집필하기위해 보냈는지 느껴지더라구요..


개인적으로 개발에대해 가르쳐줄떄 기본서적이나 기술서적등을 보고 어떻게 설명하는게 이해가쉬울지 연구해본경험이 있어서 책을 적게읽지는 않았다고 생각하는 편인데도 이책을 정독하니 놓친부분들이 보이네요..;;


float, double의 정밀도 자리수라던지 

10진수에서는 무한대가아닌데 2진수로 변환할때 무한대가발생해서

결과가 무한대로 나올수있다든지에대해서는

책을다시 읽으면서 알게된내용입니다.




책제목은 JAVA의 정석이고 

남궁성님이 저자이신 책입니다.

http://cafe.naver.com/javachobostudy


저자님이 직접운영중인 카페시고 관련답변도 매번친절하게 달아주시네요..

사실 자바가 주언어인 사람들한테는 유명한카페이기도하고 자바관련 스터디모임을 구할때도 자주사용하는 카페죠.


개발은 기본의응용이니 이렇게 잘만들어진 기본서적하나는 꼼꼼히 읽어보는것도 도움이 될거같아요.


336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
변수타입설명기본값리터럴 접미사변수의 범위byte정밀도
booleantrue와 false중에 하나만 저장FALSE false, true1 
char유니코드(Unicode) 한문자 저장'\u0000' \u0000~\uffff (0~65,535)2 
byte정수값을 저장0 -128~1271 
short0 -32,768~32,7672 
int0 -2,147,483,648~2,147,483,6474 
long0LL , l-9223372036854775808~92233720368547758078 
float실수값을 부동소수점으로 저장0.0ff, F1.4E-45~3.4028235E3847자리
double0.0dd, D, 생략가능4.9E-324~1.7976931348623157E308815자리
참조형객체의 주소를 저장null 0x0~0xffffffff4 


336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


만능유틸을 만들다보니까 java로 DB테이블의 키본키정보를 가져와야할 필요가 생겼습니다.

관련 제공방법을 찾아보니 인터넷에 예제소스가 있더군요.


우선찾은 소스인데 실험해보니

  public static Set<String> getPrimaryKeyColumnsForTable(Connection connection, String tableName) throws SQLException {
    try(ResultSet pkColumns= connection.getMetaData().getPrimaryKeys(null,null,tableName);) {
      SortedSet<String> pkColumnSet = new TreeSet<>();
      while(pkColumns.next()) {
        String pkColumnName = pkColumns.getString("COLUMN_NAME");
        Integer pkPosition = pkColumns.getInt("KEY_SEQ");
        out.println(""+pkColumnName+" is the "+pkPosition+". column of the primary key of the table "+tableName);
        pkColumnSet.add(pkColumnName);
      }
      return pkColumnSet;
    }


CD_ONTOLOGY is the 3. column of the primary key of the table TB_ARA_DOC_ONTOLOGY

CD_ONTOLOGY is the 3. column of the primary key of the table TB_ARA_DOC_ONTOLOGY

CD_ONTOLOGY_ANALYSIS is the 4. column of the primary key of the table TB_ARA_DOC_ONTOLOGY

CD_ONTOLOGY_ANALYSIS is the 4. column of the primary key of the table TB_ARA_DOC_ONTOLOGY

ID_ARA_ANAL_DOC is the 1. column of the primary key of the table TB_ARA_DOC_ONTOLOGY

ID_ARA_ANAL_DOC is the 1. column of the primary key of the table TB_ARA_DOC_ONTOLOGY

NB_LINE is the 2. column of the primary key of the table TB_ARA_DOC_ONTOLOGY

NB_LINE is the 2. column of the primary key of the table TB_ARA_DOC_ONTOLOGY

이런식으로 결과가 여러번찍히게 됩니다.

위예제소스로는 순서정보가 다르게 넘어올때는 정렬되지 않는 컬럼정보를 넘겨주게되있더군요.


그래서 기본키컬럼정보및 순서정보를 map으로 넘기게 조금변형햇습니다.

중복된 값이 있기때문에 Set이나 Map등 중복을 허용하지 않는 클래스사용이 좋아보이네요


public static Map<String, Integer> getPrimaryKeyColumnsForTable(Connection connection, String tableName) throws SQLException {

   try(ResultSet pkColumns= connection.getMetaData().getPrimaryKeys(null,null,tableName);) {

    

   Map<String, Integer> pkMap = new HashMap<String, Integer>();

     while(pkColumns.next()) {

       String pkColumnName = pkColumns.getString("COLUMN_NAME");

       Integer pkPosition = pkColumns.getInt("KEY_SEQ");

       

       pkMap.put(pkColumnName, pkPosition);

     }

     return pkMap;

   }

}


컬럼정보와 컬럼의 순서정보를 맵정보로 넘겨주는 예제소스입니다.

예제소스를 그대로 사용하기보다는 개발상황에맞게 예외상황 코드를적절히넣어서 사용하는게 좋아보입니다.

위소스에는 사용한 ResultSet pkColumns 부분을 pkColumns.close시키는부분이나 connection.close부분이없으니

관련 부분은 개발환경에맞게 예외상황처리를해주세요.

예를들면:)

public static Map<String, Integer> getPrimaryKeyColumnsForTable(Connection conn, String tableName) {

ResultSet pkColumns= null;

Map<String, Integer> pkMap = new HashMap<String, Integer>();

try{

pkColumns= conn.getMetaData().getPrimaryKeys(null,null,tableName);

while(pkColumns.next()) {

   String pkColumnName = pkColumns.getString("COLUMN_NAME");

   Integer pkPosition = pkColumns.getInt("KEY_SEQ");

       

  pkMap.put(pkColumnName, pkPosition);

}

}catch( java.sql.SQLException se){

//예외상황 로그남기기 또는 특수처리등


}catch(Exception e){

//기타예외상황 로그남기기 또는 특수처리등

}finally{

try{if(pkColumns!=null)pkColumns.close(); pkColumns=null; }catch(Exception e){}

}

 

 

 

return pkMap;

}

이런식으로 작성하는게 좋을것같네요,


위기능은 jre1.7(jdk1.7)이상부터 지원합니다.



336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


디자인 패턴이라고하면 특별한 상황에 이렇게 하는게 좋더라 라고 패턴화된 내용입니다.

객체가 하나만 생성되고 생성된 객체를 같이써야할때는 싱글턴

객체의 내용변화를 감시해야할때는 옵저버패턴

이미사용중인 소스의 메소드명 클래스명을 변경하고싶을때는 아답터패턴등이 예입니다.


디자인패턴은 말그대로 그상황에 이렇게 코딩하는 하나의 패턴을 얘기하므로

개발자분들이 이런상황에서 이렇게쓰고있는 패턴이있으면 그것또한 하나의 디자인패턴입니다.

아직이름이 알려져있지 않을뿐이죠.


디자인패턴을 책에 나오는 그대로쓰는경우도 있지만 상황에 맞게 변형해서 쓰는 경우가 더많습니다.


우리보다 선대개발자들은 이런상황에서 이렇게 개발했다고 잘정의해놓은 개발자 필수공부 내용이니까

꼭 공부해보길바래요~


아래는 OOP와 디자인패턴의 관계를 잘설명해놓은 포스팅이있어서 퍼왔어요 








http://blog.naver.com/2feelus/220642212134

자바 OOP와 디자인 패턴 - Java OOP and Design Pattern 


1. OOP 란?

Object Oriented Programming은 프로그래밍 방법론으로 기존의 절차적, 파일단위의 프로그래밍 스타일에서,

완전히 분리된 프로그래밍의 단위인 Object(객체)라는 개념을 가지고 와서 프로그래밍의 방법을 객체간의 소통의 개념으로 가져왔다.

사실상 우리가 보는 세계는 많은 것들이 분리된 단위로 떨어져 독립적으로 존재하고, 커다란 객체는 여러 객체의 조합으로 나타낼 수 있다.


이런 객체의 개념은 아래의 몇단어로 상징적으로 표현된다.

1) abstraction  (추상화) 

- 여러 객체들의 공통된 특성을 묶어 상위개념으로 표현하는 것 : 예 ) 남자 와 여자의 추상적 객체 =  사람

2) Polymorphism (다형성) 

- 객체를 표현하는 다양한 방식이 존재할수 있다. 예) 자식객체는 자식객체와 부모객체, 혹은 인터페이스로서 참조할수 있다.

3) Encapsulation(캡슐화, 은닉화) 

- 숨길 부분과 밖으로 드러낼 부분을 조절할수 있다. 예) private 필드

4) Inheritance (상속성) 

- 부모객체를 상속해서 자식객체를 만들수 있다 예) extends 키워드


자세한 설명은 인터넷에 좋은 설명들이 많다. 


2. Java와 OOP

Java에서 OOP(객체지향 프로그래밍)은 뗄래야 땔수 없는 이름표와 같다. Java가 OOP의 원조는 아니지만 현재 지구상에서 가장 많은 사람들이 사용하는 객체지향언어이다. 다른 OOP언어로서 대표적으로 C++, LISP, C#,Scala등의 OOP를 기반으로 태어난 언어들, 그리고 여러 스크립트 언어들도 최신버전에서는 OOP의 개념을 지원한다. (PHP, Python,ruby등...) 


3. Java은 완벽한 OOP인가?

자바는 '거의' 완벽한 OOP이다. 약간은 OOP에 부족한 면이 있다는 뜻이다.

OOP의 개념중에는 multiple inheritance(다중상속)이라는 개념이 존재한다. 관계가 없는 두 클래스를 상속해서 자식클래스를 만드는 것을 뜻한다.

c++나 LISP, scala등에서는 다중상속을 지원하지만, java나 C#은 이를 지원하지 않는다. 그 이유는 다중상속은 같은 이름을 가진 부모클래스의 메소드들에 대해 어떤 것을 선택할 것인지 애매하기 때문이다.  이는 생성자에 대해서도 마찬가지이다. 이런 모호성(ambiguity)는 좀더 명확한 의미를 가진 클래스디자인을 힘들게 하는 요인이기도 하다. 그래서 언어에 따라 다중상속을 지원하지 않는 경우가 존재하다. 자바의 경우는 강한 타입 캐스팅을 통해, 실제 수행전에 에러를 잡는데에 최적화된 언어이므로, 이를 위해서 가능한 모호함의 요소들은 제외를 하고자 했다. 그래서 자연스럽게 다중상속성은 포기하게 되었다. (1995년에 Java:an Overview" 라는 글에서 자바의 아버지인 James Gosling이 의견을 밝혔다). 앞으로 나올자바에서도 다중상속이 지원될 확률은 거의 없다고 볼수 있다.


주) 참고로 Java 8에서는 Interface 의 default method라는 방식으로 우회적으로 다중상속과 비슷한 방식을 지원하기는 한다. 하지만 이것 또한 진정한 의미의 다중상속은 아니다. 링크참고


4. 디자인 패턴 (Design Patterns) 이란?

오랜동안 많은 사람들에 의해 좋다고 검증된 프로그래밍 패턴. 사람들이 수많은 시간동안 프로그램으로 여러 형태의 문제들을 해결해왔는데,

문제의 타입별로 해결하기 좋은 프로그래밍 패턴들이 있다는 것이 알려졌고, 여기에 이름들이 붙여졌다. 주로 많이 알려진 디자인 패턴은 아래와 같다.


1) 생성 패턴 

추상 팩토리 패턴

빌더 패턴

팩토리 메서드 패턴 

싱글톤 패턴 


2) 구조 패턴

어댑터 패턴

브리지 패턴

컴포지트 패턴

데코레이터 패턴

파사드 패턴 

프록시 패턴 

플라이웨이트 패턴


3) 행위패턴

책임 연쇄 패턴

반복자 패턴

중재자 패턴

전략 패턴

커맨드 패턴

방문자 패턴

인터프리터 패턴

메멘토 패턴

- 옵저버 패턴

상태 패턴

템플릿 메소드 패턴

널 오브젝트 패턴



5. 객체 지향과 디자인 패턴의 관계

디자인 패턴이란 용어자체는 OOP와 직접적인 관련은 없다. 디자인 패턴이라는 말 자체는 어떤 프로그래밍 방법론에도 사용될 수 있다.

그러나 OOP라는 프로그래밍 방식은 Design Pattern이라는 프로그래밍 방식과 매우 잘 어울리며 실제로 거의 함께 쓰이곤 하는 용어이다. 그 이유는 객체(Object)라는 개념이 가진 "변화적인 성질(변이성)" 때문이다.  일반적으로 객체라는 것은 때에 따라 생성되고, 객체의 성질이나 값을 부여받고, 스스로 일들을 수행하고, 다른 객체에게 값을 전달해준뒤, 소멸한다. 즉 메모리에 태어났다가, 변경되고, 소멸된다. 이런 변이성은 Design Pattern에서 가장 많이 언급되는 아래의 방법론과 매우 잘 어울린다. 


생성패턴 = 객체의 생성에 대한 방법

구조패턴 = 객체와 객체 사이의 상속/조합 관계설정 방법

행위패턴 = 객체가 특정 행동을 함으로서 다른 객체에 값을 전달하는 방법



사실 디자인 패턴이라는 말이 유명해진 계기가 GOF(Gang Of Four - 4명의 깡패들) 이라는 사람들이

Design Patterns: Elements of Reusable Object-Oriented Software

이라는 책을 내면서 Design Pattern이라는 용어가 급부상해서, 이제는 Design Patterns이라고 하면  OOP를 빼놓고 얘기할 수가 없게 된것이다.

책의 제목에서 Reusable 이라는 말에 주목할 필요가 있다. 디자인 패턴은 효율적이며 가독성이 높은 코드를 지향할 뿐 아니라,

항상 재사용성이 높은 코드를 염두에 두고 있다는 부분이다.  절차적 프로그래밍에서도 효율적이며 가독성이 높을수는 있지만, OOP에서의 디자인 패턴은 재사용성과  그를 바탕으로한 관리용이성 또한 염두를 두고 있다.


336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.



싱글턴에 대해  잘정리해논 포스팅이 있어서 퍼왔습니다

디자인 패턴중 가장많이 사용되는 패턴입니다.
싱글턴, 팩토리, 옵저버 패턴등은 개발자 필수 공부자료이기도하구요

이보다 더 잘정리할 포스팅은 없을거같아서 퍼왔습니다
다음에는 팩토리, 옵저버 패턴등에 대해 잘정리한 포스팅이 있으면퍼올게요
없다면.. 직접작성하구요~

출저 https://blog.seotory.com/2016/03/19/java-singleton-pattern

initialization on demand holder idiom

부분을 중심적으로 보세요



singleton 이란?

프로그래밍 세계에 OOP 의 개념이 생기면서 객체 자체에 대한 많은 연구와 패턴(pattern)들이 생겨났다. singleton pattern은 인스턴스가 사용될 때에 똑같은 인스턴스를 만들어 내는 것이 아니라, 동일 인스턴스를 사용하게끔 하는 것이 기본 전략이다. 프로그램상에서 동일한 커넥션 객체를 만든다던지, 하나만 사용되야하는 객체를 만들때 매우 유용하다. singleton pattern은 4대 디자인 패턴에 들어갈 정도로 흔히 쓰이는 패턴이다. 물론 core java(java.lang.Runtime, java.awt.Desktop 등등)에서도 singleton pattern이 사용된다.

Eager initialization

아래가 가장 기본적인 singleton pattern이다. 전역 변수로 instance를 만드는데 private static을 이용한다. static이 붙은 클래스변수는 인스턴스화에 상관없이 사용이 가능하게 된다. 하지만 앞의 private 접근제어자로 인해 EagerInitialization.instance로의 접근은 불가능하다. 이런 상태에서 생성자를 private로 명시한다. 생성자를 private로 붙이게되면, new 키워드를 사용할 수 없게된다. 즉 다른 클래스에서 EagerInitialization instance = new EagerInitialization(); 이런 방법을 통한 인스턴스 생성은 불가능해진다. 결국 외부 클래스가 EagerInitialization 클래스의 인스턴스를 가질 수 있는 방법은 11번째 라인에 있는 getInstance() method를 사용하는 수 밖에 없다.

public class EagerInitialization {
	// private static 로 선언.
	private static EagerInitialization instance = new EagerInitialization();
	// 생성자
	private EagerInitialization () {
		System.out.println( "call EagerInitialization constructor." );
	}
	// 조회 method
	public static EagerInitialization getInstance () {
		return instance;
	}
	
	public void print () {
		System.out.println("It's print() method in EagerInitialization instance.");
		System.out.println("instance hashCode > " + instance.hashCode());
	}
}

위의 단순한 singleton pattern은 리소스가 작은 프로그램일때엔 고도화 대상이 아니다. 하지만 프로그램의 크기가 커져서 수 많은 클래스에서 위와 같은 singleton pattern을 사용한다고 가정해보자. 3번째 라인의 new EagerInitialization();으로 인해 클래스가 load 되는 시점에 인스턴스를 생성시키는데 이마저도 부담스러울 수가 있다. 또한 이 소스는 EagerInitialization 클래스가 인스턴스화 되는 시점에 어떠한 에러처리도 할 수가 없다.

static block initialization

public class StaticBlockInitalization {
	private static StaticBlockInitalization instance;
	private StaticBlockInitalization () {}
	
	static {
		try {
			System.out.println("instance create..");
			instance = new StaticBlockInitalization();
		} catch (Exception e) {
			throw new RuntimeException("Exception creating StaticBlockInitalization instance.");
		}
	}
	
	public static StaticBlockInitalization getInstance () {
		return instance;
	}
	
	public void print () {
		System.out.println("It's print() method in StaticBlockInitalization instance.");
		System.out.println("instance hashCode > " + instance.hashCode());
	}
	
}

static 초기화블럭을 이용하면 클래스가 로딩 될 때 최초 한번 실행하게 된다. 특히나 초기화블럭을 이용하면 logic을 담을 수 있기 때문에 복잡한 초기변수 셋팅이나 위와 같이 에러처리를 위한 구문을 담을 수 있다. 첫 번째 패턴보다 좋아보이지만 인스턴스가 사용되는 시점에 생성되는 것은 아니다.

lazy initialization

이제 클래스 인스턴스가 사용되는 시점에 인스턴스를 만드는 singleton pattern을 배워보도록 하자. 아래 소스의 lazy initialization pattern은 필요할때 인스턴스를 생성시키는 것이 핵심이다.

public class LazyInitialization {
	
	private static LazyInitialization instance;
	private LazyInitialization () {}
	
	public static LazyInitialization getInstance () {
		if ( instance == null )
			instance = new LazyInitialization();
		return instance;
	}
	
	public void print () {
		System.out.println("It's print() method in LazyInitialization instance.");
		System.out.println("instance hashCode > " + instance.hashCode());
	}
}

new LazyInitialization(); 가 어디에 선언되었는지 주목해보자. getInstance() method 안에서 사용되었다. if문을 이용해 instance가 null 인 경우에만 new를 사용해 객체를 생성하였다. 최초 사용시점에만 인스턴스화 시키기 때문에 프로그램이 메모리에 적재되는 시점에 부담이 많이 줄게된다. 하지만 여전히 문제는 남아있다. 만약 프로그램이 muilti thread 방식이라면 위와 같은 singleton pattern은 안전하지 않다. 동일 시점에 getInstance() method를 호출하면 인스턴스가 두번 생길 위험이 있다.

thread safe initalization

위에서 문제가 되었던 muilit thread문제를 해결하기 위해 synchronized(동기화)를 사용하여 singleton pattern을 구현한다. 여러 thread들이 동시에 접근해서 인스턴스를 생성시키는 위험은 없어졌다. 하지만 수 많은 thread 들이 getInstance() method 를 호출하게 되면 높은 cost 비용으로 인해 프로그램 전반에 성능저하가 일어난다.

public class ThreadSafeInitalization {
	
	private static ThreadSafeInitalization instance;
	private ThreadSafeInitalization () {}
	
	public static synchronized ThreadSafeInitalization getInstance () {
		if (instance == null)
			instance = new ThreadSafeInitalization();
		return instance;
	}
	
	public void print () {
		System.out.println("It's print() method in ThreadSafeInitalization instance.");
		System.out.println("instance hashCode > " + instance.hashCode());
	}
	
}

initialization on demand holder idiom

미국 메릴랜드 대학의 컴퓨터 과학 연구원인 Bill pugh 가 기존의 java singleton pattern이 가지고 있는 문제들을 해결 하기 위해 새로운 singleton pattern을 제시하였다. Initialization on demand holder idiom기법이다. 이것은 jvm 의 class loader의 매커니즘과 class의 load 시점을 이용하여 내부 class를 생성시킴으로 thread 간의 동기화 문제를 해결한다.

public class InitializationOnDemandHolderIdiom {
	
	private InitializationOnDemandHolderIdiom () {}
	private static class Singleton {
		private static final InitializationOnDemandHolderIdiom instance = new InitializationOnDemandHolderIdiom();
	}
	
	public static InitializationOnDemandHolderIdiom getInstance () {
		System.out.println("create instance");
		return Singleton.instance;
	}
}

initialization on demand holder idiom 역시 lazy initialization이 가능하며 모든 java 버젼과, jvm에서 사용이 가능하다. 현재 java 에서 singleton 을 생성시킨다고 하면 거의 위의 방법을 사용한다고 보면 된다.

enum initialization

Joshua Bloch가 작성한 effective java 책에서 enum singleton 방법이 소개 되었다.

public enum EnumInitialization {
	INSTANCE;
	static String test = "";
	public static EnumInitialization getInstance() {
		test = "test";
		return INSTANCE;
	}
}

enum 이 singleton pattern 으로 사용될 수 있는 이유는 아래와 같다.

  • INSTANCE 가 생성될 때, multi thread 로 부터 안전하다. (추가된 methed 들은 safed 하지 않을 수도 있다.)
  • 단 한번의 인스턴스 생성을 보장한다.
  • 사용이 간편하다.
  • enum value는 자바 프로그램 전역에서 접근이 가능하다.

using reflection to destroy singleton

위에서 여러 방법으로 singleton을 만들어 보았으니 이번엔 java의 reflection을 이용하여 singleton을 깨뜨려 보는법도 배워보자. 누군가 작성한 코드를 원본 수정없이 작업해야 할때 이용될 수 있을 것이다.

public class UsingReflectionToDestroySingleton {
	
	public static void main (String[] args) {
		EagerInitialization instance = EagerInitialization.getInstance();
		EagerInitialization instance2 = null;
		
		try {
			Constructor[] constructors = EagerInitialization.class.getDeclaredConstructors();
			for ( Constructor constructor : constructors ) {
				constructor.setAccessible(true);
				instance2 = (EagerInitialization)constructor.newInstance();
			}
		} catch (Exception e) {
			
		}
		
		System.out.println(instance.hashCode());
		System.out.println(instance2.hashCode());
		
	}
}

위의 코드를 실행해보면 아래 System.out.println();의 두 라인에서 찍히는 hachCode()값이 다른 것을 확인 할 수 있다. java의 reflection은 매우 강력하다. 설령 class 의 생성자가 private 일지라도 강제로 가져와서 새로운 인스턴스 생성이 가능하다. 결국 singleton pattern을 깨뜨리는 것이다. 이 외에도 reflection을 여러곳에서 사용할 수 있으니 알아두는 것이 좋다.

336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

자바에서의 volatile 키워드


volatile?

volatile은 멀티스레딩 환경시 동기화를 해주는 녀석 맞습니다. 읽기 쓰기시에 어떤 스레드가 값을 변경하든, 항상 최신값을 읽어갈 수 있게 해주죠.
그럼 syncronized 키워드하고는 뭐가 다른가?

작업 시 그 변수의 접근에 대해 같은 값을 읽어갈 수 있게 해주는 것은 volatile이나 syncronized나 동일합니다.

읽고 쓰기에 대한 원자성

하지만 volatile은 작업의 요소를 뺀 가시성, 즉 읽기, 쓰기동작에 대해서만 동기화가 됩니다. 그리고 그것은 원자성(Atomic)을 가집니다
말이 어려우니 예를 들면...

int count = 10;

이라는 변수가 있다고 가정하고 스레드 A와 B가 사용한다고 가정합니다.

거의 동시간에 A스레드가 count를 읽어 조작합니다. B도 같이 count를 읽어 조작합니다.
이 경우 두 스레드간에 count의 값은 서로 동일한 값, 즉 동일한 메모리 주소를 가르키지 않습니다.
스레드는 자신만의 저장 영역에 원본의 값을 복사하여 조작하기 때문이죠.

그래서 스레드마다 count는 다른 값을 가지고 있을 수 있습니다.
A에서 변경한 값을 B에서는 영원히 읽어갈 수 없을수도 있습니다.
이런저런 과정 후에 A와 B의 스레드는 작업이 끝나면 다시 조작한 값을 원본 값으로 돌리는데 그 작업이 끝나지 않거나 배치 최적화(reordering. 밑에서 설명) 등으로 그러지 않을 수 있습니다.
그럼 변수 count는 스레드간 불일치가 일어나겠죠.

리오더링 회피

또한 변수 읽기 불일치는 일종의 최적화인 리오더링 결과로 발생하기도 합니다.
리오더링은 보통 컴파일 과정에서 일어나며, 프로그래머가 만들어낸 코드는 컴파일 될 때 좀더 빠르게 실행될 수 있도록 조작을 가하는 최적화를 거칩니다.
그러한 최적화 과정중에 읽기와 쓰기의 순서가 바뀔 수 있는데, 그럴 경우 자칫 순서가 중요한 멀티스레드 환경에서 문제가 발생할 수 있습니다.

volatile을 쓴 변수는 리오더링에서 제외되며, 항상 프로그래머가 지정한 순서로 읽기 및 쓰기를 수행합니다.

연산의 원자성

이것은 가장 처음의 문제와 같은 특성인데요.
보통 변수의 쓰기 읽기는 일견 원자성(누군가 끼어들 틈이 없는 완벽한 작업단위로 이해합시다...)을 띌 수도 있어 보입니다만, 실제 count에 변수가 할당될 때 완벽히 한 메모리 작업 안에서 완벽히 쓰여지지 않습니다.

아, int같은 데이터 타입은 한번에 씁니다...;
그러나 그보다 더 큰 타입, long이나 객체..그리고 변수 할당 및 연산 시 일어나는 작업에서는 완벽히 원자성을 가지지 않고 여러번에 걸쳐 메모리 작업이 일어납니다.
(엄밀히 말하면 JVM 구현에 따라 다를 수 있다고 합니다...)

long stat = 324L;

위 코드에서 long은 데이터형이 8바이트. 즉 64비트입니다. 자바는 여기에 변수를 할당할 때 32비트 단위로 끊어서 할당합니다. 첫 32비트 할당 그다음 32비트 할당...
첫 32비트를 할당했을때 다른 스레드가 값을 읽어간다면...? 정상적이지 않은 상황이 나오겠죠 (물론 무지하게 짧은 순간입니다)

stat 변수가 volatile 키워드 변수라면 할당이 원자성을 가지게 되어 문제가 발생하지 않습니다.

syncronized랑 다를게 없네? 아닙니다.
아래 코드를 보면

int val = stat + 10;

이 코드는 val에 volatile 선언을 해 둬도, 멀티 스레딩 시 위험합니다.
분명 스레드의 접근 순서에 따라 val의 값을 어떤 스레드는 10을 더한값을 가져가기도 하고, 어떤 스레드는 10을 더하지 않은 값을 읽기도 할것입니다.
위 코드의 작업은 stat을 int로 캐스팅하고 10을 더하고 다시 val에 할당하는 3가지의 작업을 거치기 때문이죠.

syncronized는 이럴 때 작업자체를 원자화해버립니다.
volatile이 할 수 없는 일이죠.

1.5이전버전 유의

volatile 사용시에는 자바 버전에 주의해야 합니다.
1.5 이전 버전에서는 정상 지원이 되지 않고 있거든요. 그 이후라면 안전하게 보장해준다고 합니다.


336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

* 어노테이션(Annotation) 이란 ?

- 클래스나 메소드 등의 선언 시에 @ 를 사용하는 것을 말한다.

참고로 클래스, 메소드, 변수 등 모든 요소에 선언할 수 있다.

메타 데이터(Metadata) 라고도 불리며, JDK 5 부터 등장했다고 한다.



* 언제 사용할까?

- 컴파일러에게 정보를 알려주거나

- 컴파일 할 때와 설치 시의 작업을 지정하거나

- 실행할 때 별도의 처리가 필요할 때



* 일반적으로 사용가능한 Annotation 3가지 (JDK 6 기준)


(1) @Override

- 해당 메소드가 부모 클래스에 있는 메소드를 오버라이드 했다는 것을 명시적으로 선언한다.

자식 클래스에 메소드가 여러 개 있을 때 어떤 메소드가 오버라이드 된 것인지

쉽게 알 수 있고, 깜빡하고 빼먹은 매개변수가 있는지 컴파일러에게 알려달라고 생각하면 된다.

이클립스 같이 편리한 개발툴을 사용하다 보면 자주 볼수 있는 키워드다.


(2) @Deprecated

- 더 이상 사용되지 않은 클래스나 메소드 앞에 추가해준다.

'그냥 지워버리면 되는 거 아닌가?' 라고 생각할 수 있지만 여러 사람이 개발하는 프로젝트에서

갑자기 있던 걸 훅 지워버리면... 욕 엄청 먹을 수 있다ㅋ 이런 알림을 통해 서서히 지우자.

나의 경우 안드로이드 개발할 때 API 문서에서 이 키워드를 몇 번 본 적이 있다.


(3) @SuppressWarnings

- 프로그램에는 문제가 없는데 간혹 컴파일러가 경고를 뿜을 때가 있다.

내 성격상 경고 뿜어대는 게 은근 신경쓰이고 거슬릴 때가 있다. 

그럴 때는 이 Annotation을 추가해서 컴파일러에게 

'나도 알고 있고 일부러 그런 거다 그러니 좀 닥치고 있어'

라고 말해주는 거라고 생각하면 된다. 하지만 거슬린다고 너무 남용하진 말자.


- 참고로 다른 Annotation 과 다르게 속성값을 지정해 줄 수도 있다.

ex) @SuppressWarnings("deprecation"), @SuppressWarnings("serial")

개발하다보면 특히 저 serial 어쩌구 저쩌구를 자주 보는데 그럴 때는

http://blog.naver.com/fochaerim/70105895049 여기 링크를 통해 해결하자.



* Meta Annotation

- Annotation 을 선언할 때 사용한다. 

메타 어노테이션은 다음과 같이 4가지가 존재하고 반드시 모두 사용할 필요는 없다.


(1) @Target 

- 어떤 것에 어노테이션을 적용할 지 선언할 때 사용한다. 적용 가능 대상은 아래와 같다.


요소 타입 

대상 

CONSTRUCTOR 

생성자 선언시 

FIELD 

enum 상수를 포함한 필드값 선언시 

LOCAL_VARIABLE 

지역 변수 선언시 

METHOD 

메소드 선언시 

PACKAGE 

패키지 선언시 

PARAMETER 

매개 변수 선언시 

TYPE 

클래스, 인터페이스, enum 등 선언시 


(2) @Retention

- 얼마나 오래 어노테이션 정보가 유지되는 지를 선언한다.


대상

SOURCE

어노테이션 정보가 컴파일시 사라짐 

CLASS 

 클래스 파일에 있는 어노테이션 정보가 컴파일러에 의해 참조 가능함.

 하지만 가상 머신에서는 사라짐.

RUNTIME 

실행시 어노테이션 정보가 가상 머신에 의해서 참조 가능 


(3) @Documented

- 해당 어노테이션에 대한 정보가 JavaDocs(API) 문서에 포함된다는 것을 선언한다.


(4) @Inherited

- 모든 자식 클래스가 부모 클래스의 어노테이션을 사용할 수 있다는 것을 선언한다.


+ 추가로 @interface 도 존재한다.

이 어노테이션은 어노테이션을 선언할 때 사용한다.



솔직히 메타 어노테이션은 당최 무슨 말인지 잘 이해가 안 갔다.

이럴 때는 코드를 보는 게 더 빠르니 예제 코드 ㄱㄱ~

1
2
3
4
5
6
7
8
9
10
11
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAnnotation {
    public int number();
    public String text() default "This is first annotation"
}

@interface로 선언했으니 이제 @UserAnnotation 으로 사용 가능한

어노테이션이 하나 만들어진 셈이다.


이제 만든 것을 사용해보고, 어노테이션에 선언한 값들을 확인해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@UserAnnotation(number=0)
public class UserAnnotationSample
{
    @UserAnnotation(number=1)
    public static void main(String[] args) {
        UserAnnotatonSample sample = new UserAnnotation();
        sample.checkAnnotations(UserAnnotationSample.class);
    }
 
    @UserAnnotation(number=2)
    public void annotationSample1() {
    }
    @UserAnnotation(number=3, text="second")
    public void annotationSample2() {
    }
    @UserAnnotation(number=4, text="third")
    public void annotationSample3() {
    }
 
    public void checkAnnotations(Class useClass){
        Method[] methods = useClass.getDeclaredMethods();
        for ( Method tempMethod:methods ){
            UserAnnotation annotation = tempMethod.getAnnotation(UserAnnotation.class);
            if ( annotation != null ){
                int number = annotation.number();
                String text = annotation.text();
                System.out.println(tempMethod.getName() + "() : number=" + number + " text=" + text);
            } else {
                System.out.println(tempMethod.getName() + "() : annotation is null");
            }
        }
    }
}

바로 위의 checkAnnotations() 에서

Class, Method 라는 것은 Java 의 Reflection 이라는 API 에서 제공하는 클래스들이라고 한다.


그리고 결과 값은 아래와 같이 얻을 수 있다.


checkAnnotations() : annotation is null

annotationSample1() : number=2 text=This is first annotation

annotationSample2() : number=3 text=second

annotationSample3() : number=4 text=third

main() : number=1 text=This is first annotation



* Annotation 은 상속이 안 된다.

- enum 클래스가 상속을 지원하지 않듯이 Annotation 도 마찬가지로 상속이 안 되므로 확장이 불가능하다. 



어노테이션을 직접 만들 일은 거의 없지만...(시스템, 프레임웍 개발자들은 종종사용한다. 최근에  사용율이 높아지고 있다)

그래도 상위의 Annotation 3개는 꼭 알아두자 - @Override, @Deprecated, @SuppressWarnings

나같이 초보 개발자도 자주 마주치는 키워드라서 그리 생소하지 않은 요소이다.

+ Recent posts