06-线程的初始化,中断以及其源码讲解

从这里开始,我们来了解线程的创建,

继承Thread类和实现Runnable接口,这两种方式可以说是中规中矩的,也是我们用的比较多的创建线程的方式,后面也是基于继承Thread类和实现Runnable接口的另外一种,也就是,使用匿名内部类的方式。然后我们会发现无论是使用继承Thread类还是使用实现Runnable接口的方式还是使用匿名内部类的方式,这三种方式在run()方法中都没有办法去抛出更多的异常,而且没有返回值。那么,我们就希望有一个能够抛出异常并且带返回值的这么一种线程,那么,这就是第四种创建线程的方式。第五个是通过定时器的方式,其实定时器也相当于是其中的一个线程,那么,它也是属于创建线程的一种方式,我们会去了解一些关于定时器的内容。第六种方式就是通过线程池的方式。第七种就是通过Lambda表达式实现,在JDK8中,新增了一个非常强大的一个Lambda表达式,那么,就可以通过这个表达式可以实现多线程这种对我们集合的操作、对我们的数据流的操作等等。也就是说,这七中都是实现多线程的方式,那么,我们逐一的来看。每一种方式都会通过代码来进行演示。当然了,我们不仅仅要看它的使用,我们要深入到它的原理上,从源码上能够来理解线程的创建。

第一种方式就是继承Thread类的方式。我们说万物皆对象,那么,线程也是一个对象。抽取对象的公共特征就可以形成一个类,那么,我们就继承Thread类。

那么,当前的这个Demo1就是一个线程类的子类,我们只要new出来Demo1的实例,那么,调用实例的线程的启动方法,那么,就可以启动一个线程了,那么,线程执行什么活呢?就是在线程里面定义过的一个run()方法,我们来看一下

这个run()方法的代码体非常的简单,这个方法是需要我们去重写的,所以,我们需要重写run()方法来覆盖掉它,对我们继承Thread类来讲,那么,源码中的这个run()方法中的代码体是没有任何意义的,一会我们会说,源码中的这个run()方法中的代码的意思。

我们重写run()方法就可以了。

我们现在是一直在打印,那么,线程它有没有名字呢?它有没有其他的一些属性呢?我们说,对象都是有属性和行为的,那么,Thread都有哪些属性和行为呢?我们可以通过源码来简单的看一下,

 

发现它的名字就是Thread-0和Thread-1,那么,我们并没有给它指定名字,它是如何叫做Thread-0和Thread-1的呢?我们来看一下源码。我们new Demo1()其实就是new的Thread类的构造方法,我们来看一下Thread类就有哪些构造方法,

 

我们再来运行

我们发现名字已经变成了我们指定的名字了。

我们发现Thread类的构造方法有很多,

我们不管使用哪种构造方法,在创建Thread类的时候,构造方法里面都有一个进行初始化的过程

我们来看一下init()

这个方法里面有很多的参数,第一个参数是线程组,这个线程组是干什么的呢?其实就是用于对线程进行分组的,非常好理解,我们这里也不去详细的去说它,就是说,你可以创建一个线程组,然后把线程加到这个组里面去。你可以把线程加到线程组中来,加到线程组中之后,我们来看一下这个线程组都有哪些方法呢?

线程组它是一种树状结构,可以这么理解,这是一个顶层的线程组

那么,这个线程组里面,下面可以接着放线程组,也可以方线程,

总之,它就是一层一层的往下走,

这就是所谓的线程组,通过上图我们可以看到,按照面向对象的思维方式,它肯定会有它的名字,还有获取它的上一级,下一级等功能。

这就是线程组,即对线程进行分组。

第二个参数是Runnable,也就是可以指定一个线程任务。第三个参数就是线程的名字。第四个参数是stackSize,那么,这个参数又是什么呢?我们看一下init()方法中调用的init()方法,

这个init()方法中也有一个stackSize参数,就是栈的大小,那么,这个参数到底是个何方神圣呢?

