线程作为程序内部的多个执行流,相互之间是可以进行通信的。
线程间通信可以通过多种方式来进行,例如:线程间可以通过共享变量进行通信,使每个线程根据共享变量的值进行操作和运算,当通过共享变量进行通信时,通常需要引入线程同步控制;线程间也可以通过 wait()、notify()和 notifyAll() 等方法进行通信。
等待集合(Wait-sets)就像候车室一样,当线程调用 wait() 方法发生等待后,都要进入到该等待集合中,除非发生一下情况,否则线程会一直在该等待集合中。
wait()、notify()或 notifyAll() 方法是类 Object 中定义的方法,由于 Java 中的类都是 Object 类中的子类,因此在 Java 语言中任何类的对象都可以调用这些方法,但这些方法更多的是在多线程环境中使用。
wait() 方法调用的一般形式:
对象名.wait()
称作线程在对象上的等待,作用是把当前线程放入对象的等待集合中。
很多情况下,都是在当前对象上调用 wait() 方法,即 this.wait() ,而 this 又可以省去,所以只有一个 wait() 来表示等待,表示在当前对象上等待。
wait() 方法通常需要放入以 synchronized() 方法修饰的语句块或方法中,如果在 synchronized 外调用 wait() 方法,运行时刻 Java 虚拟机会抛出 IllegalMonitorException 异常。
当线程调用 wait() 方法后,Java 虚拟机会让当前线程进入到休眠状态,并释放对象的同步锁的控制权,允许其他线程执行同步代码块,要唤醒该线程,需要在一个对象上调用 notify() 或 notifyAll() 方法。
线程不能一直待在等待集合中,必须有方法对其进行唤醒 notify() 方法可以对线程进行唤醒。
当调用某个对象的 notify() 方法时,将从该对象等待集合中选择一个等待的线程唤醒,唤醒的线程将从等待集合中删除。
notifyAll() 方法会将所有在等待集合中的线程唤醒,但由于所有被唤醒的线程仍然要去争用 synchronized 锁,而 synchronized 锁具有排他性,最终只有一个线程获得该锁,进入执行状态,其他的线程仍然要继续等待。
线程进入临界区后,往往需要等待某个条件满足才能继续执行。如:消费者需要等待缓冲区有产品后才能消费。
从 JDK1.5 版本开始引入了条件变量(Condition Variables)。条件变量也被称为条件队列(Condition Queues),是由接口 Condition 定义的,它可以让一个线程在条件不满足的情况下一直等待,直到有其他线程唤醒它。
接口 Condition 与 JDK1.5 版本之前的 wait()、notify() 和 notifyAll() 方法操作有以下 4 个不同:
由于要对共享状态进行操作,所以 Condition 对象一般是要包含在同步操作中,Lock 对象为 Condition 提供了这种同步操作。Condition 对象通常被绑定到 Lock 对象上,要创建一个 Condition 对象实例,须要 Lock 的方法 newCondition()。
调用 await() 方法之前,当前线程应该持有与此 Condition 相关联的锁,否则会抛出 IllegalMonitorStateException 异常。
signal() 方法用于唤醒一个正在等待的线程。如果调用了此方法,则在当前条件对象上等待的线程中选择一个线程进行唤醒。
signalAll() 方法用于唤醒所有正在等待的线程。如果调用了此方法,则在当前条件对象上等待的所有线程都将唤醒。