Multithreaded learning - basics (6) analysis wait()-notify()-notifyAll()

1. Understand wait()-notify()-notifyAll()
obj.wait() and obj.notify() must be used together with synchronized(Obj), that is, wait and notify operate on the Obj lock that has been acquired;
Syntactically : Obj.wait() and Obj.notify() must be inside the synchronized(Obj){..}
block.
Functionally, wait() means that after the thread acquires the object lock, it actively releases the object lock, and at the same time, the thread sleeps until another thread calls the object's notify() to wake up the thread, then it can continue to acquire the object lock and continue to execute. Correspondingly, notify() is the wake-up operation of the object lock.
It is worth noting that : after the notify() call, the object lock is not released immediately, but after the execution of the corresponding synchronized(Obj){...} statement block ends, after the lock is automatically released, the JVM will wait() the object Randomly select a thread among the threads of the lock, assign its object lock, wake up the thread, and continue to execute. This provides inter-thread synchronization, wake-up operations.
Both Thread.sleep(millis) and Object.wait() can suspend the current thread and release the control of the CPU. The main difference is that Object.wait() releases the control of the object lock while releasing the control of the CPU. And Thread.sleep(millis) still retains control of the object lock.
The functions of Object.notify() and Object.notifyAll() are to wake up the thread that has released the object lock on the Object object and is in a waiting state. The difference is: notify() is to wake up one of them randomly, while notifyAll() is to wake up all of them.

According to the above concept description, now I am a little confused, which is listed as follows: (refer to the state transition diagram)

  (1) Can the lock of an object be acquired by multiple threads at the same time? 

(2) For example, a scenario: when the A thread holding the Obj object lock calls the Obj.wait() method in the code block of synchronized(Obj){...} in run(), in theory, at this time Thread A will release the lock of the Obj object, and thread A will enter the waiting queue.

          Question 1 : Does thread A release the Obj object lock immediately, or after the synchronized(Obj){...} code block is executed.
      Scenario supplement:
                 When thread A releases the object lock of Obj, a thread B also executes synchronized(Obj){...} in the run() method. First of all, thread B must control Obj's object lock at this time. Object lock, no other threads can operate the Obj object, only the B thread can. At this time, thread B executes the Obj.notify() method. After thread B finishes executing the synchronized(Obj){...} code block and releases the Obj object lock, the JVM scheduling mechanism will randomly select one on the Obj object. The waiting thread is awakened (not necessarily the A thread), and the thread after awakening enters the lock pool state. We can see the conversion process from the above analysis diagram, so the question is coming?
          Question 2 : What is the status of the lock pool?
          Question 3 : According to the transition diagram, we can know that the threads in the waiting queue can be automatically awakened by Obj.notify() or Obj.notifyAll() or wait() and enter the lock pool state, that is to say, the threads in the lock pool state There can be multiple threads. Continue the conversion diagram and look down. Can a thread in the lock pool state get the lock mark of the object or can all threads get the mark? Is it the front control thread that gets the lock mark? , at this time can enter the runnable state (ie ready state)

  Only analyzing the theory will cause a lot of confusion. You must practice to verify and analyze these problems step by step and find the answer, so let's do it!

 2. Simple case analysis: wait() and notify()

 

 The most classic example of the application of Object.wait() and Object.notify() should be the problem of printing ABC with three threads. This is a relatively classic interview question. The title requirements are as follows:
Case requirements:
  establish three threads A, B, C, thread A prints 10 times A, thread B prints 10 times B, and thread C prints 10 times C. The threads are required to run at the same time and alternately print ABC 10 times.

 

This problem can be easily solved with Object.wait() and Object.notify(). The code is as follows:

 

package com.jason.comfuns.wait;
/**
 * Multi-threaded learning
 * @function wait() method test
 * @author Xiaofeng Chiliang
 * @time 2018-4-22 9:25:43 am
 */
