1、线程间通信的定义
线程的通信可以被定义为:当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以避免无效的资源争夺。
线程间通信的方式可以有很多种:等待-通知、共享内存、管道流。每种方式用不同的方法来实现。
“等待-通知”通信方式是Java中使用普遍的线程间通信方式,其经典的案例是“生产者-消费者”模式。
2、“等待-通知”通信方式的原理
使用“等待-通知”通信方式进行生产者与消费者之间的线程通信是一个非常有效的办法。
在数据区满时,可以让生产者等待,等到下次数据区中可以加入数据时,给生产者发通知,把生产者唤醒。可以在消费者取出一个数据后,由消费者去唤醒等待的生产者。
在数据区空时,可以让消费者等待,等到下次数据区中可以取出数据时,给消费者发通知,把消费者唤醒。可以在生产者加入一个数据后,由生产者去唤醒等待的消费者。
Java语言中“等待-通知”方式的线程间通信使用对象的wait()、notify()两类方法来实现。
3、wait方法和notify方法的原理
3.1、对象的wait()方法
对象的wait()方法的主要作用是让当前线程阻塞并等待被唤醒。wait()方法对对象监视器紧密相关,使用wait()方法时一定要放在同步块中。wait()方法的调用方法如下:
Object类中的wait()方法有三个版本:
(1)void wait()
这是一个基础版本,当前线程调用了同步对象locko的wait()实例方法后,将导致当前的线程等待,当前线程进入locko的监视器WaitSet,等待被其他线程唤醒。
(2)void wait(long timeout)
这是一个限时等待版本,导致当前的线程等待,等待被其他线程唤醒,或者指定的时候timeout用完,线程不再等待。
(3)void wait(long timeout, int nanos)
这是一个高精度限时等待版本,其主要作用是更精确地控制等待时间。参数nanos是一个附加的纳秒级别的等待时间,从而实现更加高精度的等待时间控制。
3.2、wait()方法的核心原理
1)当线程调用了locko(某个同步锁对象)的wait()方法后,JVM会将当前线程加入locko监视器的WaitSet(等待集),等待被其他线程唤醒。
2)当前线程会释放locko对象监视器的Owner权限,让其他线程可以抢夺locko对象的监视器。
3)让当前线程等待,其状态编程WAITING。
在线程调用了同步对象locko的wait()方法之后,同步对象locko的监视器内部状态大致如下图所示:
3.3、对象的notify()方法
对象的notify()方法的主要作用是唤醒在等待的线程。notify()方法与对象监视器紧密相关,调用notify()方法时也需要放在同步块中。notify()方法的调用方法如下:
notify()方法有两个版本:
(1)void notify()
notify()方法的主要作用为:locko.notify()调用后,唤醒locko监视器等待集中的第一条等待线程;被唤醒的线程进入EntryList,其状态从WAITING变成BLOCKED。
(2)void notifyAll()
locko.notifyAll()被调用后,唤醒locko监视器等待集中的全部等待线程,所有被唤醒的线程进入EntryList,线程状态从WAITING变成BLOCKED。
3.4、notify()方法的核心原理
1)当线程调用了locko(某个同步锁对象)的notify()方法后,JVM会唤醒locko监视器WaitSet中的第一条等待线程。
2)当线程调用了locko的notifyAll()方法后,JVM会唤醒locko监视器WaitSet中的所有等待线程。
3)等待线程被唤醒后,会从监视器的WaitSet移动到EntryList,线程具备了排队抢夺监视器Owner权利的资格,其状态从WAITING变成BLOCKED。
4)等待线程被唤醒后,会从监视器的WaitSet移动到EntryList,线程具备了排队抢夺监视器Owner权利的资格,其状态从WAITING变成BLOCKED。
在线程调用了同步对象locko的wait()或者notifyAll()方法之后,同步对象locko的监视器内部状态大致如下图所示:
4、wait方法和notify方法的注意事项
1)调用某个同步对象locko的wait()和notify()类型方法前,必须要取得这个锁对象的监视锁,所以wait()和notify()类型方法必须放在synchronized(locko)同步块中,如果没有获得监视锁,JVM就会报IllegalMonitorStateException异常。
2)调用wait()方法时使用while进行条件判断,如果是在某种条件下进行等待,对条件的判断就不能使用if语句做一次性判断,而是使用while循环进行反复判断。只有这样才能在线程被唤醒后继续检查wait的条件,并在条件没有满足的情况下继续等待。