2019. 7. 10. 16:25

1. 조사 이유 

  Task의 내용에 따라 실행 순서가 뒤죽 박죽이어야 의도된 결과를 얻지 못함

 

2. 요약

  1) Task

     task의 본문은 Configuration 단계이다 (맨 먼저 실행된다.)

    doFirst, doLast를 Execute 단계이다.

  즉 Task는 configuration > execute 순으로 하고 task들과는  dependsOn을 통해 순서를 정해 주어야 된다. (아니면 동시에 증분 실행된다.)

 * from 에 task를 주는 그런 방법도 존재한다.

 

   2) gradle 파일 실행 순서

    setting.gradle > build.gradle 순으로 실행 된다.

    setting.gradle <== 단순 설정들만 모아서 놓은 것이다. 그래서 이름도 setting이다.

 

3. 빌드시 기본 Task

4. 원문

들어가면서

Gradle이 5.0이 되면서 rc를 벗고 공식적으로 Kotlin을 빌드스크립트로 지원하기 시작했습니다. 스프링 프레임워크 또한 5에서 공식적으로 Kotlin을 지원하고 있습니다. 아직 gradle을 사용해 보지 않았다면 이제는 한번쯤 자세히 들여다 보기에 적절한 시점이라 생각합니다.

이에 Kotlin DSL을 기반으로 글의 작성 시점 기준 최신 버전인 5.1로 Gradle을 정리해 봅니다. 참고할 내용은 Gradle 공식 사이트의 Authoring Gradle Builds입니다. 오늘은 Understanding the Build Lifecycle를 읽고 요약합니다.

빌드 단계

Gradle은 3단계의 독립적인 과정을 갖고 있다.

초기화(Initialization)

Gradle은 단일 혹은 멀티 프로젝트 빌드를 지원한다. 이 단계에서는 빌드에 참여할 프로젝트를 확정하고 Project의 객체를 생성한다.

설정(Configuration)

이 단계에서 Project의 설정이 이뤄진다. 이 때 빌드에 포함된 모든 Project의 buildscript가 실행된다.

실행(Execution)

설정 단계에서 생성하여 구성한 Task의 잡합을 결정한다. 이 집합은 gradle 명렁으로 전달한 인자인 Task의 이름을 기준으로 결정된다. 이후 Gralde은 선택된 Task들을 실행할 것이다.

Settings 파일

Gradle은 buildscript 파일 외에도 설정과 관련된 파일도 있다. 관례에 따라 settings.gradle에 설정 정보를 선언한다. 추후 뒤에서 어떻게 Gradle이 설정 파일을 찾는지 알아본다.

설정 파일은 초기화 단계에서 실행된다. 멀티 프로젝트라면 반드시 settings.gradle 파일을 프로젝트 구조의 최상위인 루트에 두어야 한다. 멀티 프로젝트 빌드 시 설정 파일이 어떤 프로젝트가 포함되는지를 정의하기 때문에 필요하다. (자세한 건 Authoring Multi-Project Builds를 참조한다.) 단일 프로젝트의 빌드 시 설정 파일은 옵션이다. 멀티프로젝트에 포함될 프로젝트를 정의하는 것 외에도, 빌드스크립트의 클래스패스에 라이브러리를 포함시킬 수도 있다. (자세한 건 Organizing Gradle Projects 참조한다.)

우선 단일 프로젝트 예제를 통해서 스크립트의 실행 과정을 살펴보자

   
   

다음은 실제 출력 결과이다. 위에서 설명했듯이 초기화 단계의 첫 시작으로 설정 스크립트의 내용이 제일 먼저 출력된다. 이후 설정단계에서 buildscript를 읽으면서 스크립트에 선언된 두 줄의 문장이 실행된다. 이후 test, testBoth의 Action이 순차적으로 실행된다.

   

buildscript의 속성이나 메서드 호출이 모두 Project에 위임한 것처럼, 설정 스크립트도 속성이나 메서드 호출을 모두 Settings 객체에 위임한다. 자센한 내용은 Settings 클래스의 API 문서를 참조한다.

멀티 프로젝트 빌드

멀티 프로젝트 빌드란 한 번의 실행을 한 개 이상의 프로젝트를 빌드하는 경우를 의미한다. settings 파일에 멀티 프로젝트에 포함할 하위 프로젝트를 선언해야 한다. 멀티 프로젝트의 설정과 관련하여 자세한 내용은 Authoring Multi-Project Builds에서 살펴본다.

프로젝트 위치

멀티 프로젝트의 빌드는 항상 단일한 최상위 루트로부터 트리형태로 구성된다. 트리의 각 요소가 프로젝트이다. 멀티프로젝트 빌드 트리에서 각 프로젝트는 자신을 나타내는 경로를 갖는다. 대부분의 경우에는 물리적인 프로젝트 경로가 사용된다. (물론 다르게 구성할 수 있다.) 프로젝트의 트리는 settings.gradle에서 구성한다. 기본적으로 이 파일의 위치는 루트 프로젝트로 하지만 수정할 수 있다.

트리 구성하기

설정 파일에 프로젝트 트리를 구성하는 방법은 2가지가 있따. 1. 계층형 2. 단층형

계층형 레이아웃

   

include 메서드는 프로젝트의 경로를 인자로 받는다. 물리적인 파일 경로가 프로젝트의 경로가 된다. 예를 들어 services:api는 루트 프로젝트를 기준으로 services/api의 상대 경로에 대응된다. 프로젝트는 마지막 잎노드만 명시하면 된다. 예를 들어 services:hotels:api는 3개의 프로젝트 services, services:hotles, services:hotels:api를 포함한다. 자세한 예는 Settings.include(java.lang.String[])를 참고한다.

단층형 레이아웃

   

includeFlat 메서드는 디렉토리의 이름을 인자로 받는다. 프로젝트에 포함할 디렉토리가 반드시 루트 프로젝트의 자식이어야 한다. 이 디렉토리 위치는 멀티 프로젝트 트리에서 루트 프로젝터의 하위 프로젝트로 간주된다.

프로젝트 트리 요소 수정

설정 파일에 정의된 멀티 프로젝트 트리는 프로젝트 서술자라 불리는 것을 개념을 통해서 통해 구성한다. 설정파일에서 서술자를 언제든지 수정 가능하다. 예를 들어 서술자에 접근하는 방법은 다음과 같다.

   

서술자를 통해서 프로젝트의 이름, 경로 빌드 파일도 수정가능하다.

   

자세한 내용은 API 문서에서 ProjectDescriptor를 참조한다.

초기화

Gradle은 어떻게 단일 프로젝트인지 멀티 프로젝트인지 아는가? 만약 멀티 프로젝트 빌드를 설정 파일이 있는 위치에서 실행했다면 어렵지 않다. 하지만 Gradle은 하위 프로젝트 위치에서도 스크립트의 실행을 할 수 있다. 이러한 경우 Gradle은 settings.gradle.kts 파일을 찾기 위해 다음의 작업을 수행한다.

  • 현재 디렉토리 레벨에서 찾는다.
  • 그렇지 않으면 부모 디렉토를 찾는다.
  • 찾지 못한 경우 단일 프로젝트로 실행한다.

