몇 가지 JAVA 세립도 자물쇠의 실현 방식

최근에 업무에서 일부 높은 병행 장면을 만났는데 자물쇠를 넣어 업무 논리의 정확성을 확보해야 하고 자물쇠를 넣은 후 성능이 큰 영향을 받지 않도록 요구한다.초보적인 생각은 데이터의 시간 스탬프, id 등 키워드를 통해 자물쇠를 채워 서로 다른 유형의 데이터 처리의 병발성을 확보하는 것이다.한편, 자바 자체api가 제공하는 자물쇠의 입도가 너무 커서 이런 수요를 동시에 만족시키기 어려워서 몇 가지 간단한 확장을 직접 썼습니다.
1. 세그먼트 자물쇠
concurrentHashMap의 단락 사상을 참고하여 선생은 일정한 수량의 자물쇠를 만들고 구체적으로 사용할 때 키에 따라 대응하는lock을 되돌려줍니다.이것은 몇 가지 실현에서 가장 간단하고 성능이 높으며 최종적으로 채택된 자물쇠 전략이다. 코드는 다음과 같다.

/**
 *  , , 
 *  : , !!!
 */
public class SegmentLock<T> {
  private Integer segments = 16;// 
  private final HashMap<Integer, ReentrantLock> lockMap = new HashMap<>();

  public SegmentLock() {
    init(null, false);
  }

  public SegmentLock(Integer counts, boolean fair) {
    init(counts, fair);
  }

  private void init(Integer counts, boolean fair) {
    if (counts != null) {
      segments = counts;
    }
    for (int i = 0; i < segments; i++) {
      lockMap.put(i, new ReentrantLock(fair));
    }
  }

  public void lock(T key) {
    ReentrantLock lock = lockMap.get((key.hashCode()>>>1) % segments);
    lock.lock();
  }

  public void unlock(T key) {
    ReentrantLock lock = lockMap.get((key.hashCode()>>>1) % segments);
    lock.unlock();
  }
}

2. 해시 자물쇠
상술한 단락 자물쇠를 토대로 발전된 두 번째 자물쇠 전략은 진정한 의미의 세립도 자물쇠를 실현하는 데 목적을 둔다.해시값이 다른 대상마다 독립된 자물쇠를 얻을 수 있다.테스트에서 잠긴 코드의 실행 속도가 매우 빠른 상황에서 효율은 단락 잠금보다 30% 정도 느리다.만약 장시간 조작이 있다면 표현이 더욱 좋을 것 같다.코드는 다음과 같습니다.

public class HashLock<T> {
  private boolean isFair = false;
  private final SegmentLock<T> segmentLock = new SegmentLock<>();// 
  private final ConcurrentHashMap<T, LockInfo> lockMap = new ConcurrentHashMap<>();

  public HashLock() {
  }

  public HashLock(boolean fair) {
    isFair = fair;
  }

  public void lock(T key) {
    LockInfo lockInfo;
    segmentLock.lock(key);
    try {
      lockInfo = lockMap.get(key);
      if (lockInfo == null) {
        lockInfo = new LockInfo(isFair);
        lockMap.put(key, lockInfo);
      } else {
        lockInfo.count.incrementAndGet();
      }
    } finally {
      segmentLock.unlock(key);
    }
    lockInfo.lock.lock();
  }

  public void unlock(T key) {
    LockInfo lockInfo = lockMap.get(key);
    if (lockInfo.count.get() == 1) {
      segmentLock.lock(key);
      try {
        if (lockInfo.count.get() == 1) {
          lockMap.remove(key);
        }
      } finally {
        segmentLock.unlock(key);
      }
    }
    lockInfo.count.decrementAndGet();
    lockInfo.unlock();
  }

  private static class LockInfo {
    public ReentrantLock lock;
    public AtomicInteger count = new AtomicInteger(1);

    private LockInfo(boolean fair) {
      this.lock = new ReentrantLock(fair);
    }

    public void lock() {
      this.lock.lock();
    }

    public void unlock() {
      this.lock.unlock();
    }
  }
}

3. 약한 인용 자물쇠
해시 자물쇠는 자물쇠의 생성과 소각의 동기화를 보장하기 위해 도입된 세그먼트 자물쇠 때문에 항상 약간의 흠이 느껴지기 때문에 세 번째 자물쇠를 써서 더욱 좋은 성능과 세립도의 자물쇠를 찾았다.이 자물쇠의 사상은 자물쇠의 약자를 빌려 자물쇠를 만들고 자물쇠의 소각을 jvm의 쓰레기 회수에 맡겨 추가 소모를 피하는 것이다.
아쉬운 점은 Concurrent Hash Map을 자물쇠로 하는 용기를 사용했기 때문에 진정한 의미에서 세그먼트 자물쇠에서 벗어나지 못했다는 점이다.이 자물쇠의 성능은 HashLock보다 10퍼센트 정도 빠르다.잠금 코드:

/**
 *  , 
 */
public class WeakHashLock<T> {
  private ConcurrentHashMap<T, WeakLockRef<T, ReentrantLock>> lockMap = new ConcurrentHashMap<>();
  private ReferenceQueue<ReentrantLock> queue = new ReferenceQueue<>();

  public ReentrantLock get(T key) {
    if (lockMap.size() > 1000) {
      clearEmptyRef();
    }
    WeakReference<ReentrantLock> lockRef = lockMap.get(key);
    ReentrantLock lock = (lockRef == null ? null : lockRef.get());
    while (lock == null) {
      lockMap.putIfAbsent(key, new WeakLockRef<>(new ReentrantLock(), queue, key));
      lockRef = lockMap.get(key);
      lock = (lockRef == null ? null : lockRef.get());
      if (lock != null) {
        return lock;
      }
      clearEmptyRef();
    }
    return lock;
  }

  @SuppressWarnings("unchecked")
  private void clearEmptyRef() {
    Reference<? extends ReentrantLock> ref;
    while ((ref = queue.poll()) != null) {
      WeakLockRef<T, ? extends ReentrantLock> weakLockRef = (WeakLockRef<T, ? extends ReentrantLock>) ref;
      lockMap.remove(weakLockRef.key);
    }
  }

  private static final class WeakLockRef<T, K> extends WeakReference<K> {
    final T key;

    private WeakLockRef(K referent, ReferenceQueue<? super K> q, T key) {
      super(referent, q);
      this.key = key;
    }
  }
}
 

후기
처음에 locksupport와 AQS를 빌려 세립도 자물쇠를 실현하려고 했는데 실현되고 있는 것을 발견한 것과 자바의 원생 자물쇠의 차이가 크지 않다고 쓰여 있어서 자바의 자물쇠에 대한 봉인으로 바꾸는 것을 포기하고 많은 시간을 낭비했다.
실제로 이러한 세립도 자물쇠를 실현한 후에 새로운 생각이 생겼다. 예를 들어 단락 사상을 통해 데이터를 전문적인 라인에 제출하여 처리할 수 있고 대량의 라인의 막힘 시간을 줄일 수 있으며 나중에 탐색할 수 있다.

좋은 웹페이지 즐겨찾기