public class Thread_wait_Action implements Runnable {
    //设置属性
    private String name;
    private Object prev;
    private Object self;    
    //构造器
    public Thread_wait_Action(String name,Object prev,Object self){
        this.name=name;
        this.prev=prev;
        this.self=self;
    }
    //线程run()
    public void run() {
        int count=10;
        while(count>0){
            synchronized (prev) {
                synchronized (self) {
                    System.out.print(name);    
                    count--;
                    self.notify(); // wake up the thread on this object 
                }
                 try {
                    prev.wait(); // Wait on this object, don't continue the loop until woken up, and so on 
                } catch (InterruptedException e) {
                    e.printStackTrace ();
                }
            }
        }        
    }
    public  static  void main(String[] args) throws InterruptedException {
         // Create three meeting objects and have three object locks 
        Object a= new Object();
        Object b=new Object();
        Object c = new Object();
         // Create three threads 
        Thread_wait_Action thread1= new Thread_wait_Action("A" ,c,a);
        Thread_wait_Action thread2=new Thread_wait_Action("B",a,b);
        Thread_wait_Action thread3=new Thread_wait_Action("C",b,c);
        //启动线程
        new Thread(thread1).start();
        Thread.sleep( 100);   // Make sure to execute new Thread(thread2).start() in order A, B, C   ;
        
        Thread.sleep(100);
        new Thread(thread3).start();
        Thread.sleep(100);
    }
}

 

operation result:

ABCABCABCABCABCABCABCABCABCABC

Code idea analysis:

Three threads: ABC
Three objects: abc
// Create three threads 
Thread_wait_Action thread1= new Thread_wait_Action("A" ,c,a);
Thread_wait_Action thread2=new Thread_wait_Action("B",a,b);
Thread_wait_Action thread3=new Thread_wait_Action("C",b,c);
        
The first execution: A thread controls the object lock prev =c self= a Printout: A Operation: a.notify() wakes up other threads and then c.wait()Analyze the operation permissions of 
        thread A on c / a objects at this time:
            Object permissions
            a None: a.notify() released
            c None: c.wait() released     
        Result: thread A wakes up the thread waiting on object a, and thread A starts waiting on object c to interrupt the loop: count = 9 at this point
Second execution: B or C thread
    Assumption 1: Running B thread
        B thread control object lock prev =a selef= b Printout: B Operation: b.notify() wakes up other threads and then a.wait()Analyze the operation permissions of the 
        B thread on the a / b object at this time:
            Object permissions
            a None: a.wait() released
            b no b.notify() released
        Result: B thread wakes up the thread waiting on b object, and B thread starts waiting on a object to interrupt the loop: count = 9 at this time
    Assumption 2: Running a C thread, does not hold  
          Reason: Thread.sleep( 100);   // Ensure that A, B, C are executed in order  

The third execution: C thread control object lock prev =b self= c Printout: C Operation: c.notify() wakes up other threads Then b.wait()Analyze the operation permissions of the 
        c thread on the b / c object at this time:
            Object permissions
            b None: b.wait() released
            c no c.notify() released
        Result: The C thread wakes up the thread waiting on the c object, and the C thread starts waiting on the b object to interrupt the loop: at this point count = 9

Summary after one loop (every third thread execution is a loop):
Printout: ABC
A thread: waiting on the c object, eager to get the object lock of the c object to continue execution. wait for c to wake up for a
B thread: waiting on the a object, eager to get the object lock of the a object to continue execution. wait c wake up b
C thread: waiting on the b object, eager to get the object lock of the b object to continue execution. wait b wake up c
So: A wakes up B, B wakes up C, and C wakes up A again.
It can be seen that after a loop, the thread will start executing again from the A thread until count = 0, 10 loops are over!

 

It should be noted that: at the beginning of execution, you need to control the execution order of threads: as follows

  // Start thread 
        new Thread(thread1).start();
        Thread.sleep( 100);   // Make sure to execute new Thread(thread2).start() in order A, B, C   ;
        
        Thread.sleep(100);
        new Thread(thread3).start();
        Thread.sleep( 100); 