설정 파일을 찾으면 Gradle은 현재 프로젝트가 멀티프로젝트 계층에 포함되는지 확인한다. 포함되어 있으면 멀티프로젝트로 그렇지 않으면 빌드는 단일 프로젝트로 진행된다. 이러한 과정의 목적은 무엇인가? Gradle 멀티 프로젝트에 포함된 하위 프로젝트 여부를 아는 것이 중요하기 때문이다. 하위 프로젝트라면 하위 프로젝트에 필요한 부분만 빌드한다. 하지만 전체 프로젝트의 설정은 필요로 한다. (자세한 내용은 Authoring Multi-Project Builds를 참고한다.).

만약 현재 프로젝트가 settings.gradle.kts를 가지고 있다면 빌드는 언제는 다음과 같이 진행된다.

  • 단일 프로젝트 빌드: settings.gradle.kts에서 멀티 프로젝트의 계층을 구성하지 않았을 경우
  • 멀티 프로젝트 빌드: settings.gradle.kts에서 멀티 프로젝트 계층을 구성한 경우

settings.gradle.kts을 자동으로 찾는 로직은 물리적으로 계층형 혹은 단층형 레이아웃 구조의 멀티 프로젝트일 때 이뤄진다. 단층형 레이아웃이라면 반드시 추가로 위에서 보여준 프로젝트의 명명 관례를 추가로 따라야 한다. Gradle은 멀티 프로젝트 빌드시 임의의 물리적인 레이아웃을 지원한다. 이 경우에는 설정 파일이 있는 위치에서 반드시 빌드를 실행해야 한다. 루트 경로에서 부분적으로 빌드하는 것과 관련해서 Running tasks by their absolute path를 참조한다.

Gradle은 빌드에 포함된느 모든 프로젝트에 대하여 Project 객체를 생성한다. 멀티 프로젝트 빌드에서 각 객체는 Settings 객체(와 루트 프로젝트)에서 구체화된다. 모든 프로젝트는 자신의 디렉토리명과 동일한 이르름을 기본으로 갖는다. 루트 프로젝트가 아니라면 자신의 부모 프로젝트를 갖기 마련이고 자신 프로젝트를 갖는 경우도 있다.

Configuration and execution of a single project build

단일 프로젝트의 설정과 실행

단일 프로젝트의 경우 초기화 이후 단계가 간단하다. 초기화 단계에서 생성한 Project객체를 바탕으로 빌드스크립트가 실행된다. 이 때 Gradle은 커맨드라인에 전달된 Task 들의 이름과 동일한 것을 찾는다. 동일한 이름의 Task를 찾은 경우 전달한 이름 순서대로 실행하되 별도의 빌드로 실행된다. 멀티프로젝트의 설정과 실행과 관련된 자세한 내용은 Authoring Multi-Project Builds를 참고한다.

빌드스크립트의 라이프사이클에 반응하기

빌드스크립트에서 라이프 사이클의 진행 과정을 전달받는 2가지 방법이 있다.

  1. 특정 라이프 사이클의 리스너 인터페이스 구현
  2. 특정 라이프 사이클이 호출될 때 클로저 구문을 전달

아례 예제는 클로저를 사용하는 경우이다. 리스터 인터페이스 구현은 API 문서를 참조한다.

프로젝트 평가 시점

프로젝트의 평가 전후에 알람을 받을 수 있다. 이 알람은 빌드스크립트에 모든 정의에 대하여 추가적인 설정을 한번 수행할 때 사용하거나 커스텀 로그나 프로파일링이 필요할 때 사용하기 좋다. 다음 예제는 hasTests속성을 참으로 갖는 모든 프로젝트에 테스트용 Task를 포함하는 예제이다.

   

위 예제에서는 Project.afterEvaluate()를 사용하여 프로젝트가 평가될 때 실행할 클로저를 추가했다.

항상 어떤 프로젝트라도 평가될 때 알람을 받는 방법도 있다. 이는 프로젝트의 평가와 관련된 커스텀한 로깅이 필요할 때 유용하다. afterProject 알람은 프로젝트의 평가의 성공여부와 상관없이 항상 실행된다는 점이 중요하다.

   

다음은 gradle -q test로 위 스크립트를 실행한 결과이다.

   

Task의 생성 시점

프로젝트에 Task를 추가한 후 알람을 받을 수도 있다. 이는 기본값을 추가하거나 Task를 빌드 파일에서 사용하기 전에 필요한 행위를 등록할 때 사용할 수 있다. 다음 예는 각 Task가 생성될 때 srcDir 속성을 정의한다.

   

이러한 이벤트를 전달 받기 위해 Action TaskContainer에 등록할 수도 있다.

Task 실행 그래프 준비된 시점

Task의 실행 그래프가 준비되었을 때 알람을 받을 수 있다. (추가로 Configure by DAG를 참조).

이러한 이벤트를 전달 받기 위해 TaskExecutionGraphListener TaskExecutionGraph에 등록할 수도 있다.

Task 실행 시점

Task의 실행 전후 시점에 알람을 받을 수 있다.

다음 예에서는 Task의 시작과 종료에 각각 로깅을 한다. afterTask 알람은 Task의 성공여부와 상관없이 실행된다는 점이 중요하다.

 

Author woojin.joe

LastMod 2019-01-14 (daf0d45)

 

# 참고

https://not-for-me.io/post/gradle/04-build-lifecycle/

 

Posted by citrine
2018. 10. 25. 11:35

1. 조사배경

  특정 구간에 로그를 찍지 않아서 현재 프로그램이 동작하고 있는지 없는지 알수가 없다



2. 알고 싶은것

  해당 프로세스의 특정 변수를 모니터링 해서 그 값이 변하는지 확인 !! 즉 특정 변수의 값을 보고 싶다.



3. 도구

  jmap ==> java dump 생성

  jhat ==> dump 를 분석하여 Web 서버를 띄워준다.



4. 분석 방법

   1) 분석을 하려는 PID를 가져온다.

       ex) jps를 하여 PID를 가져옴   


   2) java heap 메모리 덤프를 만든다.

       ex) jmap -dump:format=b,file=heap.hprof 8680  ==> 8680 프로세스를 binary 형식의 파일은 heap.hprof로 해서 덤프를 생성한다.


   3) jhat을 사용하여 분석한다. 

       ex) jhap heap.hprof  [enter]  ==> 자동으로 웹서버를 띄워준다.


Reading from heap.hprof...

Dump file created Thu Oct 25 10:58:47 KST 2018

Snapshot read, resolving...

Resolving 788140 objects...

Chasing references, expect 157 dots.............................................................................................................................................................

Eliminating duplicate references.............................................................................................................................................................

Snapshot resolved.

Started HTTP server on port 7000

