Java 多线程解密之wait等待

  在Java中,可以调用wait()方法使当前线程进入object的等待队列,这样当有新的任务需要执行的时候,调用object的notify()或者notifyAll()方法就可以唤起线程,wait(),notify()在线程池、数据库连接池有有广泛的应用。

  其流程为:


  首先我们知道,在获取对象的锁,也就是获取到其监视器,获取锁成功之后,则线程进入 RUNNABLE状态,即运行状态,这时候如果调用其对象的wait()方法,即进入对象的等待队列,然后会将锁释放。这时候,别的线程可以获取到这个对象的锁,然后调用其 notifyAll() 方法,这样,所有在等待队列中的线程(即 WAITING 状态)都会进入这个线程的 同步队列,即锁队列。注意 ,调用  notifyAll() 方法 的线程此时可能还并没有释放锁。必须等这个线程释放锁之后,同步队列中的线程才可以一个一个的执行(同步队列中的线程获取到锁之后,必须执行完同步代码块,才会释放锁),也就是说,wait()之后进入到等待队列,notifyAll()后进入锁队列,但只有重新获取到锁之后才能从wait()方法返回,但当wait()的时候线程被打断呢?其实打断的时候,线程也是进入到了锁队列,跟上述一样,而只有当重新获取到锁之后,才会走catch(Execption)的代码块,而不是从wait()方法返回。
  有人可能要说了?为什么要这么设计?为什么notify()之后能不能马上从wait()方法返回?为什么还要等再次获取到锁的时候才能返回???
  因为只有这样,才能保证在多个线程都在执行Object的同步块的时候:

  synchronized (Object) {


  }


  同一时间,只有一个线程在 synchronized 代码块中!其它的线程要么在Object的等待队列中,要么就是在锁队列中!这样就避免了多个线程在同步块中执行导致的同步问题。

  

  下面举示例来说明:

一、wait()方法与notify()方法

  两个等待线程WaitRunnable,一个唤起线程 NotifyAllRunnable,两个等待线程WaitRunnable 等待一段时间之后,唤起线程 NotifyAllRunnable运行,然后调用 NotifyAll方法,然后等待一段时间才释放锁。两个等待线程WaitRunnable在wait()方法唤起之后,又会等待一段时间,那么,谁先运行完?谁再运行完呢?

public class Main {

    public static final Object waitObject = new Object();

    public static void main(String[] args) {

        Thread waitThread1 = new Thread(new WaitRunnable(), "waitThread1");
        Thread waitThread2 = new Thread(new WaitRunnable(), "waitThread2");

        waitThread1.start();
        waitThread2.start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread notifyAllThread = new Thread(new NotifyAllRunnable(), "notifyAllThread");
        notifyAllThread.start();
    }
}


WaitRunnable:

public class WaitRunnable implements Runnable{

    @Override
    public void run() {


        synchronized (Main.waitObject) {

            System.out.println(Thread.currentThread().getName() + " 开始wait ");
            try {
                /**
                 * 会释放锁
                 */
                Main.waitObject.wait();
                /**
                 * 又拿到锁
                 */
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " wait结束,又获取到锁 ");

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " wait sleep 结束,释放锁 ");

            /**
             * 即将释放锁
             */
        }

        /**
         * 所以上述代码块是 先拿到锁,释放,然后又拿到了锁,再释放
         */


        System.out.println(Thread.currentThread().getName() + " wait结束代码块 \n\n\n");


    }

}

NotifyAllRunnable:

public class NotifyAllRunnable implements Runnable{

    @Override
    public void run() {

        synchronized (Main.waitObject) {

            System.out.println("NotifyAllRunnable begin");
            Main.waitObject.notifyAll();
            System.out.println("NotifyAllRunnable notify");

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("NotifyAllRunnable 即将释放锁");
        }

        System.out.println("NotifyAllRunnable 释放锁完毕 \n\n\n");

    }
}

执行结果为:

waitThread1 开始wait 
waitThread2 开始wait 
NotifyAllRunnable begin
NotifyAllRunnable notifyAll
NotifyAllRunnable 即将释放锁
NotifyAllRunnable 释放锁完毕 



waitThread2 wait结束,又获取到锁 
waitThread2 wait sleep 结束,释放锁 
waitThread2 wait结束代码块 



waitThread1 wait结束,又获取到锁 
waitThread1 wait sleep 结束,释放锁 
waitThread1 wait结束代码块 

  可以看到,NotifyAllRunnable 虽然已经notifyAll()来通知其它线程了,但是其他等待线程只是由等待队列进入了锁队列,NotifyAllRunnable还持有锁,所以在其 sleep 了一段时间之后,释放了锁。waitThread才能获取到锁,注意其中一个 waitThread A 获取到锁之后,并未马上释放,而是也等待了一段时间,另一个 waitThread B 依旧在锁队列。只有在 waitThread A 执行完同步代码块之后,才会把这个对象的锁释放,waitThread B才会获取到锁。所以我们notify()或者notifyAll()后wait的线程可能不会马上执行。

扫描二维码关注公众号,回复: 5001397 查看本文章

二、wait(long,int)方法:

  这个方法有点意思了就,wait(long,int)之后,进入等待队列,注意如果在指定时间内收到了notifyAll,那么会跟上述一样进入锁队列,否则会在超时之后进入到锁队列中。
  wait(long,int),第一个参数是要等到的毫秒数,第二个是纳秒数,第二个是为了更精确,指定为1即可,
  比如,想等待2秒,即2000ms,wait(2000, 1) 即可。

public class WaitTimeMain {


    public static final Object waitTimeObject = new Object();


    public static void main(String[] args) {

        Thread waitTimeThread = new Thread(new WaitTimeRunnable(), "waitTimeThread");
        waitTimeThread.start();


        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Thread syncThread = new Thread(new SyncRunnable(), "SyncThread");
        syncThread.start();

    }

}

public class WaitTimeRunnable implements Runnable{

    @Override
    public void run() {

        synchronized (WaitTimeMain.waitTimeObject) {

            long beginTime = System.currentTimeMillis();
            System.out.println(" WaitTimeRunnable wait开始,当前时间" + beginTime);

            try {
                WaitTimeMain.waitTimeObject.wait(2000, 1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            long endTime = System.currentTimeMillis();
            System.out.println(" WaitTimeRunnable wait完毕 , 用时 "
                    + (endTime - beginTime) +  "ms");
        }

    }

}

public class SyncRunnable implements Runnable{

    @Override
    public void run() {
        synchronized (WaitTimeMain.waitTimeObject) {
            System.out.println(" SyncRunnable 获取到锁");

            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(" SyncRunnable 5秒之后释放锁 \n\n\n");

        }

    }
}

 执行结果为:

 WaitTimeRunnable wait开始
 SyncRunnable 获取到锁

 SyncRunnable 5秒之后释放锁 

 WaitTimeRunnable wait完毕 , 用时 5112ms

  可以看到,虽然WaitTimeRunnable没有收到通知,2s之后自动进入锁队列,但是现在已有线程获取到锁,其要等待锁释放,即5秒之后才完成。

猜你喜欢

转载自blog.csdn.net/HappyHeng/article/details/86562633
今日推荐