문제

나는이 웹 애플리케이션에 있다는 것 중의 하드/성능에 대한 테스트,particularily 기능에는 우리가 기대하는 몇백하는 사용자에 액세스할 수 있는 동일한 페이지를 새로 고침을 타격에 대한 모든 10 초에 이 페이지로 이동합니다.한 지역의 개선은 우리가 발견한 우리는 이 기능을 캐쉬한 응답에서는 웹 서비스 일정 기간 동안,데이터 변경되지 않음.

를 구현한 후에 이 기본 캐싱에서 몇 가지 추가 테스트에 나가는 것을 발견하지 않았는 방법을 고려한 스레드를 동시에 액세스할 수 있습 캐시 동일한 시간에.내가 찾는 것에 문~100ms,약 50 스레드를 시도하고 있었을 가져올 객체를 캐시에서 찾는 그것이 만료,치는 웹 서비스를 가져오는 데이터,그리고 다음을 넣어 개체 캐시에 저장합니다.

원래 코드은 아래와 같습니다.

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {

  final String key = "Data-" + email;
  SomeData[] data = (SomeData[]) StaticCache.get(key);

  if (data == null) {
      data = service.getSomeDataForEmail(email);

      StaticCache.set(key, data, CACHE_TIME);
  }
  else {
      logger.debug("getSomeDataForEmail: using cached object");
  }

  return data;
}

그래서,는지 확인하는 단 하나의 스레드가 호출하는 경우에는 웹 서비스에 있는 객체에 key 료,생각하는 데 필요한 동기화 캐시 get/set 작업,그리고 그것같이 보였을 사용하여 캐시 키는 것이 좋은 후보에 대한 개체에 동기화(이 방법으로 호출을 위해 이 방법을 이메일 b@b.com 되지 않을 것으로 차단 방법이 통화 a@a.com).

업데이트 방법은 다음과 같이 보

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {


  SomeData[] data = null;
  final String key = "Data-" + email;

  synchronized(key) {      
    data =(SomeData[]) StaticCache.get(key);

    if (data == null) {
        data = service.getSomeDataForEmail(email);
        StaticCache.set(key, data, CACHE_TIME);
    }
    else {
      logger.debug("getSomeDataForEmail: using cached object");
    }
  }

  return data;
}

또한 추가 logging 라인이 같은 것들에 대해"동기화 전에 차단","내부에 블록 동기화","떠나려는 동기화 차단",및"동기화 후획",그래서 내가 결정할 수 있다면 효과적으로 동기화하 get/set 작업입니다.

그러나 그것같이 보이지 않는이 일했다.테스트는 로그 출력용:

(로그 출력은'threadname''로거 이름을'''메시지가)
http-80-Processor253jsp.view 페이지 getSomeDataForEmail:을 입력에 대한 동기화록
http-80-Processor253jsp.view 페이지 getSomeDataForEmail:내부 블록 동기화
http-80-Processor253 캐시입니다.StaticCache-얻을:체에서 키[SomeData-test@test.com 기]료
http-80-Processor253 캐시입니다.StaticCache-얻을:키[SomeData-test@test.com]반환 값이[null]
http-80-Processor263jsp.view 페이지 getSomeDataForEmail:을 입력에 대한 동기화록
http-80-Processor263jsp.view 페이지 getSomeDataForEmail:내부 블록 동기화
http-80-Processor263 캐시입니다.StaticCache-얻을:체에서 키[SomeData-test@test.com 기]료
http-80-Processor263 캐시입니다.StaticCache-얻을:키[SomeData-test@test.com]반환 값이[null]
http-80-Processor131jsp.view 페이지 getSomeDataForEmail:을 입력에 대한 동기화록
http-80-Processor131jsp.view 페이지 getSomeDataForEmail:내부 블록 동기화
http-80-Processor131 캐시입니다.StaticCache-얻을:체에서 키[SomeData-test@test.com 기]료
http-80-Processor131 캐시입니다.StaticCache-얻을:키[SomeData-test@test.com]반환 값이[null]
http-80-Processor104jsp.view 페이지 getSomeDataForEmail:내부 블록 동기화
http-80-Processor104 캐시입니다.StaticCache-얻을:체에서 키[SomeData-test@test.com 기]료
http-80-Processor104 캐시입니다.StaticCache-얻을:키[SomeData-test@test.com]반환 값이[null]
http-80-Processor252jsp.view 페이지 getSomeDataForEmail:을 입력에 대한 동기화록
http-80-Processor283jsp.view 페이지 getSomeDataForEmail:을 입력에 대한 동기화록
http-80-Processor2jsp.view 페이지 getSomeDataForEmail:을 입력에 대한 동기화록
http-80-Processor2jsp.view 페이지 getSomeDataForEmail:내부 블록 동기화

보고 싶은 한 번에 하나의 스레드를 입력하는 시간을 종료/블록 동기화 get/set 작업입니다.

은 문제가 있어서의 동기화 문자열에까?나는 생각 캐시 키는 좋은 선택이 될 것입으로 독특한 작업,그리고 비록 final String key 선언에서 방법을 생각하고 있었는 각 스레드를 얻고 있을 것입 참조 동체 따라서는 것에 동기화 이 단체입니다.