Server is ready.


    4) 이제 해당 서버에 웹 브라우져로 접속만 하면 된다.

        ex) chrom 으로 http://{웹서버IP}:7000 하여 접속한다.

     


   5) 예제

     PITargetDBManager>m_arrScanThread>currentTable >inspectedRow 의 값을 보고 싶을때

    

   1) 웹에 접속하고

   2) 첫 화면에서  PITargetDBManager를 찾고 링크 들어간다. (처음 보이는 것)

   3) 실행 인스턴스를 찾아서 링크 들어간다. (보통 com.somansa.manager.PITargetDBManager@0xXXXXXX 되어 있다.)

    3) m_arrScanThread로 검색하여 링크 들어간다.

    4) 배열 이므로 아무 인스턴스 링크를  들어간다.           ( com.somansa.manager.DBDiscoverScanThread@0xXXXXXXXXXX)

    4) currentTable  찾고 링크 들어간다.

   5) inspectedRow를 찾고 값을 확인한다.

Posted by citrine
2018. 10. 16. 17:01

1. 조사 배경

  - Eclipse에서는 DomParsing이 잘 되다가 Runnerble jar로 묶은 뒤는 이상하게 느림



2. 분석

  - 잘되는놈(Eclipse에서 1번 옵션으로 Export)

  - 안되는 놈 (Eclipse에서 2번 옵션으로 Export) 후 class load output 분석


  (verbose:class 옵션으로 output 만듦)


3. 증상 

   나머지 ClassLoad는 똑같은데 SecuritySupoort 하는 부분만 조금 다름


  1) 안 되는 놈

[Loaded org.apache.xerces.parsers.SecuritySupport from rsrc:./]

[Loaded org.apache.xerces.parsers.SecuritySupport$4 from rsrc:./]

[Loaded org.apache.xerces.parsers.SecuritySupport$1 from rsrc:./]

[Loaded org.apache.xerces.parsers.SecuritySupport$2 from rsrc:./]

[Loaded org.apache.xerces.parsers.SecuritySupport$3 from rsrc:./]

[Loaded org.apache.xerces.parsers.SecuritySupport$7 from rsrc:./]

[Loaded org.apache.xerces.parsers.SecuritySupport$6 from rsrc:./]


  2) 잘 되는 놈

Loaded org.apache.xerces.parsers.SecuritySupport from file:/F:/Somansa/privacyi/lib/PIDBScanCore.jar]

[Loaded org.apache.xerces.parsers.SecuritySupport$4 from file:/F:/Somansa/privacyi/lib/PIDBScanCore.jar]

[Loaded org.apache.xerces.parsers.SecuritySupport$1 from file:/F:/Somansa/privacyi/lib/PIDBScanCore.jar]

[Loaded org.apache.xerces.parsers.SecuritySupport$2 from file:/F:/Somansa/privacyi/lib/PIDBScanCore.jar]

[Loaded org.apache.xerces.parsers.SecuritySupport$7 from file:/F:/Somansa/privacyi/lib/PIDBScanCore.jar]

[Loaded org.apache.xerces.parsers.SecuritySupport$6 from file:/F:/Somansa/privacyi/lib/PIDBScanCore.jar]

  


4. 다시 조사 

  keyword : xerces SecuritySupport


  답나옴 ㅠ.ㅠ !!!


5. 결론 

   java 실행시 다음 옵션을 넣으면 됨

-Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XIncludeAwareParserConfiguration



ps) 세상에는 놀라운 개발자가 많다 ㅠ.ㅠ


# 참고

https://support.mulesoft.com/s/article/Blocked-thread-on-finding-org-apache-xerces-parsers-DOMParser-service-provider



#원문 

Sometimes threads become blocked trying to locate a org.apache.xerces.parsers.DOMParser service provider. Threads become blocked because the default JAXP implementation tries to search through all zip/jar files in classpath to find a service provider which is time/IO/CPU consuming. This article provides a workaround to bypass that process by telling the parser to use the default/fallback implementation directory without searching through the entire classpath.

Feb 26, 2018Knowledge

SYMPTOM

From thread dumps, threads with stack trace similar to the following are seen:
java.lang.Thread.State: BLOCKED (on object monitor) 
at java.util.zip.ZipFile.getEntry(ZipFile.java:308) 
- waiting to lock <0x00000005e0b124e8> (a java.util.jar.JarFile) 
at java.util.jar.JarFile.getEntry(JarFile.java:240) 
at java.util.jar.JarFile.getJarEntry(JarFile.java:223) 
at sun.misc.URLClassPath$JarLoader.getResource(URLClassPath.java:1005) 
at sun.misc.URLClassPath$JarLoader.findResource(URLClassPath.java:983) 
at sun.misc.URLClassPath.findResource(URLClassPath.java:188) 
at java.net.URLClassLoader$2.run(URLClassLoader.java:569) 
at java.net.URLClassLoader$2.run(URLClassLoader.java:567) 
at java.security.AccessController.doPrivileged(Native Method) 
at java.net.URLClassLoader.findResource(URLClassLoader.java:566) 
at java.lang.ClassLoader.getResource(ClassLoader.java:1096) 
at java.lang.ClassLoader.getResource(ClassLoader.java:1091) 
at java.lang.ClassLoader.getResource(ClassLoader.java:1091) 
at org.mule.module.launcher.MuleApplicationClassLoader.getResource(MuleApplicationClassLoader.java:144) 
at java.net.URLClassLoader.getResourceAsStream(URLClassLoader.java:232) 
at org.mule.module.launcher.application.CompositeApplicationClassLoader.getResourceAsStream(CompositeApplicationClassLoader.java:150)
at org.apache.xerces.parsers.SecuritySupport$6.run(Unknown Source) 
at java.security.AccessController.doPrivileged(Native Method) 
at org.apache.xerces.parsers.SecuritySupport.getResourceAsStream(Unknown Source) 
at org.apache.xerces.parsers.ObjectFactory.findJarServiceProvider(Unknown Source) 
at org.apache.xerces.parsers.ObjectFactory.createObject(Unknown Source) 
at org.apache.xerces.parsers.ObjectFactory.createObject(Unknown Source) 
at org.apache.xerces.parsers.DOMParser.<init>(Unknown Source) 
at org.apache.xerces.parsers.DOMParser.<init>(Unknown Source) 
at org.apache.xerces.jaxp.DocumentBuilderImpl.<init>(Unknown Source) 
at org.apache.xerces.jaxp.DocumentBuilderImpl.<init>(Unknown Source) 
at org.apache.xerces.jaxp.DocumentBuilderFactoryImpl.setFeature(Unknown Source) 
at org.mule.util.XMLSecureFactories.createDocumentBuilderFactory(XMLSecureFactories.java:69) 
 

CAUSE

org.apache.xerces.parsers.DOMParser uses the typical JAXP implementation to find the service provider using the following steps:
  1. Query the system property for factoryId using System.getProperty
  2. Read $java.home/lib/propertiesFilename file for factoryId
  3. Read META-INF/services/factoryId file
  4. Use the fallback classname
