wait() 和notify()、notifyAll()

这三个方法用于协调多个线程对共享数据的存取,所以必须在 Synchronized 语句块内使用这三个方法,否则会抛出错 IllegalMonitorStateException。前面说过 Synchronized 这个关键字用于保护共享数据,阻止其他线程对共享数据的存取。但是这样程序的流程就很不灵活了,如何才能在当前线程还没退出 Synchronized 数据块时让其他线程也有机会访问共享数据呢?此时就用这三个方法来灵活控制。

wait() 方法使当前线程被阻塞挂起暂停执行并释放对象锁标志,让其他线程可以进入 Synchronized 数据块,当前线程被放入对象等待池中。当调用共享对象的 notify() 或者 notifyAll() 方法才会返回,此时返回的线程会加入到锁标志等待池中,只有锁标志等待池中的线程能够获取锁标志,如果锁标志等待池中没有线程,则 notify() 不起作用。

notifyAll() 则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。

需要注意的是,当线程调用共享对象的 wait() 方法时,当前线程只会释放当前共享对象的锁,当前线程持有的其他共享对象的监视器锁并不会释放。

线程中断

为啥需要中断呢?下面简单的举例情况:

比如我们会启动多个线程做同一件事,比如抢 12306 的火车票,我们可能开启多个线程从多个渠道买火车票,只要有一个渠道买到了,我们会通知取消其他渠道。这个时候需要关闭其他线程;

很多线程的运行模式是死循环,比如在生产者/消费者模式中,消费者主体就是一个死循环,它不停的从队列中接受任务,执行任务,在停止程序时,我们需要一种”优雅”的方法以关闭该线程;

在一些场景中,比如从第三方服务器查询一个结果,我们希望在限定的时间内得到结果,如果得不到,我们会希望取消该任务;

上面这几个例子线程已经在运行了,并不好去干涉,但是可以通过中断,告诉这个线程,你应该中断了。比如上面的例子中的线程再收到中断后,可以通过中断标志来结束线程的运行。当然,你也可以收到后,不做任何处理,这也是可以的。

在 Java 中,停止一个线程的主要机制是中断,中断并不是强迫终止一个线程,它是一种协作机制,是给线程传递一个取消信号,但是由线程来决定如何以及何时退出。

需要注意的是:在停止线程的时候,不要调用 stop 方法,该方法已经被废弃了,并且会带来不可预测的影响。

线程对中断的反应

RUNNABLE:线程在运行或具备运行条件只是在等待操作系统调度

WAITING/TIMED_WAITING:线程在等待某个条件或超时

BLOCKED:线程在等待锁,试图进入同步块

NEW/TERMINATED:线程还未启动或已结束

线程中断常用的方法

interrupt() :中断线程,将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。

interrupted():第一次使用返回true,并清除中断标志位,在此之后查询中断状态isInterrupt()都会返回false,刚刚第一个例子也看到了,利用    第一次返回的true可以跳出循环。第二次以及以后都是返回false。

isInterrupted():仅仅查询中断标志位来判断是否发生中断并返回true或者false。

RUNNABLE 状态

如果线程在运行中,interrupt() 只是会设置线程的中断标志位,没有任何其它作用。线程应该在运行过程中合适的位置检查中断标志位,比如说,如果主体代码是一个循环,可以在循环开始处进行检查,如下所示:

public class InterruptRunnableDemo extendsThread {

@Overridepublic voidrun() {while (!Thread.currentThread().isInterrupted()) { // 也可以使用 !Thread.currentThread().interrupted() 来判断有没有中断//... 单次循环代码

}

System.out.println("done ");

}public static void main(String[] args) throwsInterruptedException {

Thread thread= newInterruptRunnableDemo();

thread.start();

Thread.sleep(1000);

thread.interrupt();

}

}

WAITING/TIMED_WAITING

线程执行如下方法会进入WAITING状态:

public final void join() throwsInterruptedExceptionpublic final void wait() throws InterruptedException

执行如下方法会进入TIMED_WAITING状态:

public final native void wait(long timeout) throwsInterruptedException;public static native void sleep(long millis) throwsInterruptedException;public final synchronized void join(long millis) throws InterruptedException

在这些状态时,对线程对象调用 interrupt() 会使得该线程抛出 InterruptedException,需要注意的是,抛出异常后,中断标志位会被清空(线程的中断标志位会由 true 重置为false,因为线程为了处理异常已经重新处于就绪状态),而不是被设置。比如说,执行如下代码:

Thread t = newThread (){

@Overridepublic voidrun() {try{

Thread.sleep(1000);

}catch(InterruptedException e) {//exception被捕获,但是为输出为false 因为标志位会被清空

System.out.println(isInterrupted());

}

}

};