나는 무엇을 잘못을까요?

업데이트:후에 추가에서 로그를 보는 방법과 동일한 동기화한 논리는 핵심은 항상 동일 등

final String key = "blah";
...
synchronized(key) { ...

전시 하지 않는 동일한 동시성 문제-단 한 번에 하나의 스레드를 입력합니다.

업데이트 2:모든 사람에게 감사를 도와주세요!나는 받아들였음에 대 intern()ing 문자열을 해결하는 내 초기 문제 어디서 여러 스레드가 들어가 동기화된 블록을 생각지 않기 때문에, key의 동일한 값입니다.

다른 사람들이 지적으로 사용하여, intern() 이러한 목적과 동기화에는 해당 문자열을 참으로 수 차례 나쁜 생각이-실행할 때 제 미터 테스트에 대하여 웹 애플리케이션을 시뮬레이션 로드,나는 사용 힙 크기 성장을 거의 1GB 에서 20 분 거리에 있습니다.

현재 사용하여 간단한 솔루션의 단지 동기화체 방법을-하지만 나는 다음과 같은 코드 샘플을 제공해 martinprobst 및 MBCook 지만,이에 대해 유사한 7 getData() 방법 이 클래스에서는 현재(야 하기 때문에 7 개의 다른 데이터를 웹 서비스에서),나는 원하지 않았을 추가하는 거의 중복 논리에 대해 점점 및 잠금을 해제 하여 각각의 방법입니다.하지만 이것은 확실히 매우,매우 가치있는 정보는 미래 사용합니다.이런 생각이 궁극적으로 올바른 답변에는 최상의 방법을 만드는 조작과 같은이 스레드에 안전,그리고 나는게 더 많은 투표는 이러한 대답하면 나는 할 수 있습니다!

도움이 되었습니까?

해결책

지 않고 내 두뇌는 완전한 장치로서 빠른 검색은 무엇을 말하는 것처럼 보이지만 당신은 필요한 인턴()문자열:

final String firstkey = "Data-" + email;
final String key = firstkey.intern();

두 개의 문자열 값이 같지만 그렇지 않으면 반드시 동일한 개체입니다.

이 수 있습을 소개하 새로운 포인트의 경기 때문에 깊은 VM,인턴()할 수 있습을 획득하려는 잠급니다.내가 무슨 생각이 없는 현대 Vm 처럼,이 지역에서 그러나 희망을 그들은 악마을 최적화할 수 있습니다.

나는 가정 당신이 알고 있는 StaticCache 여전히 해야 합니다.하지만 경쟁이 있어야 작은 비해 무엇을 했는 경우에 잠금 캐시보다는 그냥 키를 호출하는 동안 getSomeDataForEmail.

질문에 대한 응답 업데이트:

나는 생각하기 때문에 문자열 리터럴 항상 산출한 동일한 개체입니다.코스타 데이브 포인트는 코멘트에서 그것의 더 나은 그것보다:리터럴 항상 산출한 정규 표현입니다.그래서 모든 끈으로 동일한 값에서 어디서나 프로그램은 동일하게 생체입니다.

편집

다른 사람들이 지적하는 동기화에서 인턴 문자열은 실제로 정말 나쁜 생각이 -부분적으로 만들어지므로 인턴이 문자열을 허용하는 원인이 존재하에서 영구적으로,그리고 부분적으로 때문에 하나 이상일 경우에는 코드에서 어디서나 프로그램으로 동기화하에서의 인턴,문자열을 간의 종속성을 그들의 비트 코드,교착 상태 및 예방 또는 기타 버그 수 있습 불가능합니다.

전략 이를 방지하기 위해 저장하여 잠금당 개체 키 문자열에서 개발되고 있는 다른 답변으로 저는 유형입니다.

여기 대안-그것은 여전히 사용하는 단일 잠금,하지만 우리가 알고있는 우리가 해야 하나의 사람들을 위해 캐시 어쨌든,그리고 당신이 이야기에 대해 50 스레드,지 않 5000,그래서할 수 없는 치명적이다.나는 또한다고 가정하면 성능 병목 현상이 여기에는 느린 차단 I/O DoSlowThing()하는 것입니다 따라서 크게 활용되고 있지 않전.하지 않을 경우 병목 현상이 발생한 다음:

  • 면 CPU 는 바쁜 그런 다음 이 접근 방식이 충분하지 않을 수 있습고야 할 또 다른 접근 방식이다.
  • 면 CPU 는 바쁜이며,서버 접근 병목 현상이 발생하지 않는지,그런 다음 이 접근 방식이 과잉,그리고 당신이뿐만 아니라 모두 잊이 당 열쇠 잠금,어,synchronized(StaticCache)주위에 전체 작업,그리고 그렇게 쉬운 방법입니다.

물론 접근 방식이 필요하 흡수 테스트에 대한 확장성 사용하기 전에--나는 아무것도 보장.

이 코드를 필요로하지 않는 StaticCache 가 동기화되거나 그렇지 않으면 스레드에 안전합니다.해야 하는 재방문하는 경우 다른 코드(예를 들어 예정일의 오래된 데이터)를 적 닿을 주시기 바랍니다.

IN_PROGRESS 미치지 않을 정확하게 청소하지만 코드를 간단하고 저장하는 두 개의 해시 테이블.그것은 처리하지 않 InterruptedException 기 때문에 나지 않는 무엇인지 알고 싶어하는 경우입니다.또한,당 DoSlowThing()일관되게 실패에 대한 지정된 핵심 이 코드는 그대로 정확하게 되지 않아,이후 모든 실을 통해 다시 시도할 것입니다.하지 않기 때문에 나는 무엇을 알고 실패 기준,그리고 있는 책임 있는 임시 또는 영구적이지 않아요 이것을 처리하거나,나는 확실히 스레드지 않도록 영원합니다.에서 연습할 수 있어 데이터에 값을 캐시을 나타내는'사용할 수 없습니다 아마 사유 및 제한에 대한 때를 다시 시도하십시오.

// do not attempt double-check locking here. I mean it.
synchronized(StaticObject) {
    data = StaticCache.get(key);
    while (data == IN_PROGRESS) {
        // another thread is getting the data
        StaticObject.wait();
        data = StaticCache.get(key);
    }
    if (data == null) {
        // we must get the data
        StaticCache.put(key, IN_PROGRESS, TIME_MAX_VALUE);
    }
}
if (data == null) {
    // we must get the data
    try {
        data = server.DoSlowThing(key);
    } finally {
        synchronized(StaticObject) {
            // WARNING: failure here is fatal, and must be allowed to terminate
            // the app or else waiters will be left forever. Choose a suitable
            // collection type in which replacing the value for a key is guaranteed.
            StaticCache.put(key, data, CURRENT_TIME);
            StaticObject.notifyAll();
        }
    }
}

때마다 아무것도 추가 캐시에,모든 스레드를 깨고 확인시(상관없이 무엇을 키 그들은 후),그래서 그것을 가능하게 더 나은 성과를 얻을 적은 논쟁적인 알고리즘이 있습니다.그러나,많은 작동하는 동안 개최됩니다 풍부한 유휴 CPU 시간에 차단 I/O,그래서 그것은 수도 문제가 되지 않습니다.

이 코드를 수 commoned 사용을 위한 여러 캐시어를 정의할 경우 적합한 추상화를 위해 캐쉬와 관련,잠금 데이터를 반환 IN_PROGRESS 미,그리고 느리게 작업을 수행할 수 있습니다.회전으로 모든 일 방법에 캐시 되지 않을 수도 있습니다 잘못된 생각이다.

다른 팁

동기화에서 인턴 것 문자열이 되지 않을 수도 있습니다 좋은 아이디어에서 모든 학생들은 인턴십을 통해 그 문자열로 변 글로벌 객체를 동기화하는 경우에는 동일한 구금 문자열의 다른 부분에서의 응용 프로그램를 얻을 수 있습니다,당신은 정말 이상한 기본적으로 undebuggable 동기화 문제와 같은 교착 상태.보일 수 있습니다 가능성이 있지만,그것이 일어날 때 당신은 정말입니다.일반적으로만 이제까지 동기에 로컬 개체 어디에 당신이 절대적으로는 코드의 외부 모듈을 수도 있습니다.

귀하의 경우에,당신이 사용할 수 있는 동기화 해시 테이블에 저장하는 잠 개체에 대한 열쇠를 수령하실 수 있습니다.

E.g.:

Object data = StaticCache.get(key, ...);
if (data == null) {
  Object lock = lockTable.get(key);
  if (lock == null) {
    // we're the only one looking for this
    lock = new Object();
    synchronized(lock) {
      lockTable.put(key, lock);
      // get stuff
      lockTable.remove(key);
    }
  } else {
    synchronized(lock) {
      // just to wait for the updater
    }
    data = StaticCache.get(key);
  }
} else {
  // use from cache
}

이 코드는 레이스 상태,두 개의 스레드를 넣을 수 있습으로 물체 잠금 표합니다.이것은 그러나 문제가 되지 않기 때문에,당신은 단지 하나의 스레드를 호출하는 웹 서비스 및 업데이트하시는 문제가 안 된다.

무효화하는 경우 캐시 몇 시간 후,당신은 여부를 확인해야는 데이터가 null 로 다시 검색한 후 그것은에 캐시 잠금!= null 인 경우입니다.

또는,그리고 훨씬 더 쉽고,당신은 전체를 만들 수 있습니다 캐시 검색 방법("getSomeDataByEmail")동기화됩니다.이 의미는 스레드 동기화에 액세스할 때 캐시될 수 있는 성능 문제입니다.그러나 언제나처럼,이 시도는 간단한 솔루션을 먼저 참조하는 경우 그것은 정말 문제!많은 경우에 그것은,당신은 아마 많이 지출 시간을 더 가공한 결과보다는 동기화.

문자열 좋은 후보에 대한 동기화합니다.해야 하는 경우에는 동기화 문자열에 ID 를 사용하여 수행할 수 있습는 문자열을 만들 mutex("참조동기화에 ID").의 여부 비용는 알고리즘은 가치가 있는 여부에 따라 달라를 호출하여 귀하의 서비스는 포함한 어떤 중요한 I/O.

도:

  • StaticCache.(일)set() 방법은 영역에서.
  • 문자열입니다.인턴() 에 비용(중 하나 간에 차이는 VM 구현)와 사용해야 합니다.

다른 사람은 제안된 인턴은 문자열이며,일할 것이다.

문제는 자바를 유지하 억류된 문자열이다.내가 들었다 그것이 이 경우에도 당신이 가지고 있지는 않은지를 참조하기 때문에 값을 할 필요가 동일한 다음에 누군가가 사용하는 문자열입니다.즉,인턴 모든 문자열을 시작할 수 있습을 먹고 메모리의 부하와 함께 당신을 설명하는 수 큰 문제입니다.

본 두 가지 솔루션을 this:

동기화할 수 있습에서 또 다른 목

대신 이메일을 보유하고 있는 개체의 이메일(말하는 사용자 객체)를 보유하는 가치의 이메일로 변수입니다.이미 있는 경우 다른 객체를 나타내는 사람은(당신 말이 이미 뽑아서 DB 를 기반으로 자신의 이메일)사용할 수 있습니다.을 구현함으로써 같음 방법과 차이 방법을 확인하실 수 있습 Java 간주체 동일할 경우 정적시오.담고()경우 찾아 데이터가 캐시에서(당신은 동기화에 캐시).

실제로,당신은 당신을 유지할 수 있는 두 번째 맵에 대한 개체를 잠급니다.무언가 이것을 좋아한다:

Map<String, Object> emailLocks = new HashMap<String, Object>();

Object lock = null;

synchronized (emailLocks) {
    lock = emailLocks.get(emailAddress);

    if (lock == null) {
        lock = new Object();
        emailLocks.put(emailAddress, lock);
    }
}

synchronized (lock) {
    // See if this email is in the cache
    // If so, serve that
    // If not, generate the data

    // Since each of this person's threads synchronizes on this, they won't run
    // over eachother. Since this lock is only for this person, it won't effect
    // other people. The other synchronized block (on emailLocks) is small enough
    // it shouldn't cause a performance problem.
}

이렇게 하면 15 가져옵에서 동일한 전자 메일 주소에 하나입니다.해야 합 방지하기 위해 뭔가 너무 많은 항목이에서 끝나는 emailLocks 지도입니다.용 LRUMaps Apache Commons 그것을 할 것입니다.

이 필요하다는 것 조정이지만,그 문제를 해결할 수 있습니다.

는 다른 키를 사용

고자 하는 경우를 넣어 가능한 오류가(난 이것이 얼마나 중요한 일인)사용할 수 있습니 차의 문자열 수 있습니다.수 할 필요가 없 인턴.

요약

도움이 되기를 바랍니다.실을 꿰는 재미,그렇지 않나요?사용할 수도 있습 세션을 설정 값을 의미를"나는 이미에서 작업을 찾는 것이"그리고는지 확인하십시오(초 셋째,N)드를 시도를 만들거나 결과를 기다려하여 주시기 바랍니다.나는 나는 세 가지 제안을 드리겠습니다.

사용할 수 있습니다 1.5 동시 유틸리티를 제공하는 캐쉬를 허용하도록 설계된 여러 개의 동시 액세스의 단일 포인트가(즉단 하나의 스레드를 적을 수행하는 비싼 개체"창조"):

 private ConcurrentMap<String, Future<SomeData[]> cache;
 private SomeData[] getSomeDataByEmail(final WebServiceInterface service, final String email) throws Exception {

  final String key = "Data-" + email;
  Callable<SomeData[]> call = new Callable<SomeData[]>() {
      public SomeData[] call() {
          return service.getSomeDataForEmail(email);
      }
  }
  FutureTask<SomeData[]> ft; ;
  Future<SomeData[]> f = cache.putIfAbsent(key, ft= new FutureTask<SomeData[]>(call)); //atomic
  if (f == null) { //this means that the cache had no mapping for the key
      f = ft;
      ft.run();
  }
  return f.get(); //wait on the result being available if it is being calculated in another thread
}

물론,이 처리하지 않는 예외로 당신이 원하고,캐시하지 않는 제거됩니다.아마도 당신은 그것을 사용할 수 있는 기초로를 변경 StaticCache 클래스,하지만.

사용 알맞은 캐싱 framework 등 ehcache.

을 구현하기 좋은 캐시만큼 쉽지 않은 일부 사람들이 믿습니다.

에 대한 코멘트는 문자열입니다.인턴()는 원본의 메모리 누수,는 사실이 아닙니다.구금 문자열 쓰레기 수집,그것은 단지 걸릴 수 있습니다 더 이상 때문에 특정 JVM 의(일)에 저장된 펌 공간만에 감동을 전체 GC 습니다.

여기에는 안전한 짧은 Java8 솔루션을 사용하는지도의 전용 잠금체에 대한 동기화:

private static final Map<String, Object> keyLocks = new ConcurrentHashMap<>();

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
    final String key = "Data-" + email;
    synchronized (keyLocks.computeIfAbsent(key, k -> new Object())) {
        SomeData[] data = StaticCache.get(key);
        if (data == null) {
            data = service.getSomeDataForEmail(email);
            StaticCache.set(key, data);
        }
    }
    return data;
}