By default, steps 1 and 2 do not locate the service provider. So the parser begins searching for these under the classpath to read META-INF/service/factoryId to locate an implementation. This task is IO/CPU/time consuming. 

SOLUTION

In the case of org.apache.xerces.parsers.DOMParser;the following will be the determined values:
  • factoryIdorg.apache.xerces.xni.parser.XMLParserConfiguratio

  • fallbackClassName: org.apache.xerces.parsers.XIncludeAwareParserConfiguration
To avoid the whole classpath search, we can set system property with the fallback value directly, like: 
-Dorg.apache.xerces.xni.parser.XMLParserConfiguration=org.apache.xerces.parsers.XIncludeAwareParserConfiguration


Posted by citrine
2018. 9. 10. 14:43

1. 조사 배경

  - 회의 Class가 순환 참조되는 구조임 그래서 Gson을 못 씀


2. 해결

  순환 참조되는 필드에 "transient" 한정자를 준다.


3. 예제

  class BagOfPrimitives {

     private int value1 = 1;
     private String value2 = "abc";
     private transient int value3 = 3;
     BagOfPrimitives() {
      // no-args constructor
  }
}

  1) Class에 순환 참조되는 필드 "value3"가 존재한다면 transient 를 넣는다.



4. 참조 

https://sites.google.com/site/gson/gson-user-guide#TOC-Finer-Points-with-Objects

Posted by citrine
2018. 5. 29. 11:19

1. 조사배경

  - 기존에 Ant로 프로젝트가 되어 있는데 좀 더 편하게 환경 구성을 위해서 Maven으로 이전을 계획함


2. 참고

https://gs.saro.me/#!m=elec&jn=744



3. 원문

보통 서블릿이나 스프링등에서 메이븐을 사용하신다면, 서버에 메이븐 디플로이 할때 별도의 작업없이 서버쪽도 적용되기 때문에 별 문제가 없지만, 
최근 단일 파일로 뽑아야할 일이 생겨 추가된 dependency 의 jar를 포함하여 빌드해야할 일이 생겼습니다.


아래와같이 빌드를 구성해주시면됩니다.
  1. <build>
  2. <plugins>
  3. <!-- dependency 들을 모두 포함하기위한 -->
  4. <plugin>
  5. <artifactId>maven-assembly-plugin</artifactId>
  6. <version>2.6</version>
  7. <configuration>
  8.  
  9. <descriptorRefs>
  10. <descriptorRef>jar-with-dependencies</descriptorRef>
  11. </descriptorRefs>
  12.  
  13. <!-- 이건 그냥 jar의 메인 클래스 지정하는것입니다. -->
  14. <!-- 그냥 올려봤습니다... -->
  15. <archive>
  16. <manifest>
  17. <mainClass>패키지를 포함한 클래스 전체이름</mainClass>
  18. </manifest>
  19. </archive>
  20. </configuration>
  21.  
  22. <executions>
  23. <execution>
  24. <id>make-assembly</id>
  25. <phase>package</phase>
  26. <goals>
  27. <goal>single</goal>
  28. </goals>
  29. </execution>
  30. </executions>
  31. </plugin>
  32. <!-- 자바 버전을 1.8로 / 문자셋을 utf-8로 고정하기 위한 -->
  33. <plugin>
  34. <groupId>org.apache.maven.plugins</groupId>
  35. <artifactId>maven-compiler-plugin</artifactId>
  36. <configuration>
  37. <source>1.8</source>
  38. <target>1.8</target>
  39. <encoding>UTF-8</encoding>
  40. </configuration>
  41. </plugin>
  42. </plugins>
  43.  
  44. </build>


Posted by citrine
2018. 5. 29. 11:17

1. 조사배경

  - 기존에 Ant로 프로젝트가 되어 있는데 좀 더 편하게 환경 구성을 위해서 Maven으로 이전을 계획함


2. 참고

https://gs.saro.me/#!m=elec&jn=745



3. 원문

1. 로컬 파일로 포함하기 (스코프 시스템)
pom.xml 의 dependencys 에 아래와 같이 추가해줍니다.
  1. <dependency>
  2. <groupId>그룹ID</groupId>
  3. <artifactId>아티펙트ID</artifactId>
  4. <version>버전</version>
  5. <scope>system</scope>
  6. <systemPath>${basedir}/lib/saro.jar</systemPath>
  7. </dependency>
위와 같이 입력하셨다면 프로젝트경로/lib/saro.jar 가 추가됩니다.
하지만 이 방법은 문제점이 하나 있습니다.
문제점
- https://gs.saro.me/#!m=elec&jn=744 식으로 jar를 포함하여 빌드한다면 스코프가 system 이라 포함되지 않습니다.
그럴경우 아래 2번 리포지토리 등록을 통한 포함을 참고해주시기 바랍니다.


2. 리포지토리를 통하여 포함하기
pom.xml 에 아래와 같이 리포지토리를 추가해줍니다.
  1. <repositories>
  2. <repository>
  3. <!-- id나 name은 아무거나 설정하시면됩니다. -->
  4. <id>프로젝트아이디</id>
  5. <name>이름</name>
  6. <!-- 정확히 말하면 url을 등록하는겁니다. ${basedir}는 현재경로 -->
  7. <url>file://${basedir}/lib</url>
  8. </repository>
  9. </repositories>
위와 같이 적으면 file://${basedir}/lib프로젝트경로/lib를 리포지토리로 설정하는 것입니다.
이것을 공유하고싶다면 웹에올리고 주소를 작성하는 방법도 있습니다.
즉, URL 형태를 가지고 있기 때문에 http나 ftp 등에도 활용할 수 있습니다.
pom.xml 에 dependencies 내에 추가해줍니다.
  1. <dependency>
  2. <groupId>그룹ID</groupId>
  3. <artifactId>아이팩트ID</artifactId>
  4. <version>버전</version>
  5. </dependency>
예를들어 아래와 같이 작성하면
  1. <dependency>
  2. <groupId>SARO</groupId>
  3. <artifactId>SARO_MOE</artifactId>
  4. <version>2.3.1</version>
  5. </dependency>
맨위 추가된 file://${basedir}/lib 에 따라 프로젝트경로/lib/SARO/SARO_MOE/2.3.1/SARO_MOE-2.3.1.jar 가 자동으로 추가됩니다.


Posted by citrine
2018. 5. 25. 13:59

1. 조사 배경

   java Application이 Linux 에서만 동작함

   내 PC는 Windows임

   어쩔수 없이 원격 디버깅을 해야됨


2. 디버깅 방법

  1) 서버에서 java Application을 실행한다. (다음옵션을 추가하여 실행)

        ex) java -d64 -Xdebug -Xrunjdwp:transport=dt_socket,address=8998,server=y -jar myjar.jar

