线程通信

等待通知机制

等待通知模式是Java中比较经典的线程通信方式。

两个线程通过对同一对象调用等待wait()和通知notify()方法来进行通讯。

  • 示例:TwoThreadWaitNotify

需要注意的细节,如下

  1. 使用wait()notify()notifyAll()时需要先对调用对象加锁。
  2. 调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。
  3. notify()notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。
  4. notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED
  5. wait()方法返回的前提是获得了调用对象的锁。

WaitNotify.java运行过程

https://s1.wailian.download/2020/01/09/WaitNotify-min.pngWaitNotify-min

等待通知的经典范式

等待方遵循如下原则

  1. 获取对象的锁。
  2. 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
  3. 条件满足则执行对应的逻辑。
synchronized(对象) {
	while(条件不满足) {
		对象.wait();
	}
	对应的处理逻辑
}

通知方遵循如下原则

  1. 获得对象的锁。
  2. 改变条件。
  3. 通知所有等待在对象上的线程。
synchronized(对象) {
	改变条件
	对象.notifyAll();
}

join()方法

  • 示例:JoinTest

从源码可以看出,join()也是利用的等待通知机制。核心逻辑:

while (isAlive()) {
    wait(0);
}

volatile共享内存

因为Java是采用共享内存的方式进行线程通信的,所以可以采用以下方式用主线程关闭A线程

  • 示例:VolatileTest

CountDownLatch并发工具

  • 示例:CountDownLatchTest

CountDownLatch也是基于AQS(AbstractQueuedSynchronizer)实现的:

  • 初始化一个CountDownLatch时告诉并发的线程,然后在每个线程处理完毕之后调用countDown()方法。
  • 该方法会将AQS内置的一个state状态-1
  • 最终在主线程调用await()方法,它会阻塞直到state == 0的时候返回。

CyclicBarrier并发工具

  • 示例:CyclicBarrierTest

CyclicBarrier中文名叫做屏障或者是栅栏,也可以用于线程间通信。它可以等待N个线程都达到某个状态后继续运行的效果。

  • 首先初始化线程参与者。
  • 调用await()将会在所有参与者线程都调用之前等待。
  • 直到所有参与者都调用了await()后,所有线程从await()返回继续后续逻辑。

线程响应中断

  • 示例:StopThread

可以采用中断线程的方式来通信,调用了thread.interrupt()方法其实就是将thread中的一个标志属性置为了true

并不是说调用了该方法就可以中断线程,如果不对这个标志进行响应其实是没有什么作用(这里对这个标志进行了判断)。

但是如果抛出了InterruptedException异常,该标志就会被JVM重置为false

线程池awaitTermination()方法

  • 示例:AwaitTerminationTest

使用awaitTermination()方法的前提需要关闭线程池,如调用了shutdown()方法。

调用shutdown()之后线程池会停止接受新任务,并且会平滑的关闭线程池中现有的任务。

管道通信

  • 示例:PipedTest

Java虽说是基于内存通信的,但也可以使用管道通信。

需要注意的是,输入流和输出流需要首先建立连接。这样线程B就可以收到线程A发出的消息了。