그것은 단점을 가지고 있는 열쇠와 자물쇠체지도에서 지원합니다.

이 일될 수 있습니다 주위에 다음과 같다:

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
    final String key = "Data-" + email;
    synchronized (keyLocks.computeIfAbsent(key, k -> new Object())) {
        try {
            SomeData[] data = StaticCache.get(key);
            if (data == null) {
                data = service.getSomeDataForEmail(email);
                StaticCache.set(key, data);
            }
        } finally {
            keyLocks.remove(key); // vulnerable to race-conditions
        }
    }
    return data;
}

하지만 그 인기 있는 키는 것이 지속적으로 다시 삽입하면서도 자물쇠를 가진 물체를 다시 할당.

업데이트:과 이것은 경쟁 조건을 가능성 때 두 개 스레드가 동시에 입력 동기화 섹션을 같은 열쇠 그러나 다른 자물쇠.

그래서 그것을 수 있습니다 더 안전하고 효율적으로 사용 만료 구아바 캐시:

private static final LoadingCache<String, Object> keyLocks = CacheBuilder.newBuilder()
        .expireAfterAccess(10, TimeUnit.MINUTES) // max lock time ever expected
        .build(CacheLoader.from(Object::new));

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
    final String key = "Data-" + email;
    synchronized (keyLocks.getUnchecked(key)) {
        SomeData[] data = StaticCache.get(key);
        if (data == null) {
            data = service.getSomeDataForEmail(email);
            StaticCache.set(key, data);
        }
    }
    return data;
}