# 명령을 실행하면 해당 명령은 Listen 상태가됨


    2) Local의 Eclipse를 해당 서버로 접속 한다.

        A. Debug Configurations에 들어간다.

         B. Remote Java Application에 들어가서 설정을 새롭게 추가한다.

         C. Connect 및 Common을 추가한다.(서버 IP 및 port를 설정한다.)

                Connect : 서버 IP 및 port를 설정

                Common : debug 옵션을 check 한다.

         D. 적용을 누르고

         E. Debug 버튼을 누른다. (끝)







Posted by citrine
2018. 4. 25. 16:34

1. 조사 배경

  회사에서 누군가 발표 하는 듯한 낌새를 채고 선행으로 한번 봐 보았다.


2. 참고

http://multifrontgarden.tistory.com/124 ==> 그중에 제일 이해하기 편한 곳이라서 복사함


3. 상세 내용

ava8에서 뭐가 추가됐나요? 라고 물으면 가장 먼저 들리는 대답은 십중팔구 '람다와 스트림이요' 일것이다. 당연히 맞는 말이고 틀린 답은 아니지만 람다와 스트림이 왜 추가됐는지를 알아야하는데, 자바가 기존에 없던 문법까지 만들어가면서 이런것들을 추가한건 함수형 프로그래밍을 받아들이기 위해서다. 그래서 Java8을 공부하고자하는 사람이라면 그냥 단순히 '이번에 추가된 람다랑 스트림공부해야지'가 아니라 함수형 프로그래밍을 공부해야한다. 이는 프로그래밍 자체의 패러다임이 변하기때문에 그저 추가된 문법, 추가된 API만 공부하면 되는 수준이 아니라 프로그래밍 방식 자체, 문제 해결을 위한 사고방식 자체를 기존에서 탈피해야함을 의미한다. 해보니 기존에도 못하던걸 새로운걸 받아들이려고하니까 너무 어렵다;; 그래서 공부한것들을 정리해보고자 Java8의 포스팅을 시작한다.


1. 람다 표현식(Lambda Expression)

람다의 핵심은 지울수 있는건 모두 지우자는 것이다. 모든걸 컴파일러의 추론에 의지하고 코드로 표현하는건 다 없애버려 코드를 간결하게 만드는 것이다.


interface Movable{
void move(String str);
}

class Car implements Movable{
@Override
public void move(String str) {
System.out.println("gogo car move" + str);
}
}
Movable movable = new Movable() {
@Override
public void move(String str) {
System.out.println("gogo move move" + str);
}
};

Movable 인터페이스를 구현하고있다. 따로 Movable 인터페이스를 구현하는 클래스를 만들어 객체화하거나 재사용성이 없다면 그 자리에서 바로 생성하는 익명클래스 객체를 구현해야하는게 기존 방식이다. 어떤 방식을 쓰든 @Override 애노테이션을 제외해도 5줄은 필요하다.

재사용이 필요한 Car 클래스같은경우는 재사용을 위해 클래스로 남겨두고 익명 클래스부분을 람다표현식으로 수정해보자. 먼저 저기서 어떤부분을 지워도될지 한번 생각해보자.


1) 이미 대상타입(Target Type)에서 Movable 이라고 명시했기때문에 new Movable부분은 없어도 컴파일러가 추론할 수 있다.

2) 구현하려고보니까 구현해야할건 move() 메서드밖에 없다. 만약 구현해야할 메서드가 1개뿐이라면 메서드명칭도 없어도 되지않을까

3) 컴파일러가 인터페이스와 메서드를 추론했다면 인자도 추론할 수 있을 것이다. 구현해야할때 인자를 써야하니 인자를 아예 다 지우진 못해도 타입까지는 명시하지않아도 될 것 같다.


이를 토대로 람다표현식으로 고쳐보면

Movable movable1 = (str) -> {
System.out.println("gogo move move" + str);
};

이런 결과가 나온다. 여기서 더 없앨 수 있을게 있는지 생각해보자.


1) 인자가 여러개면 몰라도 1개일땐 괄호가 없어도 되지 않을까

2) 실행구문이 지금처럼 1줄일땐 중괄호가 없어도 되지 않을까

Movable movable2 = str -> System.out.println("gogo move move" + str);

5줄이던 코드가 1줄로 줄어들었다. 람다 표현식은 자바에서 기존에 지원하지않던 문법이 추가되어서 놀라운거지 사실 그 구현 자체가 어렵진않다.


1-1. 함수형 인터페이스(Functional Interface)

위 예제에서 확인한 람다 표현식은 구현해야될 추상 메서드가 1개인 인터페이스를 구현한 것이다. 인터페이스를 구현하고자하는데 어차피 구현해야될 메서드가 1개뿐이니 이름이고 뭐고 다 지워버린것이다. 그럼 메서드가 2개일땐 어떻게 해야할까? 람다로는 지원하지않는다. 람다 표현식으로 구현이 가능한 인터페이스는 오직 추상 메서드가 1개뿐인 인터페이스만 가능하며 그렇기때문에 추상 메서드가 1개인 인터페이스를 부르는 명칭이 추가됐다. 그것이 함수형 인터페이스다. 그럼 가만히 생각해보자. 추상 메서드가 1개뿐인 인터페이스가 있고, 난 이걸 다른곳에서 람다 표현식으로 구현했다. 그런데 시간이 흘러 다른 동료가 해당 인터페이스를 수정할 일이 생겼고, 추상 메서드를 추가하고자한다. 인터페이스에는 당연히 추상메서드의 개수를 제한하는 방법은 없으므로 다른 곳에 람다 표현식이 있는걸 모르고 추상메서드를 추가하는 일이 발생할 수 있다. 열심히 추상 메서드를 추가하고 구현한다음 빌드를 하는데 여기저기서 빌드가 깨지는걸 보면 그 동료(후임자)는 멘탈이 붕괴될 것이다. 이런 불상사를 막기 위해 @FunctionalInterface 애노테이션이 존재한다.


@FunctionalInterface
interface Movable{
void move(String str);
}

@FunctionalInterface 애노테이션은 해당 인터페이스가 함수형 인터페이스라는걸 알려준다. 추상메서드가 1개가 아닐경우에 컴파일에러를 내뿜는다. 아무 생각없이 추상 메서드를 추가하면 이러면 안된다는것을 알려주는 것이다. @Override 애노테이션과 마찬가지로 꼭 해당 애노테이션이 붙지 않아도 람다 표현식으로 구현할 수 있으나 이 인터페이스의 용도를 알리기위해 붙여주는것이 좋다.


1-2. 상태가 없는 객체(Stateless Object)

추상 메서드가 1개인 인터페이스를 구현할 때는 람다 표현식을 이용하면 매우 간결한 코드로 구현할 수 있다는 것을 알았다. 하지만 클래스를 구현할때 클래스에는 메서드(행위)만 존재하란 법은 없다. 경우에 따라서는 인스턴스 필드(상태)가 존재하는 클래스가 있을 수도 있는데 아래같은 클래스를 말한다.


Movable movable = new Movable() {
private int speed;

@Override
public void move() {
System.out.println("gogo move move current speed : " + speed);
}
};

