Wait / notify mechanism for Java concurrency

1 Preface

This article assumes that you have a certain understanding of synchronized and reentrantlock.

1.1 let's relax with a piece of code

The following simple code mainly simulates the multithreading scenario by accumulating count through three threads.

/**
 * 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<N; i++) {
            Runnable runnable = () -> test.doSomething();
            new Thread(runnable).start();
        }

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

}

In multithreaded programming, once start() is called, it is uncertain when the CPU time slice will be allocated to run, and how long it will run. Therefore, sometimes it is possible to estimate the running time of the program according to experience, then sleep, and finally obtain the results. But this method is too low. Is there a way to notify the program when it obtains the results? The following will lead to the waiting / notification mechanism to be talked about today.

2 Object wait()/notify()

2.1 an entry code

Let's take a look at the basic usage of 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<N; i++) {
            Runnable runnable = () -> 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);
    }

}

The result is 3000000, and the result is correct, which is what you want.

2.2 problem triple strike

a. Why does the official say wait () should be put in while?

The interface is described below

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 (<condition does not hold>)
             obj.wait();
         ... // Perform action appropriate to condition
     }

In an argument version, interrupts and false wakes are possible, so this method should always be placed in a loop.

Add your own explanation: in general, in a project, a thread cannot wait for no reason. It always needs to wait under certain conditions. When other threads wake up this thread, they may use notifyall(). The data is consumed by other threads. Here, you need to judge whether specific conditions are met before continuing to run.

B. what does wait () have to invoke in the synchronous method / block of code?

Explanation 1: the logic designed by wait () itself is to wait while releasing the lock. If the lock is not obtained, how to release it.

Explanation 2: the while statement is usually judged in front of the wait () method. There will be a time interval between these two statements, which may damage the program. Synchronized synchronization code blocks need to be added to ensure atomic operation.

c. Why are wait(), notify() and notifyall() defined in object instead of thread?

Because wait () and other methods are lock level operations, and the locks provided by Java are object level rather than thread level. Each object has a lock. If the wait () method is defined in the thread class, it is not obvious which lock the thread is waiting for.

2.3 wait(long timeout)

In the above example, if notify(); Delete that line of code and change wait () to wait (100), as follows. Can the program get the correct results?

/**
 * 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<N; i++) {
            Runnable runnable = () -> 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);
    }

}

The running result is 3000000, which is the correct result. After looking at the document, I found that this field is different from the intuitive understanding. The intuition tells me how long to wait. If you wait too long, you will get an interruptedexception. The result is not. The time set by this method means how long you wait to wake yourself up.

3 Condition await()/signal()

3.1 replace with condition

The following code replaces the synchronized code block in the previous example with lock() / unlock and notify() with condition Signal(), wait() is replaced by condition await()。 The running result is also correct.

/**
 * 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<N; i++) {
            Runnable runnable = () -> 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 it is not recommended to add logic after the signal() method

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();

    }
}

After multiple runs, the results are the same, as follows:

await start
signal start
signal end
signal unlock
await end 5005ms

It can be seen from the running results that after await (), the lock is released, but after signal (), the lock is not released. The lock must be released after unlock (), and await () will be executed downward.

Since you wake up other threads without releasing the lock, you can adjust the wake-up time. Generally, in actual code, it is not recommended to add logic after the signal () method, and the lock should be released directly.

Similarly, the above notify () statement can only be executed after the synchronized code block is completed.

3.3 boolean await(long time,TimeUnit unit)

Put the above condition Await() is changed to condition Await (1, timeunit. Seconds), and then get the return value. The running result returns false.

At this time, if you put timeunit SECONDS. sleep(5),condition. If the order of the two lines of code signal () is changed, the return value of await is true.

See the description of the return value in the official document, as follows

{@code false} if the waiting time detectably elapsed
before return from the method,else {@code true}

If the waiting time can be detected before the method returns, it will return false, otherwise it will return true. But the actual test result is when await () is awakened, not when the method returns.

4 difference

5 reference documents

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>