Q: Why while instead of if?

Most people know the common use of synchronized code:

synchronized (obj) {
     while (check pass) {
        wait(a); } / /do your business
}
Copy the code

So the question is why is it “while” instead of “if”? I thought about it for a long time, and it was already in the synchronized block, so I didn’t need it. This is what I thought until recently, when I saw a problem on Stackoverflow and got a deeper understanding of this problem.

So let’s imagine we’re going to imagine a bounded queue. Then common code might look like this:

static class Buf {
    private final int MAX = 5;
    private final ArrayList<Integer> list = new ArrayList<>();
    synchronized void put(int v) throws InterruptedException {
        if (list.size() == MAX) {
            wait(a); } list.add(v); notifyAll(); } synchronized int get() throws InterruptedException { // line 0if (list.size() == 0) {  // line 1
            wait(a); // line2 // line 3 } int v = list.remove(0); // line 4 notifyAll(); // line 5return v;
    }

    synchronized int size() {
        returnlist.size(); }}Copy the code

Notice that we use “if” here, so let’s see what error does it report? The following code uses 1 thread to put and 10 threads to get:

final Buf buf = new Buf();
ExecutorService es = Executors.newFixedThreadPool(11);
for (int i = 0; i < 1; i++)
es.execute(new Runnable() {

    @Override
    public void run() {
        while (true ) {
            try {
                buf.put(1);
                Thread.sleep(20);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
                break; }}}});for (int i = 0; i < 10; i++) {
    es.execute(new Runnable() {

        @Override
        public void run() {
            while (true ) {
                try {
                    buf.get();
                    Thread.sleep(10);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                    break; }}}}); } es.shutdown(); es.awaitTermination(1, TimeUnit.DAYS);Copy the code

This code will quickly or initially report an error:

java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653) 
at java.util.ArrayList.remove(ArrayList.java:492) 
at TestWhileWaitBuf.get(TestWhileWait.java:80)atTestWhileWait2.run(TestWhileWait.java:47) 
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) 
at java.lang.Thread.run(Thread.java:745)
Copy the code

Obviously, I got an error on remove. So let’s analyze:

Suppose we now have two threads, A and B, to perform the GET operation. Let’s assume that the following steps occur:

1. User A obtains lock line 0.

2. A finds that size==0, (line 1), and then waits, and releases the lock (line 2).

3. At this time, B gets the lock, line0, finds size==0, (line 1), and then enters the wait and releases the lock (line 2).

NotifyAll notifyAll the waiting threads are notified when thread C adds data 1 to the notifyAll.

5. AB reacquires the lock, assuming A does it again. Then he goes to line 3, removes one data, (line4) no problem.

6. A calls notifyAll (line5) when the list size changes and wants to notify others after removing the data. NotifyAll (line5) awakens B and notifies B further down.

Size ==0; size==0; B thought it could be deleted, so he tried to delete it, but the result was abnormal.

So fix is simple, add while to get:

synchronized int get() throws InterruptedException {
      while (list.size() == 0) {
          wait(a); } int v = list.remove(0); notifyAll();return v;
  }
Copy the code

Similarly, we can try to change the number of put threads and the number of get threads to see that a put does not work if it is not a while.

We can print the size of the current list with an external periodic task, and you’ll see that the size is not a fixed maximum of 5:

final Buf buf = new Buf();
ExecutorService es = Executors.newFixedThreadPool(11);
ScheduledExecutorService printer = Executors.newScheduledThreadPool(1);
printer.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        System.out.println(buf.size());
    }
}, 0, 1, TimeUnit.SECONDS);
for (int i = 0; i < 10; i++)
es.execute(new Runnable() {

    @Override
    public void run() {
        while (true ) {
            try {
                buf.put(1);
                Thread.sleep(200);
            }
            catch (InterruptedException e) {
                 e.printStackTrace();
                break; }}}});for (int i = 0; i < 1; i++) {
    es.execute(new Runnable() {

        @Override
        public void run() {
            while (true ) {
                try {
                    buf.get();
                    Thread.sleep(100);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                    break; }}}}); } es.shutdown(); es.awaitTermination(1, TimeUnit.DAYS);Copy the code

I think it’s clear why it has to be while or if.

Q: When is a notifyAll or notify used?

Most people will tell you this, notifyAll when you want to notify everyone, and notify when you want to notify only one person. But as we all know with Notify, we can’t actually decide who to notify (we all choose one from the wait set). So what’s the point of this?

In the example above, we use notifyAll. Now let’s see if notifyAll works.

synchronized void put(int v) throws InterruptedException {
       if (list.size() == MAX) {
           wait(a); } list.add(v); notify(); } synchronized int get() throws InterruptedException {while (list.size() == 0) {
           wait(a); } int v = list.remove(0); notify();return v;
   }
Copy the code

Here’s what the JVM tells us:

  1. At any time, the thread that is awakened to execute is unpredictable. Let’s say I have five threads all on the same object, and I actually don’t know which thread is going to be executed next.

  2. Synchronized semantics enable one and only one thread to execute code in a synchronized block.

So let’s assume that the following scenario would cause a deadlock:

P – The producer calls put. C — The consumer calls GET.

1. P1 puts a number 1.

2. P2 is waiting in wait.

P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3 P3

C1 wants to get it. C2 and C3 are waiting inside get.

5. C1 starts execution, gets 1, then calls notify and exits.

  • If C1 wakes up C2, so P2 (everything else has to wait) has to wait in the put method. Waiting to get synchoronized (this) the monitor.

  • C2 checks the while loop and finds that the queue is empty, so it waits in wait.

  • C3 is also executed before P2, so the discovery is also empty and will have to wait.

6. P2, C2, and C3 are all waiting for the lock. Finally P2 gets the lock, puts a 1, notify, and exits.

  1. P2 wakes up P3, which finds that the queue is full and has no choice but to wait for it to become empty. 8. There are no further calls, and now all three threads (P3, C2, and C3) have become suspend.