t.start();try{

Thread.sleep(100);

}catch(InterruptedException e) {

}

t.interrupt();//置为true

InterruptedException 是一个受检异常,线程必须进行处理。我们在异常处理中介绍过,处理异常的基本思路是,如果你知道怎么处理,就进行处理,如果不知道,就应该向上传递,通常情况下,你不应该做的是,捕获异常然后忽略。

捕获到 InterruptedException,通常表示希望结束该线程,线程大概有两种处理方式:

向上传递该异常,这使得该方法也变成了一个可中断的方法,需要调用者进行处理

有些情况,不能向上传递异常,比如Thread的run方法,它的声明是固定的,不能抛出任何受检异常,这时,应该捕获异常,进行合适的清理操作,清理后,一般应该调用Thread的interrupt方法设置中断标志位,使得其他代码有办法知道它发生了中断

第一种方式的示例代码如下:

//抛出中断异常,由调用者捕获

public void interruptibleMethod() throwsInterruptedException{//... 包含wait, join 或 sleep 方法

Thread.sleep(1000);

}

第二种方式的示例代码如下:

public class InterruptWaitingDemo extendsThread {

@Overridepublic voidrun() {while (!Thread.currentThread().isInterrupted()) {try{//模拟任务代码

Thread.sleep(2000);

}catch(InterruptedException e) {//... 清理操作

System.out.println(isInterrupted());//false//重设中断标志位为true

Thread.currentThread().interrupt();

}

}

System.out.println(isInterrupted());//true

}public static voidmain(String[] args) {

InterruptWaitingDemo thread= newInterruptWaitingDemo();

thread.start();try{

Thread.sleep(100);

}catch(InterruptedException e) {

}

thread.interrupt();

}

}

BLOCKED

如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程依然会处于BLOCKED状态,也就是说,interrupt()并不能使一个在等待锁的线程真正”中断”。我们看段代码:

public class InterruptWaitingDemo extendsThread {

@Overridepublic voidrun() {while (!Thread.currentThread().isInterrupted()) {try{//模拟任务代码

Thread.sleep(2000);

}catch(InterruptedException e) {//... 清理操作//重设中断标志位

Thread.currentThread().interrupt();

}

}

System.out.println(isInterrupted());

}public static voidmain(String[] args) {

InterruptWaitingDemo thread= newInterruptWaitingDemo();

thread.start();try{

Thread.sleep(100);

}catch(InterruptedException e) {

}

thread.interrupt();

}

}

BLOCKED 如果线程在等待锁,对线程对象调用 interrupt() 只是会设置线程的中断标志位,线程依然会处于 BLOCKED 状态,也就是说,interrupt() 并不能使一个在等待锁的线程真正”中断”。我们看段代码:

public classInterruptSynchronizedDemo {private static Object lock = new Object();//monitor

private static class A extendsThread {

@Overridepublic voidrun() {//等待lock锁

synchronized(lock) {//等待标志位被置为true

while (!Thread.currentThread().isInterrupted()) {

}

}

System.out.println("exit");

}

}public static void test() throwsInterruptedException {synchronized (lock) {//获取锁

A a = newA();

a.start();

Thread.sleep(1000);//a在等待lock锁,interrupt 无法中断

a.interrupt();//a线程加入当前线程,等待执行完毕

a.join();

}

}public static void main(String[] args) throwsInterruptedException {

test();

}

}

test 方法在持有锁 lock 的情况下启动线程 a,而线程 a 也去尝试获得锁 lock,所以会进入锁等待队列,随后 test 调用线程 a 的 interrupt 方法并等待线程线程 a 结束,线程 a 会结束吗?不会,interrupt 方法只会设置线程的中断标志,而并不会使它从锁等待队列中出来。线程a 会一直尝试获取锁,但是主线程也在等待 a 结束才会释放锁,所以相互之间互为等待,不能结束。

我们稍微修改下代码,去掉 test方法中的最后一行 a.join(),即变为:

public static void test() throwsInterruptedException {synchronized(lock) {

A a= newA();

a.start();

Thread.sleep(1000);

a.interrupt();

}//lock锁释放后 A线程重队列中出来

}

这时,程序就会退出。为什么呢?因为主线程不再等待线程 a 结束,释放锁 lock 后,线程 a 会获得锁,然后检测到发生了中断,所以会退出。

在使用 synchronized 关键字获取锁的过程中不响应中断请求,这是 synchronized 的局限性。如果这对程序是一个问题,应该使用显式锁,java 中的 Lock 接口,它支持以响应中断的方式获取锁。对于 Lock.lock(),可以改用 Lock.lockInterruptibly(),可被中断的加锁操作,它可以抛出中断异常。等同于等待时间无限长的 Lock.tryLock(long time, TimeUnit unit)。