참고하는 것으로 간주됩 여기에는 StaticCache 은 스레드에 안전하지 않아 고통에서 동시에 읽기 및 쓰기를 위해 서로 다른 키를 사용합니다.

의 주요 문제는 단지가있을 수 있다는 여러 인스턴스의 동일한 문자열 값입니다.주요 문제는 당신이 있어야만 하는 모니터에 있는 동기화에 대한 액세스 StaticCache 개체입니다.그렇지 않으면 여러 스레드가 끝낼 수 있습니다 동시에 수정 StaticCache(이기는 하지만 서로 다른 키를),가장 가능성이 있는 지원하지 않는 동시에 수정할 수 있습니다.

전화:

   final String key = "Data-" + email;

새로운 객체를 생성한다든 메소드가 호출됩니다.기 때문에 그 개체 당신은 무엇을 사용하여 잠금 장치,및 모든 이 메소드에 대한 호출에 새로운 객체를 생성한다면,당신은 정말 액세스 동기화를 지도에 따라 핵심입니다.

이 설명의 편집합니다.할 때에 고정 문자열입니다,그것은 작품이다.

를 사용하여 인턴()문제를 해결하기 때문에,그것이 문자열을 반환합에서 내부 수영장에 의해 유지 String 클래스를 보장하는 경우 두 문자열은 동일하나에서 수영장 등이 사용됩니다.보

