[java] 객체가 잠겼는지 (동기화 됨) Java에서 차단되지 않도록 어떻게 결정합니까?

레코드 집합 (recordA, recordB 등)이있는 메모리에 테이블이 포함 된 프로세스 A가 있습니다.

이제이 프로세스는 레코드에 영향을주는 많은 스레드를 시작할 수 있으며 때로는 동일한 레코드에 액세스하려는 두 개의 스레드를 가질 수 있습니다.이 상황은 거부되어야합니다. 특히 레코드가 한 스레드에 의해 잠긴 경우 다른 스레드가 중단되기를 원합니다 (BLOCK 또는 WAIT를 원하지 않음).

현재 나는 다음과 같이한다.

synchronized(record)
{
performOperation(record);
}

그러나 이것은 나에게 문제를 일으키고 있습니다 … 왜냐하면 Process1이 작업을 수행하는 동안 Process2가 들어 오면 동기화 된 문을 차단 / 대기하고 Process1이 완료되면 작업을 수행하기 때문입니다. 대신 다음과 같은 것을 원합니다.

if (record is locked)
   return;

synchronized(record)
{
performOperation(record);
}

이것이 어떻게 이루어질 수 있는지에 대한 단서가 있습니까? 어떤 도움이라도 대단히 감사하겠습니다. 감사,



답변

한 가지 주목할 점은 그러한 정보를받는 즉시 부실하다는 것입니다. 즉, 아무도 잠금을 가지고 있지 않다는 말을 들었을 때 그것을 획득하려고 할 때 다른 스레드가 수표와 획득하려고하는 사이에 잠금을 해제했기 때문에 차단됩니다.

브라이언은를 지적하는 것이 옳지 Lock만, 당신이 정말로 원하는 것은 그 tryLock방법 이라고 생각합니다 .

Lock lock = new ReentrantLock();
......
if (lock.tryLock())
{
    // Got the lock
    try
    {
        // Process record
    }
    finally
    {
        // Make sure to unlock so that we don't cause a deadlock
        lock.unlock();
    }
}
else
{
    // Someone else had the lock, abort
}

tryLock대기 시간을두고 통화 할 수도 있습니다. 따라서 10 분의 1 초 동안 획득을 시도한 다음 얻을 수없는 경우 중단 할 수 있습니다 (예 :

(내가 아는 한 Java API가 Monitor클래스가 .NET에서 하는 것처럼 “내장”잠금에 대해 동일한 기능을 제공하지 않는 것이 유감이라고 생각합니다 . 스레딩과 관련하여 두 플랫폼 모두에서 내가 싫어하는 다른 것-예를 들어 잠재적으로 모니터가있는 모든 객체!)


답변

Java 5 동시성 패키지에 도입 된 Lock 객체를 살펴보십시오 .

예 :

Lock lock = new ReentrantLock()
if (lock.tryLock()) {
   try {
      // do stuff using the lock...
   }
   finally {
      lock.unlock();
   }
}
   ...

ReentrantLock와의 객체는 기본적으로 기존과 동일한 일을하고 synchronized메커니즘,하지만 더 많은 기능과 함께.

편집 : Jon이 언급 했듯이이 isLocked()방법은 그 순간에 당신에게 알려주고 그 이후에는 그 정보가 오래되었습니다. 의 tryLock () 메소드는 더 안정적인 작동 (당신은 시간 제한이 사용뿐만 아니라 수 있습니다)을 줄 것이다

편집 # 2 : 이제 tryLock()/unlock()명확성을 위해 예제가 포함 됩니다.


답변

나는 이것을 발견 Thread.holdsLock(Object obj)했고 , 우리 는 객체가 잠겨 있는지 확인하는 데 사용할 수 있습니다 .

true현재 스레드가 지정된 개체에 대한 모니터 잠금을 보유하는 경우에만 반환 합니다.

참고 Thread.holdsLock()반환 false잠금이 보유하면 뭔가 및 호출 스레드가 잠금을 수용하는 스레드가 아닙니다.


답변

Lock 객체를 사용하는 위의 접근 방식이 가장 좋은 방법이지만 모니터를 사용하여 잠금을 확인할 수 있어야한다면 그렇게 할 수 있습니다. 그러나이 기술은 Oracle Java가 아닌 VM으로 이식 할 수 없으며 지원되는 공용 API가 아니므로 향후 VM 버전에서 중단 될 수 있으므로 상태 경고가 표시됩니다.

방법은 다음과 같습니다.

private static sun.misc.Unsafe getUnsafe() {
    try {
        Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        return (Unsafe) field.get(null);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}

public void doSomething() {
  Object record = new Object();
  sun.misc.Unsafe unsafe = getUnsafe();
  if (unsafe.tryMonitorEnter(record)) {
    try {
      // record is locked - perform operations on it
    } finally {
      unsafe.monitorExit(record);
    }
  } else {
      // could not lock record
  }
}

내 조언은 java.util.concurrent Lock 객체를 사용하도록 코드를 리팩터링 할 수없고 Oracle VM에서 실행중인 경우에만이 접근 방식을 사용하는 것입니다.


답변

Lock 답변은 매우 좋지만 다른 데이터 구조를 사용하는 대안을 게시 할 것이라고 생각했습니다. 기본적으로 다양한 스레드는 어떤 레코드가 잠겨 있고 어떤 레코드가 잠겨 있는지 알고 싶어합니다. 이를 수행하는 한 가지 방법은 잠긴 레코드를 추적하고 데이터 구조에 잠긴 세트에 레코드를 추가하기위한 올바른 원자 적 작업이 있는지 확인하는 것입니다.

예를 들어 CopyOnWriteArrayList를 사용하겠습니다. 예를 들면 “마법”이 덜하기 때문입니다. CopyOnWriteArraySet이 더 적절한 구조입니다. 평균적으로 동시에 많은 레코드가 잠긴 경우 이러한 구현에 성능 영향이있을 수 있습니다. 적절하게 동기화 된 HashSet도 작동하고 잠금이 짧습니다.

기본적으로 사용 코드는 다음과 같습니다.

CopyOnWriteArrayList<Record> lockedRecords = ....
...
if (!lockedRecords.addIfAbsent(record))
    return; // didn't get the lock, record is already locked

try {
    // Do the record stuff
}
finally {
    lockedRecords.remove(record);
}

레코드 당 잠금을 관리 할 필요가 없으며 어떤 이유로 든 모든 잠금을 해제해야하는 경우 단일 위치를 제공합니다. 반면에 레코드가 몇 개 이상인 경우 추가 / 제거 조회가 선형이 아닌 O (1)이기 때문에 동기화 된 실제 HashSet이 더 좋을 수 있습니다.

사물을 보는 다른 방식입니다. 실제 스레딩 요구 사항에 따라 다릅니다. 개인적으로 저는 Collections.synchronizedSet (new HashSet ())을 사용합니다. 정말 빠르기 때문입니다.


답변

또 다른 해결 방법은 (여기에 제공된 답변에 대한 기회가없는 경우) 시간 초과를 사용하는 것입니다. 즉, 하나 아래는 1 초간 중단 된 후 null을 반환합니다.

ExecutorService executor = Executors.newSingleThreadExecutor();
        //create a callable for the thread
        Future<String> futureTask = executor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                return myObject.getSomething();
            }
        });

        try {
            return futureTask.get(1000, TimeUnit.MILLISECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            //object is already locked check exception type
            return null;
        }


답변

덕분에 경쟁 조건을 해결하는 데 도움이되었습니다. 벨트와 멜빵을 모두 착용하기 위해 조금 변경했습니다.

그래서 여기에 받아 들여지는 대답의 개선에 대한 제안이 있습니다.

tryLock()다음과 같은 작업을 수행 하여 메서드에 안전하게 액세스 할 수 있습니다 .

  Lock localLock = new ReentrantLock();

  private void threadSafeCall() {
    boolean isUnlocked = false;

    synchronized(localLock) {
      isUnlocked = localLock.tryLock();
    }

    if (isUnlocked) {
      try {
        rawCall();
      }
      finally {
        localLock.unlock();
      }
    } else {
      LOGGER.log(Level.INFO, "THANKS! - SAVED FROM DOUBLE CALL!");
    }
  }

이렇게 tryLock()하면 거의 동시에 두 번의 전화 를 받을 수있는 상황을 피할 수 있어 수익이 의심 스러울 수 있습니다. 지금 내가 틀렸다면 여기서 조심 스러울지도 몰라요. 하지만 헤이! 내 공연은 이제 안정적입니다 🙂 ..

블로그 에서 내 개발 문제에 대해 자세히 읽어보십시오 .