JAVA线程间协作:wait.notify.notifyAll

    JAVA的进程同步是通过synchronized()来实现的,须要说明的是,JAVA的synchronized()方法相似于操作系统概念中的相互排斥内存块。在JAVA中的Object类型中。都是带有一个内存锁的,在有线程获取该内存锁后。其它线程无法訪问该内存。从而实现JAVA中简单的同步、相互排斥操作。

明确这个原理。就能理解为什么synchronized(this)与synchronized(static XXX)的差别了。synchronized就是针对内存区块申请内存锁,thiskeyword代表类的一个对象,所以其内存锁是针对同样对象的相互排斥操作,而static成员属于类专有,其内存空间为该类全部成员共同拥有。这就导致synchronized()对static成员加锁,相当于对类加锁,也就是在该类的全部成员间实现相互排斥。在同一时间仅仅有一个线程可訪问该类的实例。

假设仅仅是简单的想要实如今JAVA中的线程相互排斥,明确这些基本就已经够了。

可是假设须要在线程间进行协作通信,就须要借助Object对象的wait,notify和notifyAll方法了。

    在Java中。能够通过配合调用Object对象的wait方法和notify方法或notifyAll方法来实现线程间的协作通信。在线程中调用wait方法。将堵塞等待其它线程的通知(其它线程调用notify方法或notifyAll方法),在线程中调用notify方法或notifyAll方法,将通知其它线程从wait方法处返回。 
    Object.wait()与Object.notify()必须要与同步块或同步方法(synchronized块或者synchronized方法)一起使用,也就是wait与notify是针对已经获取了Object锁进行操作,从语法角度来说就是Object.wait(),Object.notify必须在同步块或同步方法内。从功能上来说wait就是说线程在获取对象锁后。主动释放对象锁。同一时候本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才干继续获取对象锁。并继续运行。

对应的notify()就是对对象锁的唤醒操作。

但有一点须要注意的是notify()调用后,并非立即就释放对象锁的,而是在对应的同步块或同步方法中运行结束,自己主动释放锁后。JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁。唤醒线程,继续运行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都能够暂停当前线程,释放CPU控制权,基本的差别在于Object.wait()在释放CPU同一时候。释放了对象锁的控制。

    wait方法

    调用线程的sleep,yield方法时,线程并不会让出对象锁,wait却不同。

    wait函数必须在同步代码块中调用(也就是当前线程必须持有对象的锁),他的功能是这种:

        我累了。歇息一会儿。对象的锁你们拿去用吧。CPU也给你们。

    调用了wait函数的线程会一直等待,直到有其它线程调用了同一个对象的notify或者notifyAll方法才干被唤醒,须要注意的是:被唤醒并不代表立即获得对象的锁。也就是说,一个线程调用了对象的wait方法后,他须要等待两件事情的发生:

    1. 有其它线程调用同一个对象的notify或者notifyAll方法(调用notify/notifyAll方法之前)
    2. 被唤醒之后又一次获得对象的锁(调用notify/notifyAll方法之后)才干继续往下运行兴许动作。

    假设一个线程调用了某个对象的wait方法,可是兴许并没有其它线程调用该对象的notify或者notifyAll方法。则该线程将会永远等下去…

    notify和notifyAll方法

    notify/notifyAll方法也必须在同步代码块中调用(也就是调用线程必须持有对象的锁),他们的功能是这种:

        女士们。先生们请注意,锁的对象我即将用完,请大家醒醒,准备一下。立即你们就能使用锁了。

    不同的是。notify方法仅仅会唤醒一个正在等待的线程(至于唤醒谁,不确定!)。而notifyAll方法会唤醒全部正在等待的线程。

另一点须要特别强调:调用notify和notifyAll方法后,当前线程并不会立即放弃锁的持有权,而必须要等待当前同步代码块运行完才会让出锁。

    假设一个对象之前没有调用wait方法,那么调用notify方法是没有不论什么影响的。

    以下来看一个样例:

package com.cooperation;

import java.util.concurrent.TimeUnit;

public class Test
{
    public static Object object = new Object();

    static class Thread1 implements Runnable
    {
        @Override
        public void run()
        {
            synchronized(object)
            {
                System.out.println(Thread.currentThread().getName()+" is running.");
                try
                {
                    object.wait();
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" get the lock.");
            }
        }
    }

    static class Thread2 implements Runnable
    {
        @Override
        public void run()
        {
            synchronized(object)
            {
                System.out.println(Thread.currentThread().getName()+" is running.");
                object.notify();
                System.out.println(Thread.currentThread().getName()+" invoke notify()");
                System.out.println(Thread.currentThread().getName()+" release the lock.");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException
    {
        Thread thread1 = new Thread(new Thread1());
        Thread thread2 = new Thread(new Thread2());
        thread1.start();
        TimeUnit.SECONDS.sleep(1);
        thread2.start();
    }
}
    运行结果(运行多次,结果同样):
Thread-0 is running.
Thread-1 is running.
Thread-1 invoke notify()
Thread-1 release the lock.
Thread-0 get the lock.

   能够看到当Thread-0调用了wait方法后就释放了object锁。Thread-1获取锁之后调用notify的释放锁,可是这个时候Thread没有立马获取object锁。而是等到了Thread-1的同步块退出之后才获取了object的锁。

    假设将class Thread2中的 System.out.println(Thread.currentThread().getName()+" release the lock.");这句放在synchronized(object)的外面。有可能出现例如以下的运行结果:

Thread-0 is running.
Thread-1 is running.
Thread-1 invoke notify()
Thread-0 get the lock.
Thread-1 release the lock.

    这是由于,当Thread-1释放了object锁而且退出了同步块之后Thread-0立马获取了锁,而这时候两个线程的打印语句的顺序就随机了。

    在JDK1.4之后出现了一个Condition类,这个类也能够实现同样的功能。而且一般建议使用Condition替代wait,notify,notifyAll家族,实现更安全的线程间协作通信功能,比方ArrayBlockingQueue就是使用Condition实现堵塞队列的。

这个我在以后的博客中会涉及到。

    假设有什么意见或者建议请在下方留言。



posted on 2017-07-07 19:59  ljbguanli  阅读(136)  评论(0编辑  收藏  举报