NEW/TERMINATE

如果线程尚未启动 (NEW),或者已经结束 (TERMINATED),则调用 interrupt() 对它没有任何效果,中断标志位也不会被设置。比如说,以下代码的输出都是 false。

public classInterruptNotAliveDemo {private static class A extendsThread {

@Overridepublic voidrun() {

}

}public static void test() throwsInterruptedException {

A a= newA();

a.interrupt();

System.out.println(a.isInterrupted());

a.start();

Thread.sleep(100);

a.interrupt();

System.out.println(a.isInterrupted());

}public static void main(String[] args) throwsInterruptedException {

test();

}

}

IO操作

如果线程在等待 IO 操作,尤其是网络 IO,则会有一些特殊的处理,我们没有介绍过网络,这里只是简单介绍下。

实现此 InterruptibleChannel 接口的通道是可中断的:如果某个线程在可中断通道上因调用某个阻塞的 I/O 操作(常见的操作一般有这些:serverSocketChannel. accept()、socketChannel.connect、socketChannel.open、socketChannel.read、socketChannel.write、fileChannel.read、fileChannel.write)而进入阻塞状态,而另一个线程又调用了该阻塞线程的 interrupt 方法,这将导致该通道被关闭,并且已阻塞线程接将会收到 ClosedByInterruptException,并且设置已阻塞线程的中断状态。另外,如果已设置某个线程的中断状态并且它在通道上调用某个阻塞的 I/O 操作,则该通道将关闭并且该线程立即接收到 ClosedByInterruptException;并仍然设置其中断状态。

如果线程阻塞于 Selector 调用,则线程的中断标志位会被设置,同时,阻塞的调用会立即返回。

我们重点介绍另一种情况,InputStream 的 read 调用,该操作是不可中断的,如果流中没有数据,read 会阻塞 (但线程状态依然是 RUNNABLE ),且不响应 interrupt(),与 synchronized 类似,调用 interrupt() 只会设置线程的中断标志,而不会真正”中断”它,我们看段代码

public classInterruptReadDemo {private static class A extendsThread {

@Overridepublic voidrun() {while(!Thread.currentThread().isInterrupted()){try{

System.out.println(System.in.read())//wait input

} catch(IOException e) {

e.printStackTrace();

}

}

System.out.println("exit");

}

}public static void main(String[] args) throwsInterruptedException {

A t= newA();

t.start();

Thread.sleep(100);

t.interrupt();

}

}

线程t启动后调用 System.in.read() 从标准输入读入一个字符,不要输入任何字符,我们会看到,调用 interrupt() 不会中断 read(),线程会一直运行。

不过,有一个办法可以中断 read() 调用,那就是调用流的 close 方法,我们将代码改为:

public classInterruptReadDemo {private static class A extendsThread {

@Overridepublic voidrun() {while (!Thread.currentThread().isInterrupted()) {try{

System.out.println(System.in.read());

}catch(IOException e) {

e.printStackTrace();

}

}

System.out.println("exit");

}public voidcancel() {try{

System.in.close();

}catch(IOException e) {

}

interrupt();

}

}public static void main(String[] args) throwsInterruptedException {

A t= newA();

t.start();

Thread.sleep(100);

t.cancel();

}

}

我们给线程定义了一个 cancel 方法,在该方法中,调用了流的 close 方法,同时调用了 interrupt 方法,这次,程序会输出:

-1exit

也就是说,调用close方法后,read方法会返回,返回值为-1,表示流结束。

如何正确地取消/关闭线程

1. 以上,我们可以看出,interrupt 方法不一定会真正”中断”线程,它只是一种协作机制,如果 不明白线程在做什么,不应该贸然的调用线程的 interrupt 方法,以为这样就能取消线程。

2. 对于以线程提供服务的程序模块而言,它应该封装取消/关闭操作,提供单独的取消/关闭方法给调用者,类似于 InterruptReadDemo 中演示的 cancel 方法,外部调用者应该调用这些方法而不是直接调用 interrupt。

3. Java并发库的一些代码就提供了单独的取消/关闭方法,比如说,Future接口提供了如下方法以取消任务:boolean cancel(boolean mayInterruptIfRunning);

4. 再比如,ExecutorService提供了如下两个关闭方法:

voidshutdown();

List shutdownNow();

5. Future 和 ExecutorService 的 API 文档对这些方法都进行了详细说明,这是我们应该学习的方式。

参考文章:

java 线程不足_Java 线程基础知识相关推荐

  1. java填空题_Java语言基础知识填空题

    Java语言基础知识填空题 想学java语言的人,要多做题才能巩固知识,下面小编为大家带来了Java语言基础知识的填空题,欢迎大家阅读! Java语言基础知识填空题. 1.浮点型数据根据数据存储长度和 ...

  2. JAVA面试题之JVM基础知识

    JAVA面试题总结-JVM的基础知识 JAVA面试题之JVM基础知识 说一下JVM的主要组成部分及作用 说一下 jvm 运行时数据区? 说一下堆和栈的区别? 队列和栈是什么?有什么区别? 什么是双亲委 ...

  3. 作为一名Java开发者应该掌握的基础知识汇总!

    Java语言作为热门编程语言之一,受到了更多的欢迎.今天小千就为大家介绍一下作为一名Java开发者应该掌握的基础知识. 一.修饰符 java语言中提供了一些修饰符,这些修饰符可以修饰类,变量和方法. ...

  4. Java开发者需要掌握的基础知识

    Java语言作为热门编程语言之一,受到了更多的欢迎.今天小千就为大家介绍一下作为一名Java开发者应该掌握的基础知识. 一.修饰符 java语言中提供了一些修饰符,这些修饰符可以修饰类,变量和方法.以 ...

  5. Java中易忽略的基础知识

    欢迎关注我的公众号[软件大爆炸] Java学习中的碎碎念 Java中易忽略的基础知识 Java面向对象基础 Java中的核心类 Java抽象类和接口 Java中的异常 Java中的泛型与集合 Java ...

  6. java+向前进一_Java 线程基础

    前言 线程并发系列文章: 熟练掌握线程原理与使用是程序员进阶的必经之路,网上很多关于Java线程的知识,比如多线程之间变量的可见性.操作的原子性,进而扩展出的Volatile.锁(CAS/Synchr ...

  7. java线程 属性_Java 线程基础

    本文部分摘自<Java 并发编程的艺术> 线程简介 1. 什么是线程? 现代操作系统在运行一个程序时,会为其创建一个进程,一个进程里可以创建多个线程.现代操作系统调度的最小单元是线程,也叫 ...

  8. java 线程机制_Java线程机制学习

    前面的文章中总结过Java中用来解决共享资源竞争导致线程不安全的几种常用方式: synchronized: ReentrantLock: ThreadLocal: 这些都是在简单介绍了基本用法的基础上 ...

  9. idea 线程内存_Java线程池系列之-Java线程池底层源码分析系列(一)

    课程简介: 课程目标:通过本课程学习,深入理解Java线程池,提升自身技术能力与价值. 适用人群:具有Java多线程基础的人群,希望深入理解线程池底层原理的人群. 课程概述:多线程的异步执行方式,虽然 ...

最新文章

  1. 架构师书单 2nd Edition--转载
  2. clevo风扇调速软件_YVP变频调速电机
  3. 【年终总结】2019年有三AI知识星球做了什么,明年又会做什么
  4. cudamemcpy运行速度很慢_只要设置好这几个选项,让你的 PS CC 2019 运行如飞
  5. Java 必看的 Spring 知识汇总
  6. 项目管理控件Project Management Library
  7. Windows下设置自动关机的命令指示符
  8. 从BMW Vision iNEXT 看宝马如何进军自动驾驶 1
  9. big sur java home_无法在macOS Big Sur上运行Eclipse
  10. java imagemagick 灰度,如何在imagemagick中转换灰度bmp
  11. 在FMS服务器端侦听流的发布
  12. 7、python数据框重复值的查找和删除
  13. unexpected indent错误
  14. 程序设计——票务管理系统
  15. Cross_entropy和softmax
  16. php项目排期表模板,最近在开发后台管理,想问下广告排期表怎么做?
  17. Scrum立会报告+燃尽图(十月十七日总第八次)
  18. http1,http2,http3
  19. 最好和最便宜的照片存储网站(优质图库摄影)
  20. Android 切换系统语言

热门文章

  1. 前端学习(2983):一文理解数据劫持1
  2. 原生js实现tab栏切换效果
  3. [html] 写一个布局,它的宽度是不固定的100%,如果让它的宽度始终是高度的一半呢?
  4. [css] ui设计中px、pt、ppi、dpi、dp、sp之间的关系?
  5. [js] 举例说明Object.defineProperty会在什么情况下造成循环引用导致栈溢出?
  6. 前端学习(2823):sitemap配置
  7. 前端学习(2507):初始化多个实例化对象
  8. “约见”面试官系列之常见面试题第八篇说说原型与原型链(建议收藏)
  9. 第八十九期:还在手动盖楼领喵币?双十一这群开发者竟然如此「作弊」
  10. java学习(101):arraylist的遍历和增加