//Run result
ABCABCABCABCABCABCABCABCABCABCABC

 

 What if there is no Thread.sleep(100) when threads thread1, thread2, thread3 are started?

//启动线程
        new Thread(thread1).start();
        new Thread(thread2).start();
        new Thread(thread3).start();
//运行结果
  CBACBACBACBACBACBACBACBACBACBA
或者
ACABCABCABCABCABCABCABCABCABC
或者
  ......

 

可以看出:如果不控制初始启动线程的顺序,那么打印输入的结果就变得不确定了!

三.简单案例分析:锁(monitor)池和等待池 

    在Java中,每个对象都有两个池,锁(monitor)池和等待池。
  wait(),notify(),notifyAll()三个方法都是Object类的方法。 

  锁池
    假设A线程拥有某个对象(注意不是类)的锁,而其他的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获取这个对象锁的控制权,但该对象的锁目前正被线程A拥有,

  所以这些线程就进入了该对象的锁池中。

  等待池
    假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()之前,线程A就已经拥有了该对象的锁),同时A进入到了该对象的等待池中。如果另外一个线程调用了相同对象

  的notifyAll()方法,那么处于该对象等待池中的所有线程全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外一个线程调用了相同对象的notify()方法,那么仅仅让一个处于高对象的等待池中的线程(随机选取的线程)进入该对象的等待池。

下面通过一个简单的案例来说明:

package com.jason.comfuns.monitors;
/**
 * 多线程学习
 * @function  测试:Object的锁池和等待池
 * @author 小风微凉
 * @time  2018-4-22 上午11:48:53
 */
public class Thread_ObjectMonitor_Action {
    public static void main(String[] args)  
    {  
        //对象
        Target t = new Target(); 
        //创建线程 
        Thread thread1 = new Increase(t);  
        thread1.setName("+");  
        Thread thread2 = new Decrease(t);  
        thread2.setName("-");  
        //启动线程
        thread1.start();  
        thread2.start();  
    }  
}
class Target{
     private int count; 
     public synchronized void increase(){
         System.out.println(Thread.currentThread().getName()+"线程被唤醒:count="+count); 
         if(count==2){
             try {
                System.out.println(Thread.currentThread().getName()+"线程开始wait休眠,进入等待池"); 
                this.wait();//当前该对象的上的线程进入等待池中
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
         }
         count++;  
         System.out.println(Thread.currentThread().getName()+"线程释放对象锁,并唤醒t对象上的其他等待线程,count="+count);
         this.notify();//唤醒该对象的上等待池中的随机一个线程进入锁池中
     }
     public synchronized void decrease(){
         System.out.println(Thread.currentThread().getName()+"线程被唤醒:count="+count); 
         if(count == 0)  
            {  
                try  
                { 
                    System.out.println(Thread.currentThread().getName()+"线程开始wait休眠,进入等待池"); 
                    this.wait();//当前该对象的上的线程进入等待池中
                }  
                catch (InterruptedException e)  
                {  
                    e.printStackTrace();  
                }  
            }  
            count--;   
            System.out.println(Thread.currentThread().getName()+"线程释放对象锁,并唤醒t对象上的其他等待线程,count="+count);
            this.notify();  //唤醒该对象的上等待池中的随机一个线程进入锁池中
     }
}
class Increase extends Thread{
    private Target t;  
    public Increase(Target t) { this.t = t;  }  
    public void run()  
    {     
        for(int i = 0 ;i < 5; i++)  
        {  
            try  
            {  
                Thread.sleep((long)(Math.random()*500));  
            }  
            catch (InterruptedException e)  
            {  
                e.printStackTrace();  
            } 
            System.out.println(Thread.currentThread().getName()+"第"+(i+1)+"次");
            t.increase();  //调用对象t的synchronized方法
        }
    }  
}
class Decrease extends Thread  
{   
    private Target t;  
    public Decrease(Target t){this.t = t;}  
    public void run()  
    {  
        for(int i = 0 ; i < 5 ; i++)  
        {  
            try  
            {  
                //随机睡眠0~500毫秒  
                //sleep方法的调用,不会释放对象t的锁  
                Thread.sleep((long)(Math.random()*500));  
            }  
            catch (InterruptedException e)  
            {  
                e.printStackTrace();  
            } 
            System.out.println(Thread.currentThread().getName()+"第"+(i+1)+"次");
            t.decrease(); //调用对象t的synchronized方法
        }  
    } 
}  

 

