6

저는 스칼라와 함수 프로그래밍에 비교적 익숙하지 않습니다. 많은 스레드 안전 함정을 피할 수있는 변경 불가능한 객체를 사용한다는 생각을 좋아합니다. 하나의 문제는 여전히 나를 괴롭 히며 스레드 안전성을 가르치는 데 사용되는 고전적인 예인 공유 카운터입니다.스레드 안전 공유 카운터를 구현하는 기능적인 방법

스레드 안전 카운터 (이 예제에서는 요청 카운터)를 구현하고, 변경 불가능한 개체와 기능 개념을 사용하고 동기화를 완전히 피할 수 있는지 궁금합니다.

: 여기에 참조 할 수 있도록 그래서

변경 가능한, 비 스레드 안전 버전 (단지 예 간결, 대중 멤버 변수 실례) 먼저 카운터 의 고전 변경 가능한 버전입니다

public class Servlet extends HttpServlet { public int requestCount = 0; @Override public void service(ServletRequest req, ServletResponse res) throws ... { requestCount++; //thread unsafe super.service(req, res); } } 

변경 가능한, 클래식 스레드 안전 버전 :

public class Servlet extends HttpServlet { 

    public volatile int requestCount = 0; 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    synchronized (this) { 
     requestCount++; 
    } 
    super.service(req, res); 
    } 
} 
(또는 그래서 ... 희망)

불변 개체 및 휘발성 변수를 사용하여 동기화없이 스레드 안전성을 얻는 방법이 있는지 궁금합니다.

여기 내 순진한 시도였습니다. 아이디어는 카운터에 대해 불변 개체를 갖고, 휘발성 변수를 사용하여 참조를 바꿉니다. 비린내 같지만 한발의 가치가 있습니다.

홀더 :

public class Incrementer { 
    private final int value; 
    public Incrementer(final int oldValue) { 
    this.value = oldValue + 1; 
    } 

    public Incrementer() { 
    this.value = 0; 
    } 

    public int getValue() { 
    return value; 
    } 
} 

수정 서블릿 :

public class Servlet extends HttpServlet { public volatile Incrementer incrementer = new Incrementer(); @Override public void service(ServletRequest req, ServletResponse res) throws ... { incrementer = new Incrementer(incrementer.getValue()); super.service(req, res); } } 

은 내가 증가 기에서 읽고 있어요으로이 또한 스레드로부터 안전하지 강한 느낌을 가지고 있고, 얻을 수 있습니다 부실 값 (예 : 참조가 이미 다른 스레드로 대체 된 경우) 실제로 스레드가 안전하지 않은 경우 잠금/동기화없이 이러한 카운터 시나리오를 처리 할 수있는 "기능적"방법이 있는지 궁금합니다.

그래서 제 질문 (들)

  1. 이 스레드는 우연히 안전하다?
  2. 예인 경우 그 이유는 무엇입니까?
  3. 그렇지 않은 경우 동기화하지 않고 카운터를 구현할 수있는 방법이 있습니까? 예제 코드는 위의 자바 있지만

, 스칼라 응답도

답변

13

환영이 스레드 혹시 안전한가요 물론입니까?

아니요. 이미 동기화 된 블록에 불변 개체를 만들지 않았다면 스레드로부터 안전하지 않습니다. 스레드 경합 상태에서 손상된 불변 개체를 만드는 가능성이 있습니다.

동일한 기능을 사용하려면 명시적인 동기화를 피하는 AtomicInteger을 사용할 수 있습니다.

public class Servlet extends HttpServlet { 

    public AtomicInteger incrementer = new AtomicInteger (0); 

    @Override 
    public void service(ServletRequest req, ServletResponse res) throws ... { 
    int newValue = incrementer.incrementAndGet(); 
    super.service(req, res); 
    } 
} 
+0

흥미롭게도 AtomicInteger는 내부적으로 동기화를 사용하기 때문에 답변으로 생각하지도 않았지만 소스 코드를 보면 목표를 달성하는 데 다른 방법 (기본 코드)을 사용하고있는 것처럼 보입니다. http :// /grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/atomic/AtomicInteger.java –

4

immutables의 나사 안전은 훨씬 더 비슷해 보입니다.

val x = AtomicReference(Vector("salmon", "cod")) 

// Thread 1 
val y = x.get 
println(y(y.length-1)) 

// Thread 2 
x.getAndSet(x.get.tail) 

당신이 mutably 작업하는 경우는, 당신은 심하게 스레드 (2) 다음 스레드 1의 인덱스가 실패 할 수 변경 가능한 목록을 변경하도록 유혹 할 것입니다. 또는 데이터를 복사해야합니다. 컬렉션을 실용적으로 재사용 할 필요가 없다면 (그리고 벡터가 길면) 매우 비쌀 수도 있습니다. 또는 데이터를 원자 적으로 가져 오거나 가져 오는 대신 두 스레드에서 큰 블록을 동기화해야합니다.

여전히 어떻게 든 동기화해야하며 오래된 데이터 복사본을 처리해야 할 수도 있습니다. 그러나 은 누가 데이터 구조의 복사본을 가지고 있는지 추적하고 미친 듯이 모든 사람을 동기화해야합니다. 데이터가 사용자의 아래에서 바뀌고 모든 곳에서 예외가 발생할 수 있기 때문입니다.