Java 동시 대기/알림 메커니즘

8493 단어
선언
이 글은 모두 synchronizedReentrantLock에 대해 어느 정도 알고 있음을 묵인한다.
1.1 코드부터 풀어볼게요.
다음 간단한 코드는 주로 3개의 라인을 통해count를 누적하여 다중 라인을 모의하는 장면이다.
/**
 * zhongxianyao
 */
public class Test {
    private static final int N = 3;
    private int count = 0;

    public void doSomething() {
        //      ,                  
        for (int i=0; i<1000_000; i++) {
            synchronized (this) {
                count ++;
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Test test = new Test();
        for (int i=0; i test.doSomething();
            new Thread(runnable).start();
        }

        Thread.sleep(1000);
        System.out.println(test.count);
    }

}

다중 루틴 프로그래밍에서 start () 를 호출한 후에 언제 CPU 타임 슬라이드가 실행될지 확실하지 않고 얼마나 실행될지 확실하지 않기 때문에 때때로 경험에 따라 프로그램의 실행 시간을 예측한 다음sleep를 진행하여 결과를 얻을 수 있습니다.그러나 이런 방식은 너무 low적이다. 프로그램이 결과를 얻은 후에 알림을 하는 그런 방식이 있을까?다음은 오늘 말하고자 하는 대기/통지 메커니즘을 인용할 것이다.
2 Object wait()/notify()
2.1 엔트리 코드
먼저 코드 한 토막으로wait()/notify()의 기본 사용법을 살펴보겠습니다.
/**
 * zhongxianyao
 */
public class Test {

    private static final int N = 3;
    private int count = 0;
    private int finishCount = 0;

    public void doSomething() {
        for (int i=0; i<1000_000; i++) {
            synchronized (this) {
                count ++;
            }
        }
        synchronized (this) {
            finishCount ++;
            notify();
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        for (int i=0; i test.doSomething();
            new Thread(runnable).start();
        }

        synchronized (test) {
            try {
                while (test.finishCount != N) {
                    test.wait();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        System.out.println(test.count);
    }

}

결과 출력3000000, 결과는 정확하고 자신이 원하는 것이다.
2.2 문제 3연타
a. 왜 공식적으로wait()는while 안에 넣어야 한다고 말합니까?
인터페이스 설명은 다음과 같다.
As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
     synchronized (obj) {
         while ()
             obj.wait();
         ... // Perform action appropriate to condition
     }

한 논점 버전에서 중단과 거짓 각성은 가능하기 때문에 이 방법은 시종일관 하나의 순환에 두어야 한다.
덧붙여 자신의 해석을 하자면 일반적으로 프로젝트에서 한 라인이 이유 없이 기다릴 수 없고 항상 어떤 조건에서 기다려야 한다. 그리고 다른 라인이 이 라인을 깨울 때 notify All()을 사용할 수 있다. 데이터가 다른 라인에 의해 소비되기 때문에 특정한 조건을 충족시키는지 판단한 다음에 계속 운행해야 한다.
b. wait () 는 왜 동기화 방법/코드 블록에서 호출해야 합니까?
해석1:wait() 자체가 디자인한 논리는 자물쇠를 놓고 기다리는 것이다. 자물쇠를 얻지 못하면 어떻게 풀어야 하는지를 말한다.
해석2: 통상적으로wait()의 방법 앞에는while문장의 판단이 있는데 이 두 문장에 시간 간격이 있어 프로그램을 파괴할 수 있으며synchronized 동기화 코드 블록을 추가하여 원자 조작을 확보해야 한다.
c. 왜 wait(), notify()와 notifyAll()은 Thread가 아닌 Object에 정의됩니까?
wait() 등 방법은 모두 잠금 단계 작업이기 때문에 자바가 제공하는 잠금은 모두 대상 단계이지 라인 단계가 아니며 모든 대상에 잠금이 있다.wait () 방법이 Thread 클래스에 정의되어 있으면, 라인이 기다리고 있는 자물쇠가 뚜렷하지 않습니다.
2.3 wait(long timeout)
위의 예에서 notify(); 줄 코드가 삭제되고 wait()wait(100)로 바뀌면 프로그램은 정확한 결과를 얻을 수 있습니까?
/**
 * zhongxianyao
 */
public class Test {

    private static final int N = 3;
    private int count = 0;
    private int finishCount = 0;

    public void doSomething() {
        for (int i=0; i<1000_000; i++) {
            synchronized (this) {
                count ++;
            }
        }
        synchronized (this) {
            finishCount ++;
            //notify();
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        for (int i=0; i test.doSomething();
            new Thread(runnable).start();
        }

        synchronized (test) {
            try {
                while (test.finishCount != N) {
                    test.wait(100);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        System.out.println(test.count);
    }

}

운행 결과3000000는 정확한 결과입니다. 문서를 보니 이 필드는 직각이 이해하는 것과 다르다는 것을 발견했습니다. 직각은 나에게 이것이 가장 길고 얼마나 오래 기다렸는지 알려주었습니다. 너무 오래 기다리면 InterruptedException결과가 아닙니다.이 방법이 설정된 시간은 얼마나 기다리면 자신을 깨운다는 뜻이다.
3 Condition await()/signal()
3.1 Condition으로 교체
다음 코드는 이전 예의synchronized 코드 블록을 lock()/unlock으로 바꾸고, notify()를condition으로 바꾸었습니다.signal (),wait () 가 condition으로 바뀌었습니다.await().운행 결과도 정확하다.
/**
 * zhongxianyao
 */
public class Test {

    private static final int N = 3;
    private int count = 0;
    private int finishCount = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void doSomething() {
        for (int i=0; i<1000_000; i++) {
            synchronized (this) {
                count ++;
            }
        }
        lock.lock();
        finishCount ++;
        if (finishCount == N) {
            condition.signal();
        }
        lock.unlock();
    }

    public static void main(String[] args) {
        Test test = new Test();
        for (int i=0; i test.doSomething();
            new Thread(runnable).start();
        }

        test.lock.lock();
        try {
            test.condition.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            test.lock.unlock();
        }

        System.out.println(test.count);
    }

}

3.2 signal () 방법 이후 논리 추가를 권장하지 않음
public class ConditionTest {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
            try {
                long time = System.currentTimeMillis();
                lock.lock();
                System.out.println("await start");
                condition.await();
                System.out.println("await end " + (System.currentTimeMillis() - time) + "ms");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "Thread-await").start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        new Thread(() -> {
            try {
                lock.lock();
                System.out.println("signal start");
                TimeUnit.SECONDS.sleep(5);
                condition.signal();
                System.out.println("signal end");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("signal unlock");
            }
        }, "Thread-signal").start();

    }
}

여러 번 실행한 결과는 다음과 같습니다.
await start
signal start
signal end
signal unlock
await end 5005ms

실행 결과에서 알 수 있듯이 await () 후에는 자물쇠가 풀리지만,signal () 후에는 자물쇠가 풀리지 않습니다. 반드시 unlock () 이후에 자물쇠가 풀려야 await () 가 아래로 실행됩니다.
다른 라인을 깨웠는데 자물쇠를 놓지 않으니 깨울 시기를 조절할 수 있다.일반적으로 실제 코드에서도 signal () 방법을 권장하지 않고 논리를 추가하면 자물쇠를 직접 풀어야 합니다.
마찬가지로 위의 notify ()도synchronized 코드 블록이 끝난 후에wait () 뒤의 문장이 실제로 실행될 수 있습니다.
3.3 boolean await(long time, TimeUnit unit)
위의 condition.await()condition.await(1, TimeUnit.SECONDS)로 바꾸고 되돌아오는 값을 얻으면 실행 결과가 되돌아오는 것은 false입니다.
이때 TimeUnit.SECONDS.sleep(5), condition.signal() 두 줄의 코드 순서를 바꾸면 await의 반환값은 true이다.
이 반환값에 대한 공식 문서의 설명을 보면 다음과 같다.
{@code false} if the waiting time detectably elapsed
before return from the method, else {@code true}

대체적으로'대기시간이 되돌아오기 전에false로 되돌아오는 것을 감지할 수 있다면 아니면true로 되돌아간다'는 뜻이다.그러나 실제 테스트 결과await()는 방법이 돌아올 때가 아니라 깨어났을 때였다.
4 차이
  • Object wait() notify()와 synchronized를 조합해서 사용
  • Condition await() signal()과 Lock 사용
  • Object notify ()는 무작위로 깨우기
  • Condition signal ()은 첫 번째 await () 를 깨우는 스레드
  • Object wait()는 거짓으로 깨웠지만, Condition await()는 없었다
  • 좋은 웹페이지 즐겨찾기