线程的状态

一个线程会有如下五个状态

1.新建:线程在被创建时会暂时处于这种状态,此时系统为线程分配资源并对其进行初始化

2.就绪:此时线程已经可以运行,只是系统没有为其分配CPU时间。

3.运行:系统为线程分配了CPU时间,线程处于运行状态

4.阻塞:线程由于等待I/O等原因无法继续运行,等待结束后就又进入就绪状态了,阻塞时系统不会再为其分配CPU时间。

5.死亡:线程执行完所有的代码,此时线程不不可以再调度

上面五种状态中,只有在运行和阻塞状态时才有被终结的机会,其它状态时都无法终结。

在运行时终结

在运行状态时终结有一个最简单粗暴的办法,前面我们也使用过这种例子:

定义全局变量:

volatile boolean run = true;

线程一执行如下代码:

while(run) {
//Do something
}

这时在另一个线程中将run的值设置为false,线程一再检查run值的时候就不再继续运行了。

除了这种简单粗暴的方法之外,还可以使用中断信号来停止正在运行的线程。中断信号是一个线程发送给另一个线程的信号,这个信号告诉接收线程:你应该尽快停止运行。注意这只是一个信号而不是命令,接收方可以选择“听从劝告”,也可以选择“一意孤行”。前面我们讲线程池的时候有说过当调用shutdownNow()方法时,会向线程池中所有(注意是所有)线程发送一个中断信号,但是如果我只想对某个线程发送中断信号该怎么处理呢?其实我们也可以使用submit()方法返回的Future对象来发送中断信号,具体代码如下:

class InterruptableThread implements Runnable {
private int value;
public void run() {
Thread currentThread = Thread.currentThread();
while(!currentThread.isInterrupted()) {
value++;
}
System.out.println("Interrupted value = "+value);
}
}
class AlwaysRunThread implements Runnable {
public void run() {
while(true) {
System.out.println("Running");
}
}
}
public class InterruptRunningTest {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
Future interruptableFuture= exec.submit(new InterruptableThread());
Future alwaysRunFuture= exec.submit(new AlwaysRunThread());
exec.shutdown();
interruptableFuture.cancel(true);
alwaysRunFuture.cancel(true);
}
}

输出结果如下,此处省略无数个Running。

Running

Interrupted value = 12731

Running

Running

...

代码中创建了两个线程,第一个线程调用Thread类的currentThread()方法获得当前线程的对象,然后进入循环,每次循环都检查当前线程是否收到了中断信号,如果收到中断信号就中止循环。第二个线程定义了一个死循环,没有检查是否收到中断信号。我们在主线程中获得了两个线程的Future类,并调用cancel(true)方法向两个线程发送中断信号。根据结果我们可以看出第一个线程收到了中断信号并退出了;而第二个线程始终在运行,因为它没有检查是否有中断信号,即忽略了中断信号。这种让线程自己决定什么时候退出的机制是合理的,在收到信号后线程可以根据自己的需要释放持有的资源,如果粗暴的将线程中止可能发生内存泄露、死锁等问题。

在阻塞时终结

造成线程阻塞的原因有如下三种:

1. 线程内部调用了Thread.sleep()方法使线程进入休眠状态或者调用Object.wait()使线程被挂起

2. 线程在等待I/O

3. 线程在试图获取锁的时候,锁已经被其它线程获取,这时线程会被阻塞。

我们对处于这三种阻塞的线程发送中断信号,看看其反应:

class SleepBlockedThread implements Runnable {
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
System.out.println("SleepThread interrupted");
}
}
}
class IOBlockedThread implements Runnable {
public void run() {
try {
InterruptBlockedThread.input.read();
} catch (IOException e) {
}
System.out.println("IOThread interrupted");
}
}
class LockBlockedThread implements Runnable {
private Lock lock;
LockBlockedThread(Lock lock) {
this.lock = lock;
}
public void run() {
lock.lock();
System.out.println("LockThread interrupted");
}
}
public class InterruptBlockedThread {
public static InputStream input;
public static void main(String[] args) throws Exception {
new ServerSocket(8010);
input = new Socket("localhost", 8010).getInputStream();
ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(new SleepBlockedThread());
exec.submit(new IOBlockedThread());
Lock lock = new ReentrantLock();
lock.lock();//先在主线程里获取锁
exec.submit(new LockBlockedThread(lock));
exec.shutdownNow();
}
}

输出结果如下,并且程序始终没有退出:

SleepThread interrupted

从运行结果我们可以看出只有当线程被挂起时才可以被中断信号终结,其它两种情况都不能终结。但是我们可以通过其它办法将其终结。IO阻塞可以关闭让其阻塞的输入输出流,即干掉让其阻塞的根源;Lock阻塞可以调用lockInterruptibly()方法来替代lock()方法。具体代码如下:

class IOBlockedThread implements Runnable {
public void run() {
try {
InterruptBlockedThread.input.read();
} catch (IOException e) {
}
System.out.println("IOThread interrupted");
}
}
class LockBlockedThread implements Runnable {
private Lock lock;
LockBlockedThread(Lock lock) {
this.lock = lock;
}
public void run() {
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
}
System.out.println("LockThread interrupted");
}
}
public class InterruptBlockedThread {
public static InputStream input;
public static void main(String[] args) throws Exception {
new ServerSocket(8010);
input = new Socket("localhost", 8010).getInputStream();
ExecutorService exec = Executors.newCachedThreadPool();
exec.submit(new IOBlockedThread());
Lock lock = new ReentrantLock();
lock.lock();//先在主线程里获取锁
exec.submit(new LockBlockedThread(lock));
exec.shutdownNow();
input.close();
}
}

输出结果如下:

LockThread interrupted

IOThread interrupted

由结果可以看出以上方法奏效了,这两个线程都被终结了。如果使用内置锁的时候被阻塞了,那么线程将无法被终结,感兴趣的读者可以自行测试。

总结

当线程处于运行状态时,可以向线程发送信号,同时线程每次循环都检查是否收到了信号,这样线程就可以自行退出。当线程处于阻塞状态时,只有线程被Thread.sleep()或者Object.wait()挂起的时候才可以接收中断信号。由于IO导致的阻塞可以通过关闭IO来实现,终结线程。显式锁可以通过调用lock.lockInterruptibly()方法来实现一个可中断的锁。内置锁和lock.lock()导致的阻塞是不能被终结的。

公众号:今日说码。关注我的公众号,可查看连载文章。遇到不理解的问题,直接在公众号留言即可。

Java并发编程(七)终结线程的更多相关文章

  1. Java并发编程系列-(2) 线程的并发工具类

    2.线程的并发工具类 2.1 Fork-Join JDK 7中引入了fork-join框架,专门来解决计算密集型的任务.可以将一个大任务,拆分成若干个小任务,如下图所示: Fork-Join框架利用了 ...

  2. 【Java并发编程一】线程安全和共享对象

    一.什么是线程安全 当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用代码代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的 ...

  3. 【java并发编程实战】-----线程基本概念

    学习Java并发已经有一个多月了,感觉有些东西学习一会儿了就会忘记,做了一些笔记但是不系统,对于Java并发这么大的"系统",需要自己好好总结.整理才能征服它.希望同仁们一起来学习 ...

  4. Java并发编程:进程和线程的由来(转)

    Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够融会贯通 ...

  5. 【Java并发编程六】线程池

    一.概述 在执行并发任务时,我们可以把任务传递给一个线程池,来替代为每个并发执行的任务都启动一个新的线程,只要池里有空闲的线程,任务就会分配一个线程执行.在线程池的内部,任务被插入一个阻塞队列(Blo ...

  6. Java并发编程扩展(线程通信、线程池)

    之前我说过,实现多线程的方式有4种,但是之前的文章中,我只介绍了两种,那么下面这两种,可以了解了解,不懂没关系. 之前的文章-->Java并发编程之多线程 使用ExecutorService.C ...

  7. Java并发编程(02):线程核心机制,基础概念扩展

    本文源码:GitHub·点这里 || GitEE·点这里 一.线程基本机制 1.概念描述 并发编程的特点是:可以将程序划分为多个分离且独立运行的任务,通过线程来驱动这些独立的任务执行,从而提升整体的效 ...

  8. Java并发编程(01):线程的创建方式,状态周期管理

    本文源码:GitHub·点这里 || GitEE·点这里 一.并发编程简介 1.基础概念 程序 与计算机系统操作有关的计算机程序.规程.规则,以及可能有的文件.文档及数据. 进程 进程是计算机中的程序 ...

  9. java并发编程实战之线程安全性(一)

    1.1什么是线程安全性 要对线程安全性给出一个确切的定义是非常复杂的.最核心的概念就是正确性.正确性:某个类的行为与其规范完全一致.在良好的规范中通常会定义各种不变性条件来约束对象的状态,以及定义各种 ...

  10. Java并发编程学习:线程安全与锁优化

    本文参考<深入理解java虚拟机第二版> 一.什么是线程安全? 这里我借<Java Concurrency In Practice>里面的话:当多个线程访问一个对象,如果不考虑 ...

随机推荐

  1. Java内存模型(JMM)以及 垃圾回收机制 小结

    内存模型: 内存模型描述了程序中各个变量(实例域.静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存中取出变量这样的底层细节,对象最终是存储在内存里面的,这点没有错,但是编译 ...

  2. Java设计模式—代理模式

    代理模式(Proxy Pattern)也叫做委托模式,是一个使用率非常高的模式. 定义如下:     为其他对象提供一种代理以控制对这个对象的访问. 个人理解:        代理模式将原类进行封装, ...

  3. 【vue】混合模式

    因为工作的分配,写财务的对账部分,因为3个页面的设计和功能基本相同,都是查询筛选表格,所以用混合模式优化了部分代码.用混合把一些共用的东西抽离了出来. 具体使用方法参照文档. https://cn.v ...

  4. MySQL数据库(6)----配置文件 my.cnf 的使用

    1. 使用源码安装好MySQL后,其配置文件一般位于 /usr/local/my.cnf,可以使用如下命令查看查看配置文件的搜索顺序: root@javis:~$ mysqld --help --ve ...

  5. Android图片处理--缩放

    PS:在开发中我们会遇到一些图片处理问题,比如说缓存图片了.限制图片大小了.查看图片了等.上一篇文章介绍了图片的全景效果查看,今天介绍一个图片缩放,我们如果有时间的话,可以自己写一个属于自己的库,里面 ...

  6. Fragment 重叠 遮盖问题

    1.导致Fragment 重叠 和遮盖的原因 主要还是因为Fragment的状态保存机制,当系统内存不足时,Fragment的主Activity被回收,Fragment的实例并没有随之被回收. Act ...

  7. Tinker + Bugly + Jenkins 爬坑之路

    前阵子 Android 端的线上崩溃比较多,热修复被提上日程.实现方案是 Tinker,Jenkins 打包,最后补丁包上传到 Bugly 进行分发.主要在 Jenkins 打包这一块爬了不少坑,现记 ...

  8. 读取本地IP地址和子网页码

    #region 读取本地IP地址和子网页码 //读取本地IP地址和子网页码 NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterf ...

  9. codeforces 703E Mishka and Divisors

    codeforces 703E Mishka and Divisors 题面 给出大小为\(1000\)的数组和一个数\(k\),求长度最短的一个子序列使得子序列的元素之积是\(k\)的倍数,如果有多 ...

  10. 认识 Java(配置环境变量)

    1. Java 简介 Java由Sun Microsystems公司于1995年5月推出,是一种面向对象的编程语言.在2009年4月20号,ORACLE (甲骨文)收购了 Sun 公司,也就是说 Ja ...