线程间的通信

JVM在运行时会将自己管理的内存区域,划分为不同的数据区,称为运行时数据区。每个线程都有自己私有的内存空间,如下图示:

java 如何通知线程 java 线程间通信_java 如何通知线程

Java线程按照自己虚拟机栈中的方法代码一步一步的执行下去,在这一过程中不可避免的会使用到线程共享的内存区域堆或方法区。为了防止多个线程在同一时刻访问同一个内存地址,需要互相告知自己的状态以避免资源争夺。

线程的通信方式主要分为三种方式:①共享内存②消息传递③管道流

共享内存:线程之间通过对共享内存的读-写来实现隐式通信。Java中的具体实现是:volatile共享内存。

消息传递:线程之间通过明确的发送消息来实现显示通信。Java中的具体实现是:等待/通知机制(wait/notify),join方法。

管道流:管道输入/输出流。

1、等待/通知机制

其过程是:线程A由于某些原因,自主调用了对象o的wait方法,进入WAITING状态,释放占有的锁并等待通知。而线程B则调用对象o的notify方法或notifyall方法进行通知,线程A会收到通知,并从wait方法中返回,继续执行后面的代码。

可以发现,线程A和线程B就是通过对象o的wait方法和notify方法来发送消息,进行通信。

wait方法和notify方法是Object类的方法,而Object类是所有类的父类,因此所有对象都实现了Object类的方法。即所有的对象都具有wait方法和notify方法。

方法

作用

备注

wait

线程调用共享对象的wait()方法后会进入WAITING状态,释放占有的对象锁并等待其他线程的通知或中断才从该方法返回。

该方法可以传参数,wait(long n):超时等待n毫秒,进入TIME-WAITING状态,如果在n毫秒内没有通知或中断,则自行返回

notify

线程调用共享对象的notify()方法后会通知一个调用了wait方法并在此等待的线程返回。但由于在共享变量上等待的线程可能不止一个,故具体通知哪一个线程是随机的。

notifyAll()方法与notify()方法作用一致,不过notify是随机通知一个线程,而notifyAll则是通知所有在该共享变量上等待的线程

由于线程的等待/通知机制需要借助共享对象,所以在调用wait方法前,线程必须先获得该对象的锁,即只能在同步方法或同步块(synchronized代码块)中调用wait方法,在调用wait方法后,线程释放锁。

同样的notify方法在调用前也需要获得对象的锁,即也只能在同步方法或同步块中调用notify方法。若有多个线程在等待,则线程调度器会随机挑选一个线程来通知。需要注意的是,被通知的线程并不会在得到通知后就马上从wait方法返回,而是需要等待获得对象的锁后才能从wait方法返回。而调用了notify方法的线程也并不会在调用时就马上释放对象的锁,而是在执行完同步方法或同步块(synchronized代码块)后,才释放对象的锁。因此,被通知的线程要等调用了notify的线程释放锁后,才能从wait方法中返回。

综上所述,等待/通知机制的经典范式如下:

/** * 等待线程(调用wait方法的线程) */synchronized(共享对象){ //同步代码块,进入条件是获得锁 while(判断条件){ //进行wait线程任务的条件不满足时进入 共享对象.wait() } 线程任务代码}/** * 通知线程(调用notify方法的线程) */synchronized(共享对象){ //同步代码块,进入条件是获得锁 线程任务代码 改变wait线程任务的条件 共享对象.notify()}

根据以上范式,有代码如下:

public class WaitNotify { static boolean flag = true; //等待线程继续执行往下执行的条件 static Object lock = new Object(); //上锁的对象 public static void main(String[] args) throws InterruptedException { Thread waitThread = new Thread(new WaitRunnable(),"waitThread"); //以WaitRunnable为任务类的线程 Thread notifyThread = new Thread(new NotifyRunnable(),"notifyThread"); //以NotifyRunnable为任务类的线程 waitThread.start(); //wait线程启动 Thread.sleep(2000); //主线程休眠2s notifyThread.start(); //notify线程启动 } /** * Runnable等待实现类 * synchronized关键字:可以修饰方法或者以同步块的形式来使用 */ static class WaitRunnable implements Runnable{ @Override public void run() { //对lock加锁 synchronized(lock){ //判断,若flag为true,则继续等待(wait) while(flag){ try { System.out.println( Thread.currentThread().getName()+ "---flag为true,等待 @"+ new SimpleDateFormat("hh:mm:ss").format(new Date()) ); lock.wait(); //等待,并释放锁资源 } catch (InterruptedException e) { e.printStackTrace(); } } //若flag为false,则进行工作 System.out.println( Thread.currentThread().getName()+ "---flag为false,运行 @"+ new SimpleDateFormat("hh:mm:ss").format(new Date()) ); } } } /** * Runnable通知实现类 */ static class NotifyRunnable implements Runnable{ @Override public void run(){ //对lock加锁 synchronized(lock){ //以NotifyRunnable为任务类的线程释放lock锁,并进行通知后,以Wait为任务类的线程才可以跳出循环 System.out.println( Thread.currentThread().getName()+ "---当前持有锁,释放 @"+ new SimpleDateFormat("hh:mm:ss").format(new Date()) ); lock.notifyAll(); //通知所有正在等待的线程从wait返回 flag = false; try { Thread.sleep(5000); //notifyThread线程休眠5s } catch (InterruptedException e) { e.printStackTrace(); } } //再次对lock加锁,并休眠 synchronized (lock){ System.out.println( Thread.currentThread().getName()+ "---再次持有锁,休眠 @"+ new SimpleDateFormat("hh:mm:ss").format(new Date()) ); try { Thread.sleep(2000); //再次让notifyThread线程休眠2s } catch (InterruptedException e) { e.printStackTrace(); } } } }}//该代码示例来自《Java并发编程的艺术》

其结果如下:

waitThread---flag为true,等待 @01:53:51notifyThread---当前持有锁,释放 @01:53:53waitThread---flag为false,运行 @01:53:58notifyThread---再次持有锁,休眠 @01:53:58

以上代码根据等待/通知的经典范式,设置一个线程是否继续往下执行的条件变量flag,以及一个共享对象lock,并使用synchronized关键字对lock上锁。

waitThread线程是等待线程,在启动时会尝试获得锁,成功则进入synchronized代码块。在synchronized代码块中,如果条件不满足(即flag为true),则waitThread线程会进入while循环,并在循环体中调用wait方法,进入WAITING状态及释放锁资源。直到有其他线程调用notify方法通知才从wait方法返回。

notifyThread线程是通知线程,在启动时也会尝试获得锁,成功则同样进入synchronized代码块。在synchronized代码块中,notifyThread线程会改变条件,使waitThread线程可以继续往下执行(即令flag为false),同时notifyThread线程也会调用notyfiAll方法,让waitThread线程收到通知。

但注意,notifyThread线程并不会在调用notyfiAll方法后就马上释放锁,而是在执行完synchronized代码块的内容后才释放锁。我们在notifyThread线程调用notyfiAll后,将该线程休眠5s。可以从打印结果发现,在notifyThre.........