 运行结果:

-第1次
-线程被唤醒:count=0
-线程开始wait休眠,进入等待池
+第1次
+线程被唤醒:count=0
+线程释放对象锁,并唤醒t对象上的其他等待线程,count=1
-线程释放对象锁,并唤醒t对象上的其他等待线程,count=0
-第2次
-线程被唤醒:count=0
-线程开始wait休眠,进入等待池
+第2次
+线程被唤醒:count=0
+线程释放对象锁,并唤醒t对象上的其他等待线程,count=1
-线程释放对象锁,并唤醒t对象上的其他等待线程,count=0
+第3次
+线程被唤醒:count=0
+线程释放对象锁,并唤醒t对象上的其他等待线程,count=1
-第3次
-线程被唤醒:count=1
-线程释放对象锁,并唤醒t对象上的其他等待线程,count=0
+第4次
+线程被唤醒:count=0
+线程释放对象锁,并唤醒t对象上的其他等待线程,count=1
-第4次
-线程被唤醒:count=1
-线程释放对象锁,并唤醒t对象上的其他等待线程,count=0
+第5次
+线程被唤醒:count=0
+线程释放对象锁,并唤醒t对象上的其他等待线程,count=1
-第5次
-线程被唤醒:count=1
-线程释放对象锁,并唤醒t对象上的其他等待线程,count=0

 

 结果分析:

  (1)根据上面的代码,可以知道“+”和“-”这两个线程的执行时没有先后顺序的。

