java 并发编程:线程间通信

2020-05-05 0 By admin

线程作为程序内部的多个执行流,相互之间是可以进行通信的。
线程间通信可以通过多种方式来进行,例如:线程间可以通过共享变量进行通信,使每个线程根据共享变量的值进行操作和运算,当通过共享变量进行通信时,通常需要引入线程同步控制;线程间也可以通过 wait()、notify()和 notifyAll() 等方法进行通信。

一、等待集合

等待集合(Wait-sets)就像候车室一样,当线程调用 wait() 方法发生等待后,都要进入到该等待集合中,除非发生一下情况,否则线程会一直在该等待集合中。

  1. 其他线程调用了方法 notify() 或 notifyAll();
  2. 其他线程调用了方法 interrupt() 中断了该线程;
  3. 方法 wait() 的等待时间结束。

二、wait()、notify()、notifyAll() 方法

wait()、notify()或 notifyAll() 方法是类 Object 中定义的方法,由于 Java 中的类都是 Object 类中的子类,因此在 Java 语言中任何类的对象都可以调用这些方法,但这些方法更多的是在多线程环境中使用。

2.1、wait() 方法

wait() 方法调用的一般形式:
对象名.wait()
称作线程在对象上的等待,作用是把当前线程放入对象的等待集合中。
很多情况下,都是在当前对象上调用 wait() 方法,即 this.wait() ,而 this 又可以省去,所以只有一个 wait() 来表示等待,表示在当前对象上等待。
wait() 方法通常需要放入以 synchronized() 方法修饰的语句块或方法中,如果在 synchronized 外调用 wait() 方法,运行时刻 Java 虚拟机会抛出 IllegalMonitorException 异常。
当线程调用 wait() 方法后,Java 虚拟机会让当前线程进入到休眠状态,并释放对象的同步锁的控制权,允许其他线程执行同步代码块,要唤醒该线程,需要在一个对象上调用 notify() 或 notifyAll() 方法。

2.2、notify() 方法

线程不能一直待在等待集合中,必须有方法对其进行唤醒 notify() 方法可以对线程进行唤醒。
当调用某个对象的 notify() 方法时,将从该对象等待集合中选择一个等待的线程唤醒,唤醒的线程将从等待集合中删除。

2.3、notifyAll() 方法

notifyAll() 方法会将所有在等待集合中的线程唤醒,但由于所有被唤醒的线程仍然要去争用 synchronized 锁,而 synchronized 锁具有排他性,最终只有一个线程获得该锁,进入执行状态,其他的线程仍然要继续等待。

三、条件变量

线程进入临界区后,往往需要等待某个条件满足才能继续执行。如:消费者需要等待缓冲区有产品后才能消费。
从 JDK1.5 版本开始引入了条件变量(Condition Variables)。条件变量也被称为条件队列(Condition Queues),是由接口 Condition 定义的,它可以让一个线程在条件不满足的情况下一直等待,直到有其他线程唤醒它。

接口 Condition 与 JDK1.5 版本之前的 wait()、notify() 和 notifyAll() 方法操作有以下 4 个不同:

  1. 它允许在一个对象上有多个等待集合。
  2. 当 synchronized 锁被 Lock 锁对象替换后,相应的对象监视器方法也要用 await()、signal() 和 signalAll() 方法操作替代。
  3. Condition 对象与在对象监视器上的方法 wait()、notify() 和 notifyAll() 的行为和语义不同。Condition 对象在唤醒线程的次序上与对象监视上的方法不同,并且唤醒时不要求一定持有 Lock 对象锁。
  4. Condition 接口的实例有一个对象,在 synchronized 控制访问内,可以在 Condition 对象实例上调用方法 wait()、notify和 notifyAll()。

由于要对共享状态进行操作,所以 Condition 对象一般是要包含在同步操作中,Lock 对象为 Condition 提供了这种同步操作。Condition 对象通常被绑定到 Lock 对象上,要创建一个 Condition 对象实例,须要 Lock 的方法 newCondition()。

3.1、await()方法

调用 await() 方法之前,当前线程应该持有与此 Condition 相关联的锁,否则会抛出 IllegalMonitorStateException 异常。

3.2、signal() 方法

signal() 方法用于唤醒一个正在等待的线程。如果调用了此方法,则在当前条件对象上等待的线程中选择一个线程进行唤醒。

3.3、signalAll() 方法

signalAll() 方法用于唤醒所有正在等待的线程。如果调用了此方法,则在当前条件对象上等待的所有线程都将唤醒。