Shared Mutable State

공유하는 불변 객체

가능한한 최소한의 객체만 공유하고, 가능한한 공유하는 객체를 immutable하게 유지함으로써, 기본적으로 “동시성 문제” 크기를 줄인다. 불행히도 이 방법으로는 문제 크기를 0으로 만들지는 못한다. 실제 대부분의 애플리케이션에서 공유하는 불변 객체 사용을 완전히 없애진 못하기 때문에, 그럴 때 안전한 방법을 찾아야 한다.

서비스 기반 코드 예제를 살펴보자. 등록된 메일 박스 서비스의 Map을 관리한다고 가정해보자. 이름을 키로 사용하고 getMailboxByName 이라는 public 메소드를 제공하여 특정 메일 박스 또는 대응하는 박스가 없으면 null을 반환하도록 한다. 이 예제 코드는 6.1에서처럼 자바 synchronized 블럭을 사용하여 쓰레드 세이프티를 보장한다. 이 클래스는 잘 동작한다. 동일한 객체에 접근할 땐 해당 객체의 롹을 지니고 접근해야 하기 때문이다. 비록 맵 필드 자체는 final 이지만, map이 가지고 있는 내용물은 mutabl하며 여러 쓰레드가 공유하고 있다. 따라서 lock으로 보호해야 한다. 또란, 동기화 블럭은 가능한한 짧고, 빨리 롹을 반환해야 한다.

=> 음.. 글쿤 mutale한 객체를 공유하는 경우가 어떤건지 감이 오는군. 역시 예제가 있어야 이해하기 쉽네.

하지만, 코드 6.1은 Java 5에 도입된 새로운 동시성 기능의 장점은 하나도 이용하고 있지 않다. 자바 5의 기능을 사용하면 동시에 여러 쓰레드가 getMailboxByName 메소드를 호출하는 것이 가능하다. 이전 동기화 블럭은 읽기와 쓰기를 구분할 수 없었다. 모든 메소드 접근시에 불필요하게 롹 객체를 요구했다. 하지만 Java 4에 도입된 Read/Write 롹은 이를 구분할 수 있게 해준다. 코드 6.2는 이 기능을 사용하여 여러 쓰레드가 동시에 맵에서 읽기 작업을 할 수 있게 해준다. 물론 동시에 변경은 못하게 막고 있다.

만약 자바 5를 사용할 수 있는 환경이라면, 그 안에 들어있는 새로운 concurrnt 라이브러리를 확인해보는게 좋겠다. 자바 1.4를 사용하고 있다면 동기화 블럭 사용을 주저하지 말아라. 많은 개발자들은 성능 문제로 동기화 블럭 사용을 주저하지만, 실제로 동기화 블럭을 최대한 작게 유지만 한다면 그렇게 성능이 나쁘진 않다.  그리고 무엇보다 성능 조금 높이자고 동기화 문제를 방치한다는 건 말이 안 된다.

=> 옳커니~. 자바 5에 그런 게 있었구나.. 우와;; 평소에 쓰레드 다룰 일이 거의 없으니 완전 몰랐네.

=> 살펴 볼 클래스
– ReadWriterLock
– ReentrantReadWriteLock

The Price of Freedom

참조 : http://neilbartlett.name/blog/osgibook/

자유의 댓가.

=> 쓰레드를 맘대로 만들어 사용할 수 있는 자유의 댓가.

상상으로 만든 A, B, C 번들에 대한 간다한 시나리오를 살펴보자. 그림 6.1에 UML 시퀀스 다이어그램으로 나타냈다. 번들 A가 쓰레드를 시작시키고 어느 순간 번들 B에 접근하여 해당 쓰레드에서 번들 B의 start 메소드를 호출한다. 그러면 번들 B가 활동을 시작하고(active), 번들 B 액티베이터의 start 메소드가 번들 A가 만든 쓰레드에서 호출될 것이다. 게다가, B의 start 메소드에서 어떤 서비스를 등록하고, C가 서비스 트래커로 이 서비스 타입을 리스닝하고 있다. 번들 C 트래커의 addingService 메소드가 호출되고, 이 역시 A가 만든 쓰레드에서 호출된다. 마지막으로 C가 B가 등록한 서비스를 주기적으로 호출하는 쓰레드를 만들었다고 가정해보자.

