Java并发编程(三)线程通知和等待

逆流者 2021年02月21日 27次浏览

Java 中的 Object 类是所有类的父类,鉴于继承机制, Java 把所有类都需要的方法放Object 类里面,下面看下Object 类中和线程相关的方法:

public class Object {

	public final native void notify();

    public final native void notifyAll();

    public final native void wait(long timeout) throws InterruptedException;

    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }

    public final void wait() throws InterruptedException {
        wait(0);
    }
}

上面Object中线程的方法都需要在获取到对象的监视器锁中使用,否者线程会抛出 IllegalMonitorStateException 异常。

先简单说一下监视器锁,后面会有博文详细说明:

synchronized

执行 synchronized 步代码块时 使用该共享变量作为参数。

synchronized (共享变量){
	//doSomething 
}

调用该共享变量的方法,并且该方法使用了 synchronized 修饰

synchronized void add (int a , int b) { 
	//doSomething
}

wait

当一个线程调用一个共享变量 wait()方法时, 该调用线程会被阻塞挂起, 到发生下面几件事情才返回

  • 其他线程调用了该共享对象 notify()或者 notifyAll()方法;
  • 其他线程调用了该线程 interrupt()方法,导致该线程抛出 InterruptedException 异常返回。

虚假唤醒

一个线程可以从挂起状态变为可以运行状态(就是被唤醒),即使该线程没有被其他线程调用 notify()、 notifyAll()方法进行通知,或者被中断,或者等待超时,这就是所谓的虚假唤醒。

要怎么避免虚假唤醒呢?

synchronized (obj) {
	while (条件不满足) {
		obj.wait();
	}
}

线程等待不释放锁

继续来看一个例子:
当前线程调用共享变量的 wait()方法后只会释放当前共享变量上的锁,如果当前线程还持有其他共享变量的锁,则这些锁是不会被释放的。

public class WaitNotifyTest {

    /**
     * 资源A
     */
    private static volatile Object resourceA = new Object();
    /**
     * 资源B
     */
    private static volatile Object resourceB = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    synchronized (resourceA) {
                        // 获取resourceA共享资源的监视器锁
                        System.out.println("threadA 获取到 resourceA 监视器锁");

                        // 获取resourceB共享资源的监视器锁
                        synchronized (resourceB) {
                            System.out.println("threadA 获取到 resourceB 监视器锁");

                            // 线程A阻塞, 并释放获取到的resourceA锁
                            System.out.println("threadA 释放 resourceA 监视器锁");
                            resourceA.wait();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    // 睡眠1s
                    Thread.sleep(1000);

                    synchronized (resourceA) {
                        // 获取resourceA共享资源的监视器锁
                        System.out.println("threadB 获取到 resourceA 监视器锁");

                        System.out.println("threadB 尝试获取 resourceB 监视器锁...");
                        // 获取resourceB共享资源的监视器锁
                        synchronized (resourceB) {
                            System.out.println("threadB 获取到 resourceB 监视器锁");

                            // 线程B阻塞, 并释放获取到的resourceA锁
                            System.out.println("threadB 释放 resourceA 监视器锁");
                            resourceA.wait();
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        threadA.start();
        threadB.start();

        threadA.join();
        threadB.join();

        System.out.println("main over");
    }
}
threadA 获取到 resourceA 监视器锁
threadA 获取到 resourceB 监视器锁
threadA 释放 resourceA 监视器锁
threadB 获取到 resourceA 监视器锁
threadB 尝试获取 resourceB 监视器锁...

由上面代码测试结果可以看出: 当前线程调用共享对象的wait()方法时, 当前线程只会释放当前共享对象的锁, 当前线程持有的其他共享对象的监视器锁不会被释放掉!

中断等待中的线程

当一个线程调用共享对象的wait() 方法被阻塞挂起后, 如果其他线程中断了该线程, 则会抛出InterruptedException异常并返回

public class WaitNotifyInterrupt {

    static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("----begin----");

                    synchronized (obj) {
                        obj.wait();
                    }
                    System.out.println("----end----");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        threadA.start();

        Thread.sleep(1000);

        System.out.println("----begin interrupt threadA----");
        threadA.interrupt();
        System.out.println("----end interrupt threadA----");
    }
}
----begin----
----begin interrupt threadA----
----end interrupt threadA----
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at base.thread.book.WaitNotifyInterrupt$1.run(WaitNotifyInterrupt.java:19)
	at java.lang.Thread.run(Thread.java:748)

notify

一个线程调用共享对象的 notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。

此外,被唤醒的线程不能马上从 wait 方法返回并继续执行,它必须在获取了共享对象的监视器锁后才可以返回,也就是唤醒它的线程释放了共享变量上的监视器锁后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。

/**
 * 资源A
 */
private static volatile Object resourceA = new Object();

public static void main(String[] args) throws InterruptedException {
    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            // 获取resourceA共享资源的监视器锁
            synchronized (resourceA) {

                System.out.println("threadA get resourceA lock");

                try {
                    System.out.println("threadA start wait");
                    resourceA.wait();
                    System.out.println("threadA end wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });

    Thread threadB = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (resourceA) {
                System.out.println("threadB get resourceA lock");

                try {
                    System.out.println("threadB start wait");
                    resourceA.wait();
                    System.out.println("threadB end wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });

    Thread threadC = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (resourceA) {
                System.out.println("threadC begin notify");
                resourceA.notify();
            }
        }
    });

    threadA.start();
    threadB.start();

    Thread.sleep(1000);
    threadC.start();

    threadA.join();
    threadB.join();
    threadC.join();

    System.out.println("main over");
}
threadA get resourceA lock
threadA start wait
threadB get resourceA lock
threadB start wait
threadC begin notify
threadA end wait

从代码执行结果可以看出:threadB 线程还在等待中,threadA 线程已经执行结束了。

notifyAll

不同于在共享变量上调用 notify()函数会唤醒被阻塞到该共享变量上的一个线程, notifyAll()方法则会唤醒所有在该共享变量上由于调用 wait 系列方法而被挂起的线程。

/**
 * 资源A
 */
private static volatile Object resourceA = new Object();

public static void main(String[] args) throws InterruptedException {
    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            // 获取resourceA共享资源的监视器锁
            synchronized (resourceA) {

                System.out.println("threadA get resourceA lock");

                try {
                    System.out.println("threadA start wait");
                    resourceA.wait();
                    System.out.println("threadA end wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });

    Thread threadB = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (resourceA) {
                System.out.println("threadB get resourceA lock");

                try {
                    System.out.println("threadB start wait");
                    resourceA.wait();
                    System.out.println("threadB end wait");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });

    Thread threadC = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronized (resourceA) {
                System.out.println("threadC begin notify");
                resourceA.notifyAll();
            }
        }
    });

    threadA.start();
    threadB.start();

    Thread.sleep(1000);
    threadC.start();

    threadA.join();
    threadB.join();
    threadC.join();

    System.out.println("main over");
}
threadA get resourceA lock
threadA start wait
threadB get resourceA lock
threadB start wait
threadC begin notify
threadB end wait
threadA end wait
main over

从代码执行结果看:threadA 和 threadB 都执行结束了。