http://java.sun.com/j2se/1.4.2/docs/api/java/lang/String.html#intern()

이 질문은 것을 나에게 조금이 너무 광범위하고,따라서 그것은 동등하게 유발하는 광범위한의 답변이 있습니다.그래서 나는 대답을 하려고 질문 었 리디렉션에서도 그 하나에는 폐쇄되었으로 복제됩니다.

public class ValueLock<T> {

    private Lock lock = new ReentrantLock();
    private Map<T, Condition> conditions  = new HashMap<T, Condition>();

    public void lock(T t){
        lock.lock();
        try {
            while (conditions.containsKey(t)){
                conditions.get(t).awaitUninterruptibly();
            }
            conditions.put(t, lock.newCondition());
        } finally {
            lock.unlock();
        }
    }

    public void unlock(T t){
        lock.lock();
        try {
            Condition condition = conditions.get(t);
            if (condition == null)
                throw new IllegalStateException();// possibly an attempt to release what wasn't acquired
            conditions.remove(t);
            condition.signalAll();
        } finally {
            lock.unlock();
        }
    }

에(외부) lock 운영(안)획득을 얻을 수 있는 독점에 액세스하지도가 짧은 시간에 경고당 개체에 이미지도,현재 스레드 기다릴 것입니다, 그렇지 않으면 그것은 새로운 Condition 지도에 놓(안)잠금 및 진행 과(외부)를 잠금으로 간주됩을 얻을 수있다.의(외부) unlock 가동,처음을 획득하는(내부)잠금,신호에 Condition 고 그 개체를 제거에서 전단지를 확인할 수 있습니다.

클래스를 사용하지 않은 동시 버전 Map, 기 때문에 액세스할 때마다 그것은에 의해 보호는 단일(안)잠급니다.

통지하시기 바랍,의미의 lock() 방법은 이 클래스가 다른 것의 ReentrantLock.lock(), 이,반복되는 lock() 호출이 없는 짝 unlock() 중단됩니다 현재는 실정이다.

예를 들어의 사용법을 적용될 수 있는 상황,OP 설명