=> 아.. 너무해. 이게 뭔리야. 좀 차근 차근 얘기해주지. -_-;; 그러니까…
1. 번들 A가 쓰레드를 만들고 거기서 번들 B의 start 메소드를 호출해서 번들 B를 활성화 시킨다.
2. 번들 B의 start 메소드에서는 번들 C가 리스닝하고 있는 서비스를 서비스 레지스트리에 등록한다.
3. 번들 C는 새로운 쓰레드를 만들어서 주기적으로 B가 등록한 서비스를 가져온다.
이말인가? 그렇다 치고..

사용자 삽입 이미지
클라이언트가 서비스 레지스트리에서 서비스를 가져갈 때, 프록시나 랩퍼가 아닌 진짜 서비스 객체를 찾는다. 따라서 클라이언트가 서비스에 메소드를 호출할 때, 해당 호출들은 기본적으로 동기적인 메소드 호출이다. 즉, 쓰레드에서 실행하는 서비스 메소드를 클라이언트 번들이 “쥐고있다.”(owned)라는 뜻이다.

=> 넹.

또한, 많은(전부는 아님) OSGi 내의 알림은 동기적으로 발생한다. 프레임워크가 콜백을 사용하는 메소드(ServiceEvent를 등록되엉 있는 서비스 리스너로 보내는 것이나 번들 엑티베이터의 start 또는 stop 메소드를 호출하는 것 같은)에 의해 호출될 때, 그런 콜백들은 동일한 쓰레드에서 실행되고, 이전 제어권이 프레임워크 메소드를 호출한 쪽에 되돌아가기 전에 완료해야 한다.

=> 아.. 어렵다. 프레임워크의 어떤 메소드에 콜백을 넘겨줬을 때, 그 콜백도 같은 쓰레드에서 호출된다는 것이고, 해당 콜백 실행이 완료한 다음에, 제어권을 넘겨줘야 한다는 것이군.

위와 같은 상황에는 세 개의 주요 가정이 존재한다.

  • 콜백과 서비스 메소드는 어떤 쓰레드에서든 호출될 수 있다. 여러 쓰레드에서 동시에 호출될 수도 있을 것이다. 이를 주시하지 않고 코딩했을 때는 예상하지 못한 문제를 발생시킬 수 있다.
  • 우리가 작성할 콜백과 서비스 메소드를 호출하는 쓰레드는 우리가 만들 것에 “포함되어 있지 않다.” 만약 오랜 시간 동작하거나, blocking I/O를 이런 콜백에서 사용한다면 전체 시스템을 지연시킬 수 있다.
  • OSGi API 메소드를 사용하면, 우린 해당 프레임워크가 어떤 리스너나 콜백을 사용하는지 예측할 수 없고, 따라서 해당 콜백에서 어떤 롹을 취하려는지도 알 수가 없다. 만약 그런 메소드를 호출하는 도중 다른 롹을 가지고 있다면, daedlock을 발생키질 위험이 있다.

이런 문제에 대한 해결책은 좋은 동시성 프로그래밍 프랙티스를 사용하는 것이다. 하지만, 안전한 동시성은 그렇게 어렵지 않다. 최소한 블라 블라 하는 것처럼. 천재만 할 수 있는 것이 아니다. 핵심은 (많은 천재들이 하지않는) 훈련이다. 몇 가지 규칙을 적용하여, 우리가 맞닥들이게 될 대부분의 상황을 쉽게 처리할 수 있다.

1. 변하는(Immutable) 객체는 자동적으로 쓰레드 세이프하며, 여러 쓰레드에 걸쳐 공유될 객체가 아니라면 쓰레드 세이프 해야할 이유가 없다. 따라서 가능한 공유할 것을 최소화 하고 가능한 immutable 객체를 사용하라.

2. 공유하는 불변의(mutabl) 객체가 정말로 꼭 필요한 경우, 모든 접근(읽기 쓰기)을 막아서 동일한 객체의 롹을 가지고 필드를 보호하거나 volatile 변수를 사용하라.

3. 이미 롹을 가지고 있는 상태에서 새로운 롹을 얻으려고 하지 말아라. 이 규칙을 따르면 자연스래, 롹을 얻으려는 시도를 할지도 모르는 “아는바없는” 혹은 “외부” 코드를 호출할 때는 어떤 롹도 쥐고 있어서는 안 된다. 이는 서비스 또는 OSGi API를 호출할 때를 말하며, 이들 중 대부분이 다른 번들의 콜백을 우리가 만든 쓰레드에서 실행하게 된다.

=> 글쿤, 롹을 쥐고 있는 상태에서 롹을 가지려고 할지도 모르는 어떤 서비스 또는 OSGi API를 호출하면 deadlock이 발생할 수 있으니, 그럴 땐 롹을 쥐고 있지 말라는 것이로군요.

