Java线程的启动与中止
一、线程与进程的关系
关于进程与线程,百度百科上是这样描述的:
进程(Process) 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
线程(thread) 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
简单的总结一下,就变成了下面的结果:
进程(Process): 程序运行资源分配的最小单位,进程内部有多个线程,会共享这个进程的资源。
线程(thread) : CPU调度的最小单位,必须依赖进程而存在。
也就是说 线程与进程的关系,就像玄幻小说中虫族的母体和子体一样,子体离开了母体便不能存活。线程亦是如此,必须依赖于进程而存在。
二、Java线程的启动方式
关于Java的线程,我们首当其冲的会想到java.lang.Thread
类,这是一种最简单的创建线程的方式,我们只需要通过继承Thread
类就可以实现:
/**
* @author cai
* 通过继承Thread类的方式
*/
private static class UserThread extends Thread{
/**
* 重写 Thread 类的 run() 方法
*/
@Override
public void run() {
System.out.println("this is a thread for extends Thread");
}
}
这里,我们重写了Thread
类中的run()
方法,并打印一条语句来表示线程的启动方式,现在我们来测试一下:
public static void main(String[] args) {
// 继承Thread类的方式
Thread thread = new UserThread();
thread.start();
}
控制台的打印结果:
this is a thread for extends Thread
从打印结果可以看出,我们的线程正常的启动,中间没有出现意外。除了这种方式之外,JDK
内部又给我们提供了一个接口类:java.lang.Runnable
,同样,我们也可以通过实现该接口,重写run()
方法来启动一个新的线程:
/**
* @author cai
* 通过实现Runnable接口的方式
*/
private static class UserRunnable implements Runnable{
/**
* 重写 Runnable 类的 run() 方法
*/
@Override
public void run() {
System.out.println("this is a thread for implements Runnable");
}
}
这里我们同样打印一句话来表示此线程的启动方式,现在来测试一下:
public static void main(String[] args) throws {
// 实现Runnable接口的方式
// 这里注意:所有线程的启动,都必须通过调用Thread类中start()方法来实现
Runnable runnable = new UserRunnable();
new Thread(runnable).start();
}
相信上面的代码,小伙伴们都能看懂(多注意一下第二行的注释,这是重点),现在来看看控制台的打印结果:
this is a thread for implements Runnable
同理,这里的线程也正常的启动了。但看到这里,小伙伴们可能就会产生疑问,为什么JDK
要多此一举,提供了一种Thread
类后,为什么还要提供Runnable
接口类呢?
在这里给有这个疑惑的小伙伴们答疑下:
因为Java是单继承,只能继承一个类,不能继承多个类。而在我们某些实际的业务中,我们可能需要继承一个类来处理逻辑业务,那么就不能再继承Thread类来处理线程相关的操作了。所以
JDK
又为我们提供了一个实现Runnable接口的方式,而且在Java中,一个类是可以实现多个接口的,这样我们在使用第二种方式处理线程就不会有顾忌了。(这里比较有意思的是:Thread类实现了Runnable接口,有兴趣的小伙伴可以去看一下源码。)
那么除了上面的两种方式之外,Java是否提供了第三种方式呢?答案是肯定的,从JDK1.5
开始,JDK
为我们提供了一个新的接口类:java.util.concurrent.Callable
,我们可以通过实现这个接口来启动一个新得线程,而这种方式与实现Runnable
接口来启动线程所不同的是,它会带有一个返回值,我们来看一下代码:
/**
* @author cai
* 通过实现Callable接口的方式
* 带返回值
*/
private static class UserCallable implements Callable<String>{
/**
* 重写 Callable 类的 run() 方法
* @return
* @throws Exception
*/
@Override
public String call() throws Exception {
return "this is a thread for implements Callable(return String).";
}
}
测试一下:
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 实现Callable接口的方式 带返回值
UserCallable callable = new UserCallable();
FutureTask<String> future = new FutureTask<String>(callable);
new Thread(future).start();
System.out.println(future.get());
}
我们这里将返回值打印一下:
this is a thread for implements Callable(return String).
可以看出,我们的线程正常的启动,没有问题。
那么看了以上三种Java线程的启动方式,相信肯定有很多小伙伴会好奇,如果我要中止一个线程,我需要怎么做呢?让我们来一起看看吧。
三、Java线程的中止
怎样让一个正在运行的线程安全的停止工作呢?这里有两种方法:
1、线程自然的终止:程序正常的执行完或者抛出未处理的异常。
程序正常的执行完就不必再说,以上的代码都属于此类,我们来看一看抛出未处理异常的情况,这里我们将上述实现Runnable
接口来启动线程的代码修改一下:
/**
* @author cai
* 通过实现Runnable接口的方式
*/
private static class UserRunnable implements Runnable{
/**
* 重写 Runnable 类的 run() 方法
*/
@Override
public void run() {
// 重点加了这样的一行代码
int i = 10 / 0;
System.out.println("this is a thread for implements Runnable");
}
}
这里我们加了一行代码,小伙伴们应该都可以看懂,这行代码是必定会抛出异常的,但我们又没有对异常进行处理,现在来看一下控制台的打印结果:
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at com.cai.thread.create.NewThread$UserRunnable.run(NewThread.java:39)
at java.lang.Thread.run(Thread.java:748)
从结果可以看出,程序运行到了int i = 10 / 0
这里就停止了,并没有打印出下一行的结果,由此可见,线程到了这里便停止了,没有再走下去。
2、调用JDK
为我们提供的一系列方法
stop(),resume(),suspend(),interrupt(),isInterrupted(),interrupted()
这些方法都是JDK
为我们提供的,但是``stop(),resume(),suspend()`已经不建议使用了,原因如下:
stop()
: 会导致线程不会正常的释放资源,“恶意”的中断当前正在运行的线程,不管线程的逻辑是否完整。
suspend()
与resume()
: 极易造成公共的同步对象的独占,使得其他线程无法访问公共同步对象,产生死锁。(这个例子以后在讲线程锁的时候会解释。)
我们先来看一下调用stop()
的例子:
/**
* @author cai
* 调用 stop() 方法的例子
*/
private static class UserRunnable implements Runnable{
@Override
public void run() {
try {
// 让此线程休眠1秒钟
Thread.sleep(1000);
}catch (Exception e){
// 异常 捕获处理
}
// 此处不会运行,控制台不会打印
// 若实际开发中,这里要处理一个很重要的业务逻辑,那么这里就会有很大的问题。
// 所以不建议使用
System.out.println("代码到此处不会运行");
}
}
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new UserRunnable();
Thread thread = new Thread(runnable);
thread.start();
// 强行中止线程
// 从这里可以看出,JDK已经不建议使用stop()方法了,添加了@Deprecated注解
thread.stop();
}
我这里将测试代码也一并贴了上去,可以在代码的注释中得到相关的结果。讲完这些,我们来看看剩下的三个方法:interrupt(),isInterrupted(),interrupted()
interrupt()
:调用此方法会中断一个线程,但并不是强行关闭这个线程,只是先给这个线程打个招呼,同事将线程的中断标志位设置为true
,线程是否中断,由线程本身决定。
isInterrupted()
:判断当前的线程是否处于中断状态(返回true
或者false
)。
interrupted()
:静态方法,判断当前的线程是否处于中断状态,同时将中断的标志位设置为false
。注意:如果方法中抛出了
InterruptedException
异常,那么线程的中断标志位会被复位成false
,如果确实需要中断线程的操作,我们需要在catch
语句中再次调用interrupt()
方法。
解释完了,直接上代码吧:
/**
* @author cai
* 调用 interrupt(),isInterrupted(),interrupted() 方法的例子
*/
private static class UserThread extends Thread{
// 给线程一个名字,创建对象时赋值
public UserThread(String threadName) {
super(threadName);
}
@Override
public void run() {
// 获得线程的名字
String threadName = Thread.currentThread().getName();
try {
// @2
System.out.println(threadName+" flag is " + isInterrupted());
// 休眠一秒钟 需要捕获异常 InterruptedException @3
Thread.sleep(1000);
}catch (InterruptedException e){
// 打印一下 isInterrupted() 的状态 @4
System.out.println(threadName+" catch interrput flag is " + isInterrupted());
// 调用 interrupt() 方法 中断线程操作 @5
interrupt();
}
// 打印线程的名字 @6
System.out.println(threadName+" interrput flag is " + interrupted());
// @7
System.out.println(threadName+" interrput flag is " + isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
// interrupt(),isInterrupted(),interrupted() 演示
Thread thread = new UserThread("caiThread");
thread.start();
// @1
thread.interrupt();
}
这里为了方便解释,我分了步骤:
1、@1 的位置调用的
interrupt()
方法,所以这里的标志位是true
,所以 @2 的位置打印结果为true
。2、@3 位置的
sleep
方法会抛出InterruptedException
异常,我这里捕获了,在看之前的理论,抛出此异常,标志位会重置为false
,所以@4 这里的打印结果为false
。3、@5 位置再次调用了
interrupt()
,又把标志位改为了false
,所以 @6 这里打印的结果为true
,但是这里注意的是,@6 调用了interrupted()
这个静态方法,所以标志位又变为了false
,所以@7 打印的结果为false
。
控制台打印结果:
caiThread catch flag is true
caiThread catch interrput flag is false
caiThread interrput flag is true
caiThread interrput flag is false
小伙伴们可以通过对比结果、代码和解释一起看,相信还是很容易明白的。
对线程的了解再多一点点
Java线程总归下来有五种状态:
新建、就绪、阻塞、运行、死亡
而这里对应的方法却有很多种,具体的关系,我这里准备了一张图:
这张图上面的各种方法我都会在下次的文章中分享,这次的分享就到这里,希望大家能够喜欢。
最后
代码地址:https://github.com/caimm123/javaThread (先阅读
README.md
文件)注:若有转载,请标明原处。如若有错,也欢迎小伙伴前来指正。
Java线程的启动与中止的更多相关文章
- 面试官:Java 线程如何启动的?
摘要:Java 的线程创建和启动非常简单,但如果问一个线程是怎么启动起来的往往并不清楚,甚至不知道为什么启动时是调用start(),而不是调用run()方法呢? 本文分享自华为云社区<Threa ...
- Java线程的启动和停止(一)
如何构造线程 在运行线程之前需要先构造线程对象,线程对象的构造需要指定线程所需要的属性,比如:所属线程组.线程优先级.是否为Daemon线程等信息.下面我们看一下,java.lang.Thread中对 ...
- Java线程池的那些事
熟悉java多线程的朋友一定十分了解java的线程池,jdk中的核心实现类为java.util.concurrent.ThreadPoolExecutor.大家可能了解到它的原理,甚至看过它的源码:但 ...
- 基于 JVMTI 实现 Java 线程的监控(转)
随着多核 CPU 的日益普及,越来越多的 Java 应用程序使用多线程并行计算来充分发挥整个系统的性能.多线程的使用也给应用程序开发人员带来了巨大的挑战,不正确地使用多线程可能造成线程死锁或资源竞争, ...
- Java线程:创建与启动
Java线程:创建与启动 一.定义线程 1.扩展java.lang.Thread类. 此类中有个run()方法,应该注意其用法: public void run() 如果该线程是使用独立的 R ...
- 简说Java线程的那几个启动方式
本文首发于本博客 猫叔的博客,转载请申明出处 前言 并发是一件很美妙的事情,线程的调度与使用会让你除了业务代码外,有新的世界观,无论你是否参与但是这对于你未来的成长帮助很大. 所以,让我们来好好看看在 ...
- 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考
2018年12月12日18:44:53 一个ScheduledExecutorService启动的Java线程无故挂掉引发的思考 案件现场 不久前,在开发改造公司一个端到端监控日志系统的时候,出现了一 ...
- java线程启动原理分析
一.前言不知道哪位古人说:人生三大境界.第一境界是:看山是山看水是水:第二境界是看山不是山看水不是水:第三境界:看山还是山看水还是水.其实我想对于任何一门技术的学习都是这样.形而上下者为之器,形而上者 ...
- Java多线程之线程的启动
Java多线程之线程的启动 一.前言 启动线程的方法有如下两种. 利用Thread 类的子类的实例启动线程 利用Runnable 接口的实现类的实例启动线程 最后再介绍下java.util.concu ...
随机推荐
- Git经典学习指南
https://www.liaoxuefeng.com/ 转载于:https://blog.51cto.com/4402071/1977945
- ip地址与运算 ipcalc命令
http://man.linuxde.net/ipcalc 转载于:https://blog.51cto.com/sonlich/2064133
- NetCore项目实战篇02---全局异常处理
在 .netcore中可以自定义自己的异常类型,步骤如下: 1.自定义自己的异常类型UserOperationException 并继承自Exception public class UserOper ...
- Nmon 监控性能分析
一.CPU 信息 1.折线图中蓝线为 cpu 占有率变化情况:粉线为磁盘 IO 的变化情况: 2.下面表各种左边的位磁盘的总体数据,包括如下几个: Avg tps during an interval ...
- python requests 接口测试
1.get方法请求接口 url:显而易见,就是接口的地址url啦 headers:请求头,例如:content-type = application/x-www-form-urlencoded par ...
- spark系列-8、Spark Streaming
参考链接:http://spark.apache.org/docs/latest/streaming-programming-guide.html 一.Spark Streaming 介绍 Spark ...
- Z - New Year Tree CodeForces - 620E 线段树 区间种类 bitset
Z - New Year Tree CodeForces - 620E 这个题目还没有写,先想想思路,我觉得这个题目应该可以用bitset, 首先这个肯定是用dfs序把这个树转化成线段树,也就是二叉树 ...
- OpenCV 经纬法将鱼眼图像展开
文章目录 前言 理论部分 鱼眼展开流程 鱼眼标准坐标计算 标准坐标系与球坐标的转换 代码实现 测试效果如下图 总结 this demo on github 前言 鱼眼镜头相比传统的镜头,视角更广,采集 ...
- CF-557C Arthur and Table 权值线段树
Arthur and Table 题意 一个桌子有n个腿,每个腿都有一个高度,当且仅当最高的腿的数量大于桌子腿数量的一半时,桌子才是稳定的.特殊的是当只有一个腿时,桌子是稳定的,当有两个腿时两个腿必须 ...
- python语法学习第一天--变量、运算符、数据类型
变量:计算机中的一块内存,给变量赋值意味着将值存入内存中 python中变量不用类型声明(根据赋的值决定类型),但使用时(创建时)必须赋值(=赋值). 多个变量的赋值: ①a=b=c=1; ②a,b, ...