익명객체를 구현할때는 인스턴스 필드를 추가할 수 있다. 하지만 람다는 바로 메서드를 구현해버리니 인스턴스 필드가 들어갈 공간이 없어보인다. 인스턴스 필드를 추가하려면 어떻게 해야할까? 방법은 없다. 메서드와 함수의 가장 큰 차이는 메서드는 객체에 종속되어있다는 것이다. 함수란 인풋(Input)에 의해서만 아웃풋(Output)이 달라져야하는데 메서드는 객체에 종속적이기때문에 인풋이 달라지지않아도 객체의 상태에 따라 결과값이 다를 수 있다. 함수형 프로그래밍에서 함수는 인풋에 의해서만 아웃풋이 달라져야하며 그것을 지원하기위해 람다 표현식으로 구현할때 객체는 상태를 가질 수 없다. 이는 추후에 알아볼 스트림(Stream)을 이용한 병렬(Parallel)적 프로그래밍시 매우 중요한 요소다.


1-3. 행위 파라미터화(Behavior Parameterize)

보통 코드를 짤때 데이터를 매개변수로 전달하고 해당 데이터를 가지고 무언가 행위를 하는 메서드를 구현하기 마련이다. 하지만 데이터를 전달하는 것이 아닌 행위를 전달하게되면 좀 더 유연한 코드가 될 수 있는데 '이게 뭔말인가..'싶다면 예제 코드를 봐보자.


class Fruit{
private String name;
private String color;

Fruit(String name, String color){
this.name = name;
this.color = color;
}

String getName(){
return this.name;
}

String getColor(){
return this.color;
}
}
List<Fruit> extractApple(List<Fruit> fruits){
List<Fruit> resultList = new ArrayList<>();
for(Fruit fruit : fruits){
if("apple".equals(fruit.getName())){
resultList.add(fruit);
}
}

return resultList;
}

List<Fruit> extractRed(List<Fruit> fruits){
List<Fruit> resultList = new ArrayList<>();
for(Fruit fruit : fruits){
if("red".equals(fruit.getColor())){
resultList.add(fruit);
}
}

return resultList;
}

Fruit 클래스가 있고, 해당 객체들의 리스트를 받아 특정 조건으로 추출해내는 메서드 2개다. 메서드구현부분을 잘 보면 if문 외에는 전부 동일한 걸 볼 수 있다. 이걸 행위 파라미터를 이용해 1개의 메서드로 합치면 훨씬 보기가 좋을것 같다.


static List<Fruit> extractFruitList(List<Fruit> fruits, Predicate<Fruit> predicate){
List<Fruit> resultList = new ArrayList<>();
for(Fruit fruit : fruits){
if(predicate.test(fruit)){
resultList.add(fruit);
}
}

return resultList;
}

그래서 합쳤다. 비교하는 행위를 파라미터로 입력받아 if문 내 조건을 하드코딩에서 동적으로 처리하게끔 파라미터가 1개 늘었다. 호출하는 부분은 이렇다.


List<Fruit> fruits = Arrays.asList(new Fruit("apple", "red"), new Fruit("melon", "green"), new Fruit("banana", "yellow"));
List<Fruit> appleList = extractFruitList(fruits, new Predicate<Fruit>() {
@Override
public boolean test(Fruit fruit) {
return "apple".equals(fruit.getName());
}
});

List<Fruit> redList = extractFruitList(fruits, new Predicate<Fruit>() {
@Override
public boolean test(Fruit fruit) {
return "red".equals(fruit.getColor());
}
});

2개의 메서드는 합쳐져서 만족스러운데 호출하는 구문이 지저분해진거같다. 메서드가 2개일때와 마찬가지로 오히려 이제는 호출부분이 return문만 제외하고 똑같은 코드가 반복된다. 그런데 잘 보니 Predicate<T>라는 인터페이스는 뭔진 모르겠지만 추상메서드가 1개뿐인것같다(test). 그럼 람다 표현식으로 고칠 수 있지 않을까?


List<Fruit> appleList = extractFruitList(fruits, fruit -> "apple".equals(fruit.getName()));
List<Fruit> redList = extractFruitList(fruits, fruit -> "red".equals(fruit.getColor()));

코드가 상당히 줄어든걸 볼 수 있다. 행위 파라미터라는 기법 자체는 기존에도 익명 클래스를 이용해 매우 활용되고 있던 기법이다. 자바 개발자라면 안쓸수가 없는 Spring Framework가 익명 클래스를 이용한 행위 파라미터를 적극 활용해 Template Callback Pattern이라는 디자인패턴까지 만들정도로 이미 유용히 사용되던 기법이다. 이것이 함수형 인터페이스와 람다 표현식으로 인해 더욱 간결하게 사용될 수 있는것이다.



출처: http://multifrontgarden.tistory.com/124 [우리집앞마당]

Posted by citrine
2018. 4. 25. 15:49

Thread Pool(스레드 풀) 이란??


오늘은 스레드 풀에대해서 간략하게 알아보자.


간단하다. 스레드를 미리 만들어 놓은 하나의 풀장...이라고 생각하면된다.

군대를 빗대어보면, 전쟁이 나서 사방팔방에서 국지전을 펼친다고 생각해보자.

그때그때 추가병력을 요청할때마다 당신이 지휘관이라면, 1명씩 지원을 보낼텐가???


아니다. 미리 100명의 군인을 섭외해서 다중적으로 발생되는 국지전을 대비해 예비 병력을 갖추고 즉각 국지전에 대응해야한다.


이제 감이 좀 잡혔을 것이라고 본다.


그렇다면 이제 SW적으로 접근해보자.

"스레드"라는 녀석이 생성될 때 컴퓨터 내부적으로 운영체제(OS)가 요청을 받아들여 메모리공간을 확보해주고 

그 메모리를 스레드에게 할당해준다. 스레드는 동일한 메모리영역에서 생성되고 관리되지만, 생성/수거에 드는 비용을 무시할 수 없다.


그렇기 때문에 요청이 들어올 때 마다 스레드를 생성하고 일을 다하면 수거하고 하는 작업은 프로그램 퍼포먼스에 지대한 영향을 줄 수 있다.

따라서 스레드를 미리 만들어 놓는 것이다.



Thread Pool(스레드 풀)의 동작 원리


아래 첨부한 그림을 통해서 확실히 개념을 정립하자!

우리가 만든 어플리케이션에서 사용자로부터 들어온 요청을 작업큐에 넣고 

스레드풀은 작업큐에 들어온 Task일감을 미리 생성해놓은 Tread들에게 일감을 할당한다.

일을 다 처리한 Thread들은 다시 어플리케이션에게 결과값을 리턴한다.




자바에서는 스레드풀을 생성하고 사용할 수 있도록 java.util.concurrent Package에서 ExecutorService 인터페이스와 Executors 클래스를 제공하고 있다. Executors의 다양한 정적 메서드를 통해 ExecutorService 구현객체를 만들어서 사용할 수 있으며, 그것이 바로 스레드 풀이다. 






Thread Pool(스레드 풀) 을 왜 사용해야해???