=> 한가지 궁금한건 2번에서 volatile을 사용하면 모든 쓰레드에서 동일한 값을 사용하게 될텐데 그렇게 되면, 결국엔 mutable 하지 못한게 된느거 아닌가.. 흠.. A 쓰레드에서 foo.name을 a라고 하고 B 쓰레드에서 foo.name을 찍으면 a가 찍힐테고 B 쓰레드에서 다시 foo.name을 b라고 하면, 이번엔 A 쓰레드에서 foo.name을 찍으면 b가 찍힐텐데… 내가 volatile을 잘못이해한건가.. 아닌데, 맞을텐데, 아니면, mutable->immutable하게 사용하라는 것인가.. 차리리 ThreadLocal을 사용하는게 낫지 않을까. 아닌데 저자가 그걸 모를리도 없고, 아. mutable이니까, setter를 막아둬서 값 변경을 막아뒀겠구나.. 위와 같은 내 생각은 immutable객체를 공유할 때 생기는 문제지;. (흠.. 이것도 아닌가 위에선 분명 ‘읽기와 쓰기’라고 ‘쓰기’를 언급하고 있자나..) 흠..그럼 뮤터블 객체를 왜 공유해서 쓰는거지. 그럴 때 그 객체 값을 변경하지 못하면, 멀티 쓰레드 걱정 안해도 되는거 아니야? 아.. 다음 챕터를 읽어보자. 아 내 머리.

Concurrency and OSGi

참조 : http://neilbartlett.name/blog/osgibook/

J2EE 같은 무거운(heavyweight) 프레임워크에 비해, OSGi는 쓰레드를 포함한 JVM의 모든 리소스를 제어하려 들지 않는다. J2EE에선 직접 쓰레드를 만들거나 명시적인 동기화(synchronization)를 하는 코드를 작성하는 것을 금하고, 대신에 제한적인 “작업 관리” 프레임워크를 제공한다. OSGi는 여러분이 직접 애플리케이션에서 쓰레드를 만들고 스케쥴링을 할 수 있다. 그렇게 하려면 OSGi 라이브러리들은 쓰레드 세이프해야 하며 어떤 쓰레드에서도 호출할 수 있어야 한다.

=> 즉, OSGi에서는 쓰레드를 직접 다루는 코드를 작성할 수도 있으니, 쓰레드 세이프한 라이브러리를 만드는 것이 중요하다는 뜻인듯..

하지만, 이런 자유에는 댓가가 따르듯이, 우리가 만들 번들에서 쓰레드를 생성하여 사용하는것이 가능하듯이, 우리가 만들 번들이 단일 쓰레드 환경에서만 사용되리라는 보장은 못한다. OSGi는 암묵적으로 멀티 쓰레드다. 따라서 반드시 작성하는 코드가 쓰레드 세이프해야 하며, 특히 이벤트나 콜백을 다른 프레임워크 또는 번들에서 받아올 때 주의해야 한다.

=> 흠. 당연한 말씀.

Brian Goetz의 “Java Concurrency in Practice”에서 자바 동시성에 대해 자세히 다루고 있으니, 프로페셔널 자바 프로그래머라면 항상 이 책을 가까이 하라.

=> 넵.

BundleContext로 할 수 있는 일

BundleActivator 다음으로 가장 중요한 OSGi API를 꼽으라면, BundleContext일 겁니다. 어쩌면 BundleActivator보다 중요할지도 모르죠. OSGi 플랫폼에 설치한 번들과 관련된 문맥 정보를 담고 있는 객체니까요. 어떤 용도로 쓸 수 있는지 알아두면 좋겠죠?

시스템 전역에서 사용할 설정 프로퍼티즈 룩업

ID로 설치된 다른 번들 찾기

설치된 모든 번들 목록 가져오기

번들을 프로그래밍을 통해서 라이프사이클 다루기

새로운 번들을 프로그래밍을 통해서 설치하기

프레임워크가 관리하는 영속 저장소에서 파일 가져오거나 저장하기

프레임워크 내부에 있는 어떤 번들의 상태 변화를 알려주기 위한 번들 리스너 등록 또는 해지하기

프레임워크 내부에 있는 어떤 서비스의 상태 변화를 알려주기 위한 서비스 리스너 등록 또는 해지하기

일반적인 프레임워크 이벤트를 알려주기 위한 프레임워크 리스너 등록 또는 해지하기

참조 : http://neilbartlett.name/blog/osgibook/