  分析:(后面的结果截取均来至下面的结果部分

-第1次
-线程被唤醒:count=0
-线程开始wait休眠,进入等待池
+第1次
+线程被唤醒:count=0
+线程释放对象锁,并唤醒t对象上的其他等待线程,count=1
-线程释放对象锁,并唤醒t对象上的其他等待线程,count=0
-第2次
-线程被唤醒:count=0
-线程开始wait休眠,进入等待池
+第2次
+线程被唤醒:count=0
+线程释放对象锁,并唤醒t对象上的其他等待线程,count=1
-线程释放对象锁,并唤醒t对象上的其他等待线程,count=0

 

 可以看出;

  "-"线程第一次执行的时候,就休眠了,“-”线程进入等待池

-第1次
-线程被唤醒:count=0
-线程开始wait休眠,进入等待池

 

 紧接着“+”线程在target对象的锁池中,成功抢夺了target对象的锁的控制权,开始执行

+第1次
+线程被唤醒:count=0
+线程释放对象锁,并唤醒t对象上的其他等待线程,count=1

 根据代码可以知道,在“+”线程会调用:

 this.notify();//唤醒该对象的上等待池中的随机一个线程进入锁池中

 

然后“+”线程的任务完成,并成功释放了target对象的锁,并唤醒了“-”线程,是的“-”线程从等待池中转移到了锁池中,此时“+”线程和“-”线程同时存在于锁池中,并且两个线程的优先级别是一样的(由于都没有设置优先级,所有优先界别都默认为:NORM_PRIORITY=5)

此时:“+”线程和“-”线程开始抢夺target对象的控制权。谁抢到,谁就继续开始执行。

我们继续看一下代码:(以:increase()方法为例

 if(count == 0)  
            {  
                try  
                { 
                    System.out.println(Thread.currentThread().getName()+"线程开始wait休眠,进入等待池"); 
                    this.wait();//当前该对象的上的线程进入等待池中
                }  
                catch (InterruptedException e)  
                {  
                    e.printStackTrace();  
                }  
            }  
            count--;   
            System.out.println(Thread.currentThread().getName()+"线程释放对象锁,并唤醒t对象上的其他等待线程,count="+count);
            this.notify();  //唤醒该对象的上等待池中的随机一个线程进入锁池中

 

可以看到如果线程被notify()唤醒并且继续执行的话,会继续执行this.wait()后面的代码:

        count--;   
            System.out.println(Thread.currentThread().getName()+"线程释放对象锁,并唤醒t对象上的其他等待线程,count="+count);
            this.notify();  //唤醒该对象的上等待池中的随机一个线程进入锁池中

 

 那么我们继续看一下运行结果:观察是否是这样的现象!!!! 

-第1次
-线程被唤醒:count=0
-线程开始wait休眠,进入等待池
+第1次
+线程被唤醒:count=0
+线程释放对象锁,并唤醒t对象上的其他等待线程,count=1
-线程释放对象锁,并唤醒t对象上的其他等待线程,count=0
-第2次
-线程被唤醒:count=0
-线程开始wait休眠,进入等待池
+第2次
+线程被唤醒:count=0
+线程释放对象锁,并唤醒t对象上的其他等待线程,count=1
-线程释放对象锁,并唤醒t对象上的其他等待线程,count=0

 

 可以看出,继续执行的话是:“-”线程抢夺到了target对象的锁,并且确实是继续执行this.wait()后面的代码。此时:又会执行一次this.notify(),"-"线程仍然后释放target对象的锁,然后存在于锁池中,继续和锁池中另外一个线程:“+”线程继续抢夺target对象锁的控制权。

后面的显示结果,就是这样以此类推,一次往后执行。

需要注意的是:控制线程执行个数的控制源在:

public void run()  
{     
        for(int i = 0 ;i < 5; i++)  
        {  
            //......
        }
}

 

 四、归纳总结

 (1)最开始的疑惑在上面的两个简单案例中已经得到了明确的答案;(如果有不对的地方,请指正,一起交流共同进步。)

  • 问:“一个对象的锁可以同时被多个线程拿到吗?”   

   答案:当然不可以,一个对象的锁只能被一个线程锁拥有。只有当前线程释放了该对象的对象锁,其他在该对象锁池中的线程才有机会彼此竞争抢夺该对象的对象锁。

  • 问:“一个线程在run()的synchronized方法或synchronized块中,如果要释放对象锁是立马释放还是synchronized结束之后再释放?”

  答案:这个要分情况了。

  1. this.wait()方式释放对象锁:

      this.wait()这句代码执行之后,根据上面我做的案例测试得出的结果是,会立即释放对象锁。  

  2.this.notify()方式释放对象锁: 

      this.notify()的作用是释放当前线程所拥有的对象的对象锁,然后再在锁池中和该对象上的其他线程一起竞争抢夺该对象的对象锁。

    注意:

      this.notify()这句代码执行之后,并不会立马释放该对象的对象锁,而是继续执行this.notify()后面的代码,直到synchronized方法或synchronized块中的代码执行完毕,才会开始释放对象锁,而只有释放对象锁完毕之后,该对象的对象锁才能够被开始竞争抢夺! 

   3.问:“锁池状态是什么状态?”

    答案:见上面的第二个简单案例,里面有分析说明。

   4.问:“锁池状态中的某一条线程可以拿到对象的锁标记还是所有的线程都可以拿到所标记,是怎么控制线程拿到锁标记的?”

    答案:   根据上面的案例分析和前几条问题的回答,这个问题就很明显了。锁池中的存在一条或多条线程来竞争对象锁的控制权,只会有一条线程获得控制权,不会是多条。哎?(此处回单仅限于cpu是单核的,如果是多核的话,我还没测试过,有测试过的朋友麻烦不吝告知,在此先谢过啦)。

      至于如何控制线程拿到对象锁,这个不是我们手动编码控制的,JVM有自己的调度控制(目前我还不清楚,以后再慢慢研究,同样了解的朋友可以直接告知,不吝感谢)。 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324673585&siteId=291194637