이미 답은 나왔지만, 크게 2가지만 기억하면 된다.


1. 프로그램 성능저하를 방지하기 위해


매번 발생되는 작업을 병렬처리하기 위해 스레드를 생성/수거하는데 따른 부담은 프로그램 전체적인 퍼포먼스 저하시킨다.

따라서 스레드풀을 만들어 놓는다.


2. 다수의 사용자 요청을 처리하기 위해


서비스적인 측면으로 바라볼 때

특히 대규모 프로젝트에서 중요하다. 다수의 사용자의 요청을 수용하고, 빠르게 처리하고 대응하기 위해 스레드풀을 사용한다.




Thread Pool(스레드 풀)의 단점???


1. 과유불급...너무 많이 만들어 놓았다가 메모리 낭비만 발생.


많은 병렬처리를 예상해서 1억개의 스레드를 만들어 놓았다고 생각해보자. 실제로 100개정도의 요청과 병렬처리를 했다. 그렇다면.. 나머지 스레드들은 아무일도 하지않고 메모리만 차지하는 최악의 경우가 발생될 수 있다.



2. 노는 스레드가 발생될 수 있다.


1번과 비슷하지만 조금 다르다. 

예를 들어 A,B,C 스레드 3개가 있는데, 병렬적으로 일을 처리하는 과정에서 A,B,C 작업완료 소요시간이 다른 경우 스레드 유휴시간 즉

A스레드는 아직 일이 많아서 허덕이고 있는데, B,C는 일을 다하고 A가 열심히 일하는 것을 보고 놀고만 있는 유휴시간이 발생된다.

자바에서는 이를 방지하기 위해 forkJoinPool 을 지원한다. 아래 링크를 통해 알아보자


http://hamait.tistory.com/612




Thread Pool(스레드 풀) 생성/종료


1. 스레드 풀 생성


ExecutorService 구현 객체는 Executors 클래스의 다음 두가지 메소드 중 하나를 이용해 간편하게 생성할 수 있다.

생성방밥에 앞서 알아야 할 개념이 있다.


초기 스레드 수 : ExecutorService 객체가 생성될 때 기본적으로 생성되는 스레드 수

코어 스레드 수 : 스레드가 증가한 후 사용되지 않은 스레드를 스레드 풀에서 제거할 때 최소한으로 유지해야할 수

코어 스레드 수 : 스레드풀에서 관리하는 최대 스레드 수



1, newCachedThreadPool()

초기스레드 수, 코어스레드 수 0개 최대 스레드 수는 integer 데이터타입이 가질 수 있는 최대 값(Integer.MAX_VALUE)

스레드 개수보다 작업 개수가 많으면 새로운 스레드를 생성하여 작업을 처리한다.

만약 일 없이 60초동안 아무일을 하지않으면 스레드를 종료시키고 스레드풀에서 제거한다.



2. newFixedThreadPool(int nThreads)

초기 스레드 개수는 0개 ,코어 스레드 수와 최대 스레드 수는 매개변수 nThreads 값으로 지정,

이 스레드 풀은 스레드 개수보다 작업 개수가 많으면 마찬가지로 스레드를 새로 생성하여 작업을 처리한다.

만약 일 없이 놀고 있어도 스레드를 제거하지 않고 내비둔다.  


newCachedThreadPool(),newFixedThreadPool() 메서드를 사용하지 않고 직접 스레드 개수들을 설정하고 싶다면

직접 ThreadPoolExecutor 객체를 생성하면 된다. 




2. 스레드 풀 종료


스레드 풀에 속한 스레드는 기본적으로 데몬스레드(주 스레드를 서포트하기 위해 만들어진 스레드, 주 스레드 종료시 강제 종료)가 아니기 때문에 main 스레드가 종료되어도 작업을 처리하기 위해 계속 실행 상태로 남아있다. 즉 main() 메서드가 실행이 끝나도 어플리케이션 프로세스는 종료되지 않는다. 어플리케이션 프로세스를 종료하기 위해선 스레드 풀을 강제로 종료시켜 스레드를 해체시켜줘야 한다. 

ExecutorService 구현객체에서는 기본적으로 3개 종료 메서드를 제공한다.


excutorService.shutdown();

 - 작업큐에 남아있는 작업까지 모두 마무리 후 종료 (오버헤드를 줄이기 위해 일반적으로 많이 사용.)


excutorService.shoutdownNow();

 - 작업큐 작업 잔량 상관없이 강제 종료


excutorService.awaitTermination(long timeout, TimeUnit unit);

 - 모든 작업 처리를 timeout 시간안에 처리하면 true 리턴 ,처리하지 못하면 작업스레드들을 interrupt()시키고 false리턴




Thread Pool(스레드 풀)에게 작업시키기


스레드 풀에게 작업을 시키기 전... 작업을 생성시켜야 작업처리를 요청할 수 있다.

작업 생성은 Runnable 인터페이스 or Callable 인터페이스를 구현한 클래스로 작업요청할 코드를 삽입해 작업을 만들 수 있다.

둘의 차이점은 Runnable의 run() 메서드는 리턴값이 없고, Callable의 call() 메서드는 리턴 값이 있다. 

자세한건 doc을 참고하시길!


이제 마지막!! 다왔다... 스레드풀에게 작업을 처리 요청을 하기 위해선 execute(), submit() 2가지 메서드가 있다.


execute();

 - 작업 처리 결과를 반환하지 않는다.

 - 작업 처리 도중 예외가 발생하면 스레드가 종료되고 해당 스레드는 스레드 풀에서 제거된다. 

 - 다른 작업을 처리하기 위해 새로운 스레드를 생성한다.


submit();

 - 작업 처리 결과를 반환한다.

 - 작업 처리 도중 예외가 발생하더라도 스레드는 종료되지 않고 다음 작업을 위해 재사용

 - 스레드의 생성 오버헤드를 방지하기 위해서라도 submit() 을 가급적 사용한다.




마무리는 예제로... 앞에서 한 내용이 모두 들어가있다. 주석을 참고하시길...


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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package ThreadPoolExample;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
 
public class main {
 
    public static void main(String[] args) {
        // ExecutorService 인터페이스 구현객체 Executors 정적메서드를 통해 최대 스레드 개수가 2인 스레드 풀 생성
        ExecutorService executorService = Executors.newFixedThreadPool(2);
         
        for(int i = 0; i < 10; i++){
            Runnable runnable = new Runnable() {
                 
                @Override
                public void run() {
                    //스레드에게 시킬 작업 내용
                    ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
                     
                    int poolSize = threadPoolExecutor.getPoolSize();//스레드 풀 사이즈 얻기
                    String threadName = Thread.currentThread().getName();//스레드 풀에 있는 해당 스레드 이름 얻기
                     
                    System.out.println("[총 스레드 개수:" + poolSize + "] 작업 스레드 이름: "+threadName);
                     
                    //일부로 예외 발생 시킴
                    int value = Integer.parseInt("예외");
                }
            };
     
            //스레드풀에게 작업 처리 요청
            executorService.execute(runnable);
            //executorService.submit(runnable);
             
             
            //콘솔 출력 시간을 주기 위해 메인스레드 0.01초 sleep을 걸어둠.
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
             
             
        }
         
        //스레드풀 종료
        executorService.shutdown();
     
         
         
         
    }
 
}