就是,开发Thread类的这个人,他也不知道stackSize到底是干什么用的,它是为虚拟机做一些事情,做一些什么事情呢?做一些虚拟机想干的事情,就是说,stackSize指定了之后,虚拟机可以通过stackSize做一些虚拟机想做的事情。当然了,很多的虚拟机是忽略stackSize的。所以,我们也不需要关stackSize这个参数了。

这样,关于初始化我们就看完了。

这样

虽然这里我们只是指定了线程的名字,其实我们知道,在初始化的时候,我们可以指定很多的参数来进行初始化我们的Thread。

这里还有一个问题,就是关于Daemon的问题,

Daemon是用来干什么的呢?我们称它叫做守护线程,或者说它是一个支持型线程,它是干什么的呢?它主要是作用在程序中后台调用,就是说,做一些支持性工作,就比如说垃圾回收线程吧,它就扔在后台,在后台自己默默的去做一些事,当程序执行完毕的时候,它即使执行不完毕,那么,它依然会跟着退出。

我们现在把这两个线程的Daemon都设置为True,也就是说,这两个线程都是支持型线程,那么,这两个线程既然都属于支持型线程了,那么,即使这两个线程的线程任务没有执行完毕,当主线程执行完毕之后,那么,这两个线程也依然会被退出,

这里加while(true)是为了让它一直执行。

运行程序,我们发现,还没执行程序就终止了。也就是说,线程还没有启动就已经退出去了。我们为了能够让它执行一点,我们让当前的这个线程休息一会,休息两秒钟,

我们再来执行,

我们发现,两秒钟过了之后,虽然线程d1和线程d2没有执行完毕,但是线程d1和线程d2依然随着主线程的退出而退出。

这就是所谓的Daemon。

关于线程的创建,我们就说完了。

接着我们看线程的中断以及销毁。

比如说,我们想让一个线程执行一段时间之后,我们不想让它再执行了,我们让它中断掉,怎么办呢?

首先,我们看到这里有三个方法,interrupt()、interrupted()、isInterrupted()。

interrupt()就是中断这个线程,

通过interrupted()可以看当前这个线程是否是中断了的,

isInterrupted()就是判断当前的这个线程是否中断。

 

其实它就是用于恢复中断的标志的。

 

我们发现执行出错了,但是,后面依然在执行。那么,这是怎么回事呢?

其实我们发现interrupt()它是JDK6.0才出现的,那么,在之前是用什么呢?

我们发现这里面有stop()让这个线程终止。那么,我们调用stop()看一下。

stop()方法过期了。

但是我们发现,这个时候就只有thread2在执行了。

为什么不推荐我们去使用stop()了呢?其实就是因为,使用stop()去进行停止的时候,那么,这个线程所获取的锁、获取的其他资源都没有被释放掉,因此,这个stop()只是让这个线程无限期的等待下去,所以这种方式是非常不好的,因此就被停用掉了。那么,推荐我们所使用的方法就是interrupt(),但是刚才我们也看到了,通过interrupt并没有让我们的线程终止掉,这是怎么回事呢?这就需要我们自己去处理,我们写代码的时候就不要这么去写了

 

那么,我们应该怎么去写呢?在这里我们就不再写while(true)了,我们想让它正常的去中断,如何让它正常中断呢?其实就是为了让它尽可能的把线程执行完毕,那么,在线程执行完毕的这个阶段,那么,它肯定是可以把所有的资源都释放掉,把该做的事情都做好,那么,我们就可以通过

我们通过这个interrupted(),如果不中断的话,那么,我们就去执行,当我们调用了interrupt()之后,

它就会把中断标志修改为“是中断”,即interrupted()方法返回值为true,所以,此时

这里就不再执行了,那么while这段代码就直接退出了,也就是说线程也就终止了。

这样也就是说我们的线程同样的也就正常的结束了。

这种中断方式是我们比较推荐的一种方式,

我们这里已经了解了关于中断,那么,我们以后在进行中断的时候,我们就不要再使用像stop()等,我们就使用interrupt()就可以了。

这就是安全的终止线程。那么,线程的关于启动和中断我们就说完了。

猜你喜欢

转载自blog.csdn.net/G_66_hero/article/details/85388990