线程的状态

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

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. 获取JPEGImageEncoder和JPEGCode这两个类

    最近要对PDF做一些操作,在查看别人代码,拿过来借用的时候,由于代码不完整,引用的类也不全,导致JPEGImageEncoder和JPEGCode这两个类找不到,后来网上搜索了下,发现这两个类来自于J ...

  2. C++学习笔记(5)----重载自增自减运算符

    自增运算符“++”和自减运算符“--”分别包含两个版本.即运算符前置形式(如 ++x)和运算符后置形式(如 x++),这两者进行的操作是不一样的.因此,当我们在对这两个运算符进行重载时,就必须区分前置 ...

  3. How to block a specific IP Address using UFW

    How to block a specific IP Address using UFW The key to blocking a specific IP address with UFW is t ...

  4. iOS各种profile文件

    iOS是一个非常封闭的系统.授权文件(.mobileprovision)和签名证书文件(.cer)的存在就是为了验证身份信息.一般情况下,比如ssh登陆或者scp需要私钥.公钥对即可,iOS也是基本采 ...

  5. spring boot(6)-JdbcTemplate访问数据库

     pom.xml 添加jdbc模块和mysql依赖 <dependency> <groupId>org.springframework.boot</groupId&g ...

  6. python中的内容编码

    一.python编码简介 1)编码格式简介 python解释器在加载 .py 文件中的代码时,会对内容进行编码(默认ASCII),ASCII(American Standard Code for In ...

  7. 排查在 Azure 中新建 Windows 虚拟机时遇到的经典部署问题

    尝试创建新的 Azure 虚拟机 (VM) 时,遇到的常见错误是预配失败或分配失败. 当由于准备步骤不当,或者在从门户捕获映像期间选择了错误的设置而导致 OS 映像无法加载时,将发生预配失败. 当群集 ...

  8. switch与java

    switch结构可以更好的解决等值判断问题switch 选择结构的语法:switch (表达式){case 常量 1://代码块1:break;case 常量 2://代码块2:break;..... ...

  9. [BZOJ 2730][HNOI 2012] 矿场搭建

    2730: [HNOI2012]矿场搭建 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 2113  Solved: 979[Submit][Statu ...

  10. [Tyvj 模拟赛] 运

    运 [问题背景] zhx和妹子们玩数数游戏. [问题描述] 仅包含4或7的数被称为幸运数. 一个序列的子序列被定义为从序列中删去若干个数,剩下的数组成的新序列.两个子序列被定义为不同的当且仅当其中的元 ...