    ValueLock<String> lock = new ValueLock<String>();
    // ... share the lock   
    String email = "...";
    try {
        lock.lock(email);
        //... 
    } finally {
        lock.unlock(email);
    }

이것은 늦은 오히려,하지만 꽤 많은 잘못된 코드의 여기에 제공됩니다.

이 예에서는 다음과 같습니다.

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {


  SomeData[] data = null;
  final String key = "Data-" + email;

  synchronized(key) {      
    data =(SomeData[]) StaticCache.get(key);

    if (data == null) {
        data = service.getSomeDataForEmail(email);
        StaticCache.set(key, data, CACHE_TIME);
    }
    else {
      logger.debug("getSomeDataForEmail: using cached object");
    }
  }

  return data;
}

동기가 잘못 범위입니다.에 대한 정 캐시를 지원하는 얻을 넣어/API 이 있어야 합니다 적어도 동기의 주위에 얻을 및 getIfAbsentPut 입력 작업을 위해,안전한 접근을 주시기 바랍니다.범위의 동기화가 될 것이 캐시 자체.

는 경우를 업데이트해야 합 데이터 요소는 자신 추가는 추가로 레이어의 동기화되어야에서의 개인 데이터 요소입니다.

SynchronizedMap 장소에서 사용할 수 있습의 명시적 동기화가 있지만,주의는 여전히 관찰될 것입니다.잘못된 Api 를 사용(넣을 대신 putIfAbsent)다음 작업을 수 없는 필요한 동기화,사용에도 불구하고의 동기 지도입니다.통지 합병증을 도입하여 사용 putIfAbsent:나 가치를 넣어 계산해야 하는 경우에도 필요하지 않은 경우(기 때문에 넣어 알 수 없는 경우에 넣어 가치가 필요할 때까지 캐시 내용은 조사)또는 필요 조심의 사용을 위임(말을 사용하여,향후 작동하는지만,약간의 불일치;아래 참조)을 넣은 값을 얻는 수요에 필요한 경우.

선물의 사용은 가능하지만,오히려 보인다고 아마도 조금의 overengineering.미래 API 에 그것의 핵심 비동기업에 대한,특히 작업을 완료되지 않을 수 있습니다 immediately.을 포함하는 미래는 매우 아마도 레이어의 스레드 생성-여분의 아마 불필요한 합병증이 있습니다.

의 주요 문제를 사용하여 미래를 위한 이 유형의 작업은 미래의 본질적으로 관계에 multi-threading.사 미래의 새로운 스레드가 필요하지 않습을 의미을 무시하고 많은 기계류의 미래를 만들고,그것을 지나치게 무거운 위한 API 를 이용.

왜 그냥 렌더링 static html 페이지를 얻는 제공하는 사용자와 재생 모든 x 분?

나는 건의 제거 연결 문자열이 완전히 당신이 필요하지 않습니다.

final String key = "Data-" + email;

은 거기에 다른 것이/유형의 객체를 캐쉬에 사용하는 이메일 주소는 필요하시다"데이터"의 시작 부분에 키?

하지 않는 경우에,나는 그냥 그

final String key = email;

고 당신이 모든 것을 피하기 위해 여분의 문자열 창조 too.

다른 방법을 동기화 문자열에 개체:

String cacheKey = ...;

    Object obj = cache.get(cacheKey)

    if(obj==null){
    synchronized (Integer.valueOf(Math.abs(cacheKey.hashCode()) % 127)){
          obj = cache.get(cacheKey)
         if(obj==null){
             //some cal obtain obj value,and put into cache
        }
    }
}

는 경우에 다른 유사한 문제는 다음 코드를 작동,멀리로 말할 수 있:

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;

public class KeySynchronizer<T> {

    private Map<T, CounterLock> locks = new ConcurrentHashMap<>();

    public <U> U synchronize(T key, Supplier<U> supplier) {
        CounterLock lock = locks.compute(key, (k, v) -> 
                v == null ? new CounterLock() : v.increment());
        synchronized (lock) {
            try {
                return supplier.get();
            } finally {
                if (lock.decrement() == 0) {
                    // Only removes if key still points to the same value,
                    // to avoid issue described below.
                    locks.remove(key, lock);
                }
            }
        }
    }

    private static final class CounterLock {

        private AtomicInteger remaining = new AtomicInteger(1);

        private CounterLock increment() {
            // Returning a new CounterLock object if remaining = 0 to ensure that
            // the lock is not removed in step 5 of the following execution sequence:
            // 1) Thread 1 obtains a new CounterLock object from locks.compute (after evaluating "v == null" to true)
            // 2) Thread 2 evaluates "v == null" to false in locks.compute
            // 3) Thread 1 calls lock.decrement() which sets remaining = 0
            // 4) Thread 2 calls v.increment() in locks.compute
            // 5) Thread 1 calls locks.remove(key, lock)
            return remaining.getAndIncrement() == 0 ? new CounterLock() : this;
        }