출력 결과





스레드 풀을 기본적으로 만들면 pool-n형식의 이름으로 스레드 풀 이름이 부여된다. 

0번째는 기본돌아가고있는 스레드풀인가???(공부해봐야함..)일단 우리는 pool-1을 만들었고,

execute()메서드를 실행시켰기 때문에 스레드 작업 중 예외가 나면 스레드를 바로 종료시키고 새로운 스레드를 생성한다. 따라서 결과가 thread-1,2,3,4,5. ... 10 까지 늘어났을 것이다.


만약 submit()메서드를 실행시키면, 아래와 같이 예외가 발생되도 해당 스레드를 죽이지 않고 계속 재사용한다.






스레드 풀에 대한 더 많은 이야기가 있지만, 다음을 기약하며... . 


끝으로 인상깊은 글을 스크랩한다.


멀티쓰레드 처리율(throughput)   임백준님의 Akka 시작하기에서 발췌 
아카를 이용한 리팩토링을 끝마쳤을 때, 똑같은 컴퓨터 위에서 전과 동일한 몬테 카를로 시나리오를 수행하는데 걸리는 시간이 6시간에서 2시간으로 단축되었다. 66%의 시간이 절약된 것이다. 결과를 확인한 사람들은 깜짝 놀랐다. 단순히 자바 스레드에서 아카로 라이브러리를 바꾸었을 뿐인데 그렇게 엄청난 차이가 있을 수 있냐며 고개를 갸웃거렸다. 물론 이런 차이를 일반화할 수는 없다. 이런 결과 하나를 가지고 아카가 자바 스레 드보다 3배 빠르다고 말하는 어리석은 사람은 없을 것이다. 아카도 내부적으로 자 1 아카에 대하여 - 017 바 스레드를 사용하기 때문에 그런 비교 자체가 성립하지 않는다. 하지만 일반적 인 차원에서 짚고 넘어갈만한 부분도 있다. 이렇게 커다란 차이가 어디에서 비롯 되었는지 이해하려면 우선 암달의 법칙Amdahl’s law을 생각해볼 필요가 있다. 암달의 법칙은 이렇다. “멀티코어를 사용하는 프로그램의 속도는 프로그램 내부에 존재하는 순차적sequential 부분이 사용하는 시간에 의해서 제한된다.” Thread나 Task를 만들어서 ExecutorService에게 제출하는 식으로 동시성 코드를 작성하면 여러 개의 스레드가 동시에 작업을 수행한다. 하지만 프로그램 안에는 Thread나 Task가 포함하지 않는 코드가 존재한다. 여러 개의 스레드가 동시에 작업을 수행하더라도 synchronized 블록이나 데이터베이스, 네트워크 API 호출 등을 만날 때 다른 스레드와 나란히 줄을 서서 순차적으로 작업을 수행 해야 하는 경우도 있다. 암달의 법칙은 프로그램이 낼 수 있는 속도의 상한이 이런 순차적 코드가 사용하는 시간에 의해서 제한된다고 말하는 것이다. 이러한 순차적 코드의 또 다른 이름은 블로킹blocking 콜이다. 문제는 스레드 자체 가 아니라 스레드를 사용하면서 자기도 모르게 만들어내는 블로킹 콜이다. 조금 과장해서 말하자면 자바 개발자가 스레드를 이용해서 만들어내는 ‘동시성’ 코드는 일종의 신기루다. 사실은 코드 곳곳에 존재하는 블로킹 콜, 순차적 코드 때문에 전 체적인 프로그램의 처리율은 이미 상한이 정해져 있지만 여러 개의 스레드가 ‘동 시에’ 동작한다는 사실로부터 위안을 받을 뿐이다.

출처: http://hamait.tistory.com/612 [HAMA 블로그]

Posted by citrine
2018. 1. 30. 10:36

1. 배경 : 버전에 따라 정상 동작하지 않는 driver가 존재

          이에 버전에 따라 각자 jdbc driver를 로딩할 필요성이 존재함



2. 소스


Pick your JDBC driver at runtime

If you're going to do any sort of database activity in Java, you'll probably be using JDBC. Like ODBC before it, JDBC is a great way to insure that your program is free of any ties to the underlying database. Traditionally, the mechanism is that you put the JDBC driver somewhere in the classpath and then use class.forName() to find and load the driver.

One problem with this is that it presumes that your driver is in the classpath. This means either packaging the driver in your jar, or having to stick the driver somewhere (probably unpacking it too), or modifying your classpath.

"But why not use something like URLClassLoader and the overload of class.forName() that lets you specify the ClassLoader?" Because the DriverManager will refuse to use a driver not loaded by the system ClassLoader. Ouch!

The workaround for this is to create a shim class that implements java.sql.Driver. This shim class will do nothing but call the methods of an instance of a JDBC driver that we loaded dynamically. Something like this:

import java.sql.*;

class DriverShim implements Driver {
	private Driver driver;
	DriverShim(Driver d) {
		this.driver = d;
	}
	public boolean acceptsURL(String u) throws SQLException {
		return this.driver.acceptsURL(u);
	}
	public Connection connect(String u, Properties p) throws SQLException {
		return this.driver.connect(u, p);
	}
	public int getMajorVersion() {
		return this.driver.getMajorVersion();
	}
	public int getMinorVersion() {
		return this.driver.getMinorVersion();
	}
	public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {
		return this.driver.getPropertyInfo(u, p);
	}
	public boolean jdbcCompliant() {
		return this.driver.jdbcCompliant();
	}
}

class test {
	public will_not_work() {
		URL u = new URL("jar:file:/path/to/pgjdbc2.jar!/");
		String classname = "org.postgresql.Driver";
		URLClassLoader ucl = new URLClassLoader(new URL[] { u });
		Class.forName(classname, true, ucl);
		DriverManager.getConnection("jdbc:postgresql://host/db", "user", "pw");
		// That will throw SQLException: No suitable driver
	}
	public will_work() {
		URL u = new URL("jar:file:/path/to/pgjdbc2.jar!/");
		String classname = "org.postgresql.Driver";
		URLClassLoader ucl = new URLClassLoader(new URL[] { u });
		Driver d = (Driver)Class.forName(classname, true, ucl).newInstance();
		DriverManager.registerDriver(new DriverShim(d));
		DriverManager.getConnection("jdbc:postgresql://host/db", "user", "pw");
		// Success!
	}

will_work() works because DriverShim was loaded by the system class loader, and the DriverManager doesn't care that it invokes a class that wasn't. We must perform the registration on the instance ourselves, because although Class.forName() will cause a registration to take place, that particular registration will fail for the same reason that will_not_work() fails.




# 참고

Posted by citrine