线程的状态

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

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. WinForm实现Rabbitmq官网6个案例-Routing

    代码: namespace RabbitMQDemo { public partial class Routing : Form { private string exchangeName = &qu ...

  2. Web开发须知的浏览器内幕 缓存与存储篇(1)

    本文禁止转载,由UC浏览器内部出品. 0.前言 大纲 浏览器缓存和存储相关的功能分为四类: 加载流程 Memory Cache Application Cache(简称AppCache) HTTP C ...

  3. Java设计模式—状态模式

    状态模式又是一个比较难的设计模式 定义如下: 当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类. 个人理解:通俗的讲,状态模式就是状态的改变引起了行为的改变,但是,我们只能看到行为的 ...

  4. BAT脚本编写教程简单入门篇

    BAT脚本编写教程简单入门篇 批处理文件最常用的几个命令: echo表示显示此命令后的字符 echo on  表示在此语句后所有运行的命令都显示命令行本身 echo off 表示在此语句后所有运行的命 ...

  5. Excel 操作总结

    1.excel 表格中换行:Alt+Enter; 2.Excel2003以上版本设置下拉菜单     DATA->Data Validation ->Data Validation -&g ...

  6. Azure 中部署Gitlab的方法

    一.Azure 中创建Gitlab虚拟机(1).登陆Azure:打开Azure 官网,点击右侧上方的登陆Azure门户,输入Azure帐号与密码,点击 登陆 . (2).创建Gitlab虚拟机:登陆A ...

  7. 【Oracle】等待事件详细内容

    一.等待事件的相关知识 1.1 等待事件主要可以分为两类,即空闲(IDLE)等待事件和非空闲(NON-IDLE)等待事件.1). 空闲等待事件指ORACLE正等待某种工作,在诊断和优化数据库的时候,不 ...

  8. LDF文件丢失, 如何仅用MDF文件恢复数据库呢?

    笔者的一个大小为2 TB的SQL Server的database的LDF文件在玩存储盘映射的过程中莫名其妙的丢失了. 好在MDF文件还在. 笔者慌了, Bruce Ye告诉笔者, 不用着急, 光用MD ...

  9. Compare DML To Both REDO And UNDO Size

    SUMMARY you can remember undo rule  the same to redo if you want demo rule that you can look up the ...

  10. 41、Thead线程 System.Thread与互斥体Mutex

    Thead线程 System.Thread 使用Thread类可以创建和控制线程.下面的代码是创建和启动一个新线程的简单例子.Thread 类的构造函数重载为接受ThreadStart和Paramet ...