        private int decrement() {
            return remaining.decrementAndGet();
        }
    }
}

의 경우에는 영업 이익,그것은 것 같이 사용됩니다.

private KeySynchronizer<String> keySynchronizer = new KeySynchronizer<>();

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
    String key = "Data-" + email;
    return keySynchronizer.synchronize(key, () -> {
        SomeData[] existing = (SomeData[]) StaticCache.get(key);
        if (existing == null) {
            SomeData[] data = service.getSomeDataForEmail(email);
            StaticCache.set(key, data, CACHE_TIME);
            return data;
        }
        logger.debug("getSomeDataForEmail: using cached object");
        return existing;
    });
}

이 없는 경우에 반환되어야에서의 동기화된 코드,동기화 방법을 작성할 수 있습 다음과 같다:

public void synchronize(T key, Runnable runnable) {
    CounterLock lock = locks.compute(key, (k, v) -> 
            v == null ? new CounterLock() : v.increment());
    synchronized (lock) {
        try {
            runnable.run();
        } finally {
            if (lock.decrement() == 0) {
                // Only removes if key still points to the same value,
                // to avoid issue described below.
                locks.remove(key, lock);
            }
        }
    }
}

나는 실내 수영장,야외 수영장,사우나 자물쇠 클래스는 잠글 수 있습니다/동기화에 모든 키를 포함하여,문자열입니다.

시에 대한 구현 Java8,Java6 고 작은 시험입니다.

Java8:

public class DynamicKeyLock<T> implements Lock
{
    private final static ConcurrentHashMap<Object, LockAndCounter> locksMap = new ConcurrentHashMap<>();

    private final T key;

    public DynamicKeyLock(T lockKey)
    {
        this.key = lockKey;
    }

    private static class LockAndCounter
    {
        private final Lock lock = new ReentrantLock();
        private final AtomicInteger counter = new AtomicInteger(0);
    }

    private LockAndCounter getLock()
    {
        return locksMap.compute(key, (key, lockAndCounterInner) ->
        {
            if (lockAndCounterInner == null) {
                lockAndCounterInner = new LockAndCounter();
            }
            lockAndCounterInner.counter.incrementAndGet();
            return lockAndCounterInner;
        });
    }

    private void cleanupLock(LockAndCounter lockAndCounterOuter)
    {
        if (lockAndCounterOuter.counter.decrementAndGet() == 0)
        {
            locksMap.compute(key, (key, lockAndCounterInner) ->
            {
                if (lockAndCounterInner == null || lockAndCounterInner.counter.get() == 0) {
                    return null;
                }
                return lockAndCounterInner;
            });
        }
    }

    @Override
    public void lock()
    {
        LockAndCounter lockAndCounter = getLock();

        lockAndCounter.lock.lock();
    }

    @Override
    public void unlock()
    {
        LockAndCounter lockAndCounter = locksMap.get(key);
        lockAndCounter.lock.unlock();

        cleanupLock(lockAndCounter);
    }


    @Override
    public void lockInterruptibly() throws InterruptedException
    {
        LockAndCounter lockAndCounter = getLock();

        try
        {
            lockAndCounter.lock.lockInterruptibly();
        }
        catch (InterruptedException e)
        {
            cleanupLock(lockAndCounter);
            throw e;
        }
    }

    @Override
    public boolean tryLock()
    {
        LockAndCounter lockAndCounter = getLock();

        boolean acquired = lockAndCounter.lock.tryLock();

        if (!acquired)
        {
            cleanupLock(lockAndCounter);
        }

        return acquired;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
    {
        LockAndCounter lockAndCounter = getLock();

        boolean acquired;
        try
        {
            acquired = lockAndCounter.lock.tryLock(time, unit);
        }
        catch (InterruptedException e)
        {
            cleanupLock(lockAndCounter);
            throw e;
        }

        if (!acquired)
        {
            cleanupLock(lockAndCounter);
        }

        return acquired;
    }

    @Override
    public Condition newCondition()
    {
        LockAndCounter lockAndCounter = locksMap.get(key);

        return lockAndCounter.lock.newCondition();
    }
}

Java6:

공급 DynamicKeyLock 를 구현하는 자물쇠 { 개인 최종 정전 concurrenthashmap 의 locksMap=new concurrenthashmap 의();개인 최종 T 키;

    public DynamicKeyLock(T lockKey) {
        this.key = lockKey;
    }

    private static class LockAndCounter {
        private final Lock lock = new ReentrantLock();
        private final AtomicInteger counter = new AtomicInteger(0);
    }

    private LockAndCounter getLock()
    {
        while (true) // Try to init lock
        {
            LockAndCounter lockAndCounter = locksMap.get(key);

            if (lockAndCounter == null)
            {
                LockAndCounter newLock = new LockAndCounter();
                lockAndCounter = locksMap.putIfAbsent(key, newLock);

                if (lockAndCounter == null)
                {
                    lockAndCounter = newLock;
                }
            }

            lockAndCounter.counter.incrementAndGet();

            synchronized (lockAndCounter)
            {
                LockAndCounter lastLockAndCounter = locksMap.get(key);
                if (lockAndCounter == lastLockAndCounter)
                {
                    return lockAndCounter;
                }
                // else some other thread beat us to it, thus try again.
            }
        }
    }

    private void cleanupLock(LockAndCounter lockAndCounter)
    {
        if (lockAndCounter.counter.decrementAndGet() == 0)
        {
            synchronized (lockAndCounter)
            {
                if (lockAndCounter.counter.get() == 0)
                {
                    locksMap.remove(key);
                }
            }
        }
    }

    @Override
    public void lock()
    {
        LockAndCounter lockAndCounter = getLock();

        lockAndCounter.lock.lock();
    }

    @Override
    public void unlock()
    {
        LockAndCounter lockAndCounter = locksMap.get(key);
        lockAndCounter.lock.unlock();

        cleanupLock(lockAndCounter);
    }


    @Override
    public void lockInterruptibly() throws InterruptedException
    {
        LockAndCounter lockAndCounter = getLock();

        try
        {
            lockAndCounter.lock.lockInterruptibly();
        }
        catch (InterruptedException e)
        {
            cleanupLock(lockAndCounter);
            throw e;
        }
    }

    @Override
    public boolean tryLock()
    {
        LockAndCounter lockAndCounter = getLock();

        boolean acquired = lockAndCounter.lock.tryLock();

        if (!acquired)
        {
            cleanupLock(lockAndCounter);
        }

        return acquired;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
    {
        LockAndCounter lockAndCounter = getLock();

        boolean acquired;
        try
        {
            acquired = lockAndCounter.lock.tryLock(time, unit);
        }
        catch (InterruptedException e)
        {
            cleanupLock(lockAndCounter);
            throw e;
        }

        if (!acquired)
        {
            cleanupLock(lockAndCounter);
        }

        return acquired;
    }

    @Override
    public Condition newCondition()
    {
        LockAndCounter lockAndCounter = locksMap.get(key);

        return lockAndCounter.lock.newCondition();
    }
}

테스트:

public class DynamicKeyLockTest
{
    @Test
    public void testDifferentKeysDontLock() throws InterruptedException
    {
        DynamicKeyLock<Object> lock = new DynamicKeyLock<>(new Object());
        lock.lock();
        AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
        try
        {
            new Thread(() ->
            {
                DynamicKeyLock<Object> anotherLock = new DynamicKeyLock<>(new Object());
                anotherLock.lock();
                try
                {
                    anotherThreadWasExecuted.set(true);
                }
                finally
                {
                    anotherLock.unlock();
                }
            }).start();
            Thread.sleep(100);
        }
        finally
        {
            Assert.assertTrue(anotherThreadWasExecuted.get());
            lock.unlock();
        }
    }

    @Test
    public void testSameKeysLock() throws InterruptedException
    {
        Object key = new Object();
        DynamicKeyLock<Object> lock = new DynamicKeyLock<>(key);
        lock.lock();
        AtomicBoolean anotherThreadWasExecuted = new AtomicBoolean(false);
        try
        {
            new Thread(() ->
            {
                DynamicKeyLock<Object> anotherLock = new DynamicKeyLock<>(key);
                anotherLock.lock();
                try
                {
                    anotherThreadWasExecuted.set(true);
                }
                finally
                {
                    anotherLock.unlock();
                }
            }).start();
            Thread.sleep(100);
        }
        finally
        {
            Assert.assertFalse(anotherThreadWasExecuted.get());
            lock.unlock();
        }
    }
}

귀하의 경우에는 사용할 수있는 무언가 다음과 같이(이하지 않는 누수가 메모리):

private Synchronizer<String> synchronizer = new Synchronizer();

private SomeData[] getSomeDataByEmail(WebServiceInterface service, String email) {
    String key = "Data-" + email;

    return synchronizer.synchronizeOn(key, () -> {

        SomeData[] data = (SomeData[]) StaticCache.get(key);
        if (data == null) {
            data = service.getSomeDataForEmail(email);
            StaticCache.set(key, data, CACHE_TIME);
        } else {
          logger.debug("getSomeDataForEmail: using cached object");
        }
        return data;

    });
}

이 기능을 사용하려면 당신은 당 종속성을 추가:

compile 'com.github.matejtymes:javafixes:1.3.0'

당신은 안전하게 사용할 수 있는 문자열입니다.턴에 대한 동기화할 수 있는 경우 합리적으로 보장하는 문자열 값에서 고유의 시스템입니다.UUID 는 좋은 방법이다.에 연결할 수 있습 UUID 와 실제 문자열을 키거나,을 통해 캐시지도,또는 어쩌면 저장 uuid 필드에서 당신의 엔티티는 개체입니다.

    @Service   
    public class MySyncService{

      public Map<String, String> lockMap=new HashMap<String, String>();

      public void syncMethod(String email) {

        String lock = lockMap.get(email);
        if(lock==null) {
            lock = UUID.randomUUID().toString();
            lockMap.put(email, lock);
        }   

        synchronized(lock.intern()) {
                //do your sync code here
        }
    }
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top