线程的状态

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

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. 鼠标事件-拖拽2(不能拖出指定对象的div)

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  2. Java学习笔记(2)----散列集/线性表/队列/集合/图(Set,List,Queue,Collection,Map)

    1. Java集合框架中的所有实例类都实现了Cloneable和Seriablizable接口.所以,它们的实例都是可复制和可序列化的. 2. 规则集存储的是不重复的元素.若要在集合中存储重复的元素, ...

  3. Android 弹出框Dialog并缩放图片

    java代码 Activity: // 调用dialog,参数:1:自身的activity,2:Bitmap bm读取好的图片 MyDialog dialog = new MyDialog(MyAct ...

  4. spring cloud zuul 配置

    参考:http://www.ityouknow.com/springcloud/2017/06/01/gateway-service-zuul.html spring boot版本:2.0.3.REL ...

  5. kernel update

    CentOS/RHEL更新包:https://rhn.redhat.com/errata/RHSA-2017-1382.html yum makecache --更新源 yum update sudo ...

  6. SQL Server 2014 虚拟机的自动备份 (Resource Manager)

    自动备份将在运行 SQL Server 2014 Standard 或 Enterprise 的 Azure VM 上自动为所有现有数据库和新数据库配置托管备份到 Azure. 这样,便可以配置使用持 ...

  7. win10系统80端口被System (PID=4)占用的解决

    今天想用wamp搭建虚拟目录.发现80端口被占用,操作挺麻烦的,所以想要更改. 具体流程如下: 1.“win+R”输入“cmd”,然后输入“netstat -ano | findstr "8 ...

  8. python3 邮件,多用户,抄送

    #!/usr/bin/env python # -*- coding: utf-8 -*- # @Time : 2017/8/19 10:44 # @Author : Lys # @Site : # ...

  9. [BZOJ 4555][Tjoi2016&Heoi2016]求和

    题意 给定 $n$ , 求下式的值: $$ f(n)= \sum_{i=0}^n\sum_{j=0}^i\begin{Bmatrix}i\\ j\end{Bmatrix}\times 2^j\time ...

  10. SOJ 1017 Power of Cryptography 库函数精度

    Background Current work in cryptography involves (among other things) large prime numbers and comput ...