본문 바로가기

병렬처리


public class UnsafeCachingFactorizer implements Servlet{
private final AtomicReference<BigInteger> lastNumber = new AtomicReference<>();
private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<>();

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
BigInteger i = extractFromRequest(servletRequest);

if (i.equals(lastNumber.get())) {
encodeIntoResponse(servletResponse, lastFactors.get());
} else {
BigInteger[] factors = factor(i);
lastNumber.set(i);
lastFactors.set(factors);
encodeIntoResponse(servletResponse, factors);
}
}
}

 - AtomicReference를 사용한 단일  연산 참조 변수 각각은 스레드에 안전 하지만 UnsafeCachingFactorizer 자체는 틀린 결과를 낼 수 있는 경쟁 조건을 가

   지고 있음.

 - UnsafeCachingFactorizer에는 인수분해 결과를 곱한 값이 lastNumber에 캐시된 값가 같아야 된다는 불변 조건이 있으며 이 조건은 항상 성립해야 제대      로 동작 함.

 - 여러개의 변수가 하나의 불변조건을 구성하고 있다면 이 변수들은 서로 독립적이지 않다.



암묵적인 락


 - 자바에서는 단일 연산 특성을 보장하기 위해 synchronized 라는 구문으로 사용할 수 있는 락을 제공 함.

 - 자바에 내장된 락을 암묵적인 락(intrinsic lock 혹은 monitor lock 이라 한다)

 - 자바에서 암묵적인 락은 뮤텍스 (mutexes , mutual exclusion lock (상호배제 락))로 동작 한 번에 한 스레드만 특정 락을 소유




public class SynchronizedFactorizer implements Servlet {
private BigInteger lastNumber;
private BigInteger[] lastFactors;

@Override
public synchronized void service(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception{
BigInteger i = extractFromRequest(servletRequest);

if (i.equals(lastNumber)) {
encodeIntoResponse(servletResponse, lastFactors);
} else {
BigInteger[] factors = factor(i);
lastNumber = i;
lastFactors = factors;
encodeIntoResponse(servletResponse, factors);
}
}


 - 동기화 수단을 쓰면 아주 쉽게 인수분해 서블릿 스레드를 안전하게 고칠 수 있다.

 - 위 예제는 service 메소드에 synchronized 키워드를 추가해 한번에 한 스레드만 실행 할 수 있도록 하였다.

 - 하지만 이 방법은 너무 극단적이라 여러 클라이언트가 동시에 사용할 수 없고 이 때문에 응답성이 엄청 떨어질 수 있다.



public class CachedFactorizer implements Servlet {
private BigInteger lastNumber;
private BigInteger[] lastFactors;
private long hits;
private long cacheHits;

public synchronized long getHits() {
return hits;
}

public synchronized double getCacheHitRatio() {
return (double) cacheHits / (double) hits;
}

@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception
{
BigInteger i = extractFromRequest(servletRequest)
;
BigInteger[] factors = null;

synchronized (this) {
++
hits;

if (i.equals(lastNumber)) {
++
cacheHits;
factors = lastFactors.clone();
}
}

if (factors == null) {
factors = factor(i)
;
synchronized (this) {
lastNumber = i;
lastFactors = factors;
}
}

encodeIntoResponse(servletResponse
, lastFactors);
}
}


  - 위 예제는 전체 메소드를 동기화 하는 대신 두  개의 짧은 코드 블록을 synchronized키워드로 보호했다  

  - 하나는 캐시된 결과를 갖고 있는지 검사하는 일종의 확인 후 동작(check-and-act)부분이고 하나는 캐시 된 입력

    결과를 새로운 값으로 변경하는 부분 이다

 - synchronized 블록 밖에 있는 코드는 다른 스레드와 공유되지 않는 지역 변수만 사용하기 때문에 동기화 할 필요 없다.

 - 예전 처럼 AtomicLong 써도 괜찮지만 별다른 장점이 없다. 



참고 : 자바 병렬 프로그래밍

저자 브라이언 괴츠더그 리|에이콘출판 

'병렬처리' 카테고리의 다른 글

스레드 소개  (0) 2019.01.21
스레드 안전성  (0) 2017.04.28