program tip

Java : 동기화 된 블록에서 wait () 잠금 해제

radiobox 2020. 12. 28. 08:00
반응형

Java : 동기화 된 블록에서 wait () 잠금 해제


나는 wait ()가 모든 잠금을 해제한다는 인상을 받았지만이 게시물을 발견했습니다.

"동기화 된 메서드 내에서 대기를 호출하는 것은 내부 잠금을 획득하는 간단한 방법입니다."

내가 약간 헷갈 린다는 점을 명확히 해주세요.

http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html


"동기화 된 메서드 내에서 대기를 호출하는 것은 내부 잠금을 획득하는 간단한 방법입니다."

이 문장은 거짓이며 문서상의 오류입니다.

스레드는 동기화 된 메서드에 들어갈 때 고유 잠금을 획득합니다 . 동기화 된 메서드 내부의 스레드는 잠금 소유자로 설정되고 RUNNABLE 상태입니다. 잠긴 메서드를 입력하려는 모든 스레드는 BLOCKED가 됩니다.

스레드 호출이 대기하면 현재 객체 잠금이 해제되고 (다른 객체의 모든 잠금이 유지됨) 대기 상태가됩니다.

다른 스레드가 동일한 객체에서 notify 또는 notifyAll을 호출하면 첫 번째 스레드가 상태를 WAITING에서 BLOCKED로 변경하면 Notified 스레드가 자동으로 잠금을 다시 획득하거나 RUNNABLE이되지 않으며, 실제로 다른 모든 차단 된 스레드와 함께 잠금을 위해 싸워야합니다.

WAITING 및 BLOCKED 상태는 둘 다 스레드 실행을 방지하지만 매우 다릅니다.

WAITING 스레드는 다른 스레드의 알림에 의해 명시 적으로 BLOCKED 스레드로 변환되어야합니다.

WAITING은 RUNNABLE로 직접 이동하지 않습니다.

RUNNABLE 스레드가 잠금을 해제하면 (모니터를 떠나거나 대기하여) BLOCKED 스레드 중 하나가 자동으로 그 자리를 차지합니다.

요약하면 스레드는 동기화 된 메소드에 진입하거나 대기 동기화 된 메소드에 다시 진입 할 때 잠금을 획득합니다 .

public synchronized guardedJoy() {
    // must get lock before entering here
    while(!joy) {
        try {
            wait(); // releases lock here
            // must regain the lock to reentering here
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}

대기가 실제로 잠금을 해제한다는 것을 보여주기 위해 작은 테스트 클래스 (매우 더러운 코드, 죄송합니다)를 준비했습니다.

public class Test {
    public static void main(String[] args) throws Exception {
        testCuncurrency();
    }

    private static void testCuncurrency() throws InterruptedException {
        Object lock = new Object();
        Thread t1 = new Thread(new WaitTester(lock));
        Thread t2 = new Thread(new WaitTester(lock));
        t1.start();
        t2.start();
        Thread.sleep(15 * 1000);
        synchronized (lock) {
            System.out.println("Time: " + new Date().toString()+ ";" + "Notifying all");
            lock.notifyAll();
        }
    }

    private static class WaitTester implements Runnable {
        private Object lock;
        public WaitTester(Object lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            try {
                synchronized (lock) {
                    System.out.println(getTimeAndThreadName() + ":only one thread can be in synchronized block");
                    Thread.sleep(5 * 1000);

                    System.out.println(getTimeAndThreadName() + ":thread goes into waiting state and releases the lock");
                    lock.wait();

                    System.out.println(getTimeAndThreadName() + ":thread is awake and have reacquired the lock");

                    System.out.println(getTimeAndThreadName() + ":syncronized block have finished");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private static String getTimeAndThreadName() {
        return "Time: " + new Date().toString() + ";" + Thread.currentThread().getName();
    }
}

내 컴퓨터에서이 클래스를 실행하면 다음 결과가 반환됩니다.

Time: Tue Mar 29 09:16:37 EEST 2016;Thread-0:only one thread can be in synchronized block
Time: Tue Mar 29 09:16:42 EEST 2016;Thread-0:thread goes into waiting state and releases the lock
Time: Tue Mar 29 09:16:42 EEST 2016;Thread-1:only one thread can be in synchronized block
Time: Tue Mar 29 09:16:47 EEST 2016;Thread-1:thread goes into waiting state and releases the lock
Time: Tue Mar 29 09:16:52 EEST 2016;Notifying all
Time: Tue Mar 29 09:16:52 EEST 2016;Thread-1:thread is awake and have reacquired the lock
Time: Tue Mar 29 09:16:57 EEST 2016;Thread-1:syncronized block have finished
Time: Tue Mar 29 09:16:57 EEST 2016;Thread-0:thread is awake and have reacquired the lock
Time: Tue Mar 29 09:17:02 EEST 2016;Thread-0:syncronized block have finished

wait::는 java.lang.Object클래스의 일부 이므로 객체에서만이 메서드를 호출 할 수 있습니다. 이를 호출하면 해당 객체에서 monitor (lock)가 필요합니다. 그렇지 않으면 IllegalMonitorStateExceptionthrow됩니다. 예) Thread.currentThread (). wait ()는 아래 코드에서이 예외를 throw합니다.

   Example1
   public void doSomething() {                                          Line 1
        synchronized(lockObject) { //lock acquired                      Line 2
            lockObject.wait();     // NOT Thread.currentThread().wait() Line 3
        }
    }

이제 라인 3에서 wait를 호출하면 라인 2에서 획득 한 잠금이 해제됩니다. 따라서 라인 1에 입력 된 다른 스레드와 잠금 획득 대기 lockObject는이 잠금을 획득하고 진행합니다.

Now let us consider this Example2; here only lockObject2 lock is released, and still current thread holds lockObject1 lock. This will leads to deadlock; So user should be more careful on this case.

   Example2 
        public void doSomething() {                                     Line 1
             synchronized(lockObject1) { //lock1 acquired               Line 2
                 synchronized(lockObject2) { //lock2 acquired           Line 3
                     lockObject2.wait();                                Line 4
                 }
             }
        }

If this wait is replaced with sleep, yield, or join they dont have cabaility to release the lock. Only wait can release lock that it holds.

Just cautious on t1.sleep()/t1.yield() where are static api's and always action will be performed on the currentThread not on thread t1.

Then let us understand what is the difference between suspend and these api's sleep, yield, join; because suspend is deprecated to avoid situation of the thread is holding the lock which will leads to deadlock when it is in suspended (not running state) for undefined time. This is the same behavior for other apis as well.

The answer is suspend/resume will be performed on other threads, like t1.suspend() where as these api's are suspending the Thread.currentThread(). Hence user has caution note on taking care of not holding any locks before calling these api's to avoid deadlock. This is not the case when calling suspend. The callee thread don't know about the caller thread (lock) state on which it is going to perform suspend, hence deprecated.


I think this statement should be seen within its full context.

When a thread invokes d.wait, it must own the intrinsic lock for d — otherwise an error is thrown. Invoking wait inside a synchronized method is a simple way to acquire the intrinsic lock.

I understand that they should simplify this to be like:

Invocation of synchronized methods acquires lock on object, we can simply put a wait() invocation inside a synchronized method.

ReferenceURL : https://stackoverflow.com/questions/13249835/java-does-wait-release-lock-from-synchronized-block

반응형