Java 并发编程 - 线程基础
1. 线程的创建
首先我们来复习我们学习 java 时接触的线程创建,这也是面试的时候喜欢问的,有人说两种也有人说三种四种等等,其实我们不能去死记硬背,而应该深入理解其中的原理,当我们理解后就会发现所谓的创建线程实质都是一样的,在我们面试的过程中如果我们能从本质出发回答这样的问题,那么相信一定是个加分项!好了我们不多说了,开始今天的 code 之路
1.1 **继承 Thread 类创建线程 **
**
这是我们最常见的创建线程的方式,通过继承
Thread类来重写run方法,
代码如下:
测试方法:
结果:

继承
Thread的线程创建简单,启动时直接调用start方法,而不是直接调用run方法。直接调用run等于调用普通方法,并不是启动线程
1.2 **实现 Runnable 接口创建线程 **
**
上述方式我们是通过继承来实现的,那么在
java中提供了Runnable接口,我们可以直接实现该接口,实现其中的run方法,这种方式可扩展性更高
代码如下:
测试代码:
运行结果:

1.3 实现 Callable 接口创建线程
这种方式是通过 实现
Callable接口,实现其中的call方法来实现线程,但是这种线程创建的方式是依赖于 ****FutureTask **包装器**来创建Thread, 具体来看代码
代码如下:
测试代码:
结果:

有的时候,我们可能需要让一步执行的线程在执行完成以后,提供一个返回值给到当前的主线程,主线程需要依赖这个值进行后续的逻辑处理,那么这个时候,就需要用到带返回值的线程了
关于线程基础知识的如果有什么问题的可以在网上查找资料学习学习!这里不再阐述
2. 线程的生命周期
Java 线程既然能够创建,那么也势必会被销毁,所以线程是存在生命周期的,那么我们接下来从线程的生命周期开始去了解线程。
2.1 线程的状态
2.1.1 线程六状态认识
线程一共有 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)
NEW:初始状态,线程被构建,但是还没有调用 start 方法
RUNNABLED:运行状态,JAVA 线程把操作系统中的就绪和运行两种状态统一称为“运行中”
BLOCKED:阻塞状态,表示线程进入等待状态, 也就是线程因为某种原因放弃了 CPU 使用权,阻塞也分为几种情况
TIME_WAITING:超时等待状态,超时以后自动返回
TERMINATED:终止状态,表示当前线程执行完毕

2.1.2 代码实操演示
代码:
启动一个线程前,最好为这个线程设置线程名称,因为这样在使用 jstack 分析程序或者进行问题排查时,就会给开发人员提供一些提示
2.1.3 线程的状态堆栈
➢ 运行该示例,打开终端或者命令提示符,键入“ jps ”, ( JDK1.5 提供的一个显示当前所有 java 进程 pid 的命令)
➢ 根据上一步骤获得的 pid ,继续输入 jstack pid (jstack是 java 虚拟机自带的一种堆栈跟踪工具。jstack 用于打印出给定的 java 进程 ID 或 core file 或远程调试服务的 Java 堆栈信息)

3. 线程的深入解析
3.1 线程的启动原理
前面我们通过一些案例演示了线程的启动,也就是调用
start()方法去启动一个线程,当run方法中的代码执行完毕以后,线程的生命周期也将终止。调用start方法的语义是当前线程告诉JVM,启动调用start方法的线程。我们开始学习线程时很大的疑惑就是 启动一个线程是使用
start方法,而不是直接调用run方法,这里我们首先简单看一下start方法的定义,在Thread类中
这里我们能看到
start方法中调用了native方法start0来启动线程,这个方法是在Thread类中的静态代码块中注册的 , 这里直接调用了一个native方法registerNatives
由于
registerNatives方法是本地方法,我们要看其实现源码则必须去下载jdk源码,关于jdk及虚拟机hotspot的源码下载可以去openJDK官网下载 ,参考:我们可以本地查看源码或者直接去 http://hg.openjdk.java.net/jdk8u/jdk8u60/jdk/file/935758609767/src/share/native/java/lang/Thread.c 查看
Thread类对应的本地方法.c文件,

如上图,我们本地下载
jdk工程,找到src->share->native->java->lang->Thread.c文件

上面是
Thread.c中所有代码,我们可以看到调用了RegisterNatives同时可以看到method集合中的映射,在调用本地方法start0时,实际调用了JVM_StartThread,它自身是由c/c++实现的,这里需要在 虚拟机源码中去查看,我们使用的都是hostpot虚拟机,这个可以去openJDK官网下载,上述介绍了不再多说我们看到
JVM_StartThread的定义是在jvm.h源码中,而jvm.h的实现则在虚拟机hotspot中,我们打开hotspot源码,找到src -> share -> vm -> prims ->jvm.cpp文件,在2955行,可以直接检索JVM_StartThread, 方法代码如下:
JVM_ENTRY是用来定义JVM_StartThread函数的,在这个函数里面创建了一个真正和平台有关的本地线程, 上述标记 <2> 处
为了进一步线程创建,我们在进入
new JavaThread(&thread_entry, sz)中查看一下具体实现过程,在thread.cpp文件1566行处定义了new的方法

对于上述代码我们可以看到最终调用了
os::create_thread(this, thr_type, stack_sz);来实现线程的创建,对于这个方法不同平台有不同的实现,这里不再赘述,

上面都是创建过程,之后再调用
Thread::start(native_thread);在 JVM_StartThread 中调用,该方法的实现在Thread.cpp中

start方法中有一个函数调用:os::start_thread(thread);,调用平台启动线程的方法,最终会调用Thread.cpp文件中的JavaThread::run()方法
3.2 线程的终止
3.2.1 通过标记位来终止线程
正常我们线程内的东西都是循环执行的,那么我们实际需求中肯定也存在想在其他线程来停止当前线程的需要,这是后我们可以通过标记位来实现,所谓的标记为其实就是
volatile修饰的变量,着由它的可见性特性决定的,如下代码就是依据volatile来实现标记位停止线程
3.2.2 通过 stop 来终止线程
我们通过查看
Thread类或者JDK API可以看到关于线程的停止提供了stop(),supend(),resume()等方法,但是我们可以看到这些方法都被标记了@Deprecated也就是过时的,虽然这几个方法都可以用来停止一个正在运行的线程,但是这些方法都是不安全的,都已经被抛弃使用,所以在我们开发中我们要避免使用这些方法,关于这些方法为什么被抛弃以及导致的问题
JDK文档中较为详细的描述 《Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?》在其中有这样的描述:

总的来说就是:
3.2.3 通过 interrupt 来终止线程
通过上面阐述,我们知道了使用
stop方法是不推荐的,那么我们用什么来更好的停止线程,这里就引出了interrupt方法,我们通过调用interrupt来中断线程当其他线程通过调用当前线程的
interrupt方法,表示向当前线程打个招呼,告诉他可以中断线程的执行了,至于什么时候中断,取决于当前线程自己线程通过检查自身是否被中断来进行相应,可以通过
isInterrupted()来判断是否被中断。
我们来看下面代码:

上述代码中我们可以看到,我们创建了
interrupt-1线程,其中用interrupt来判断当前线程是否处于中断状态,如果处于中断状态那么就自然结束线程,这里的结束的具体操作由我们开发者来决定。再创建interrupt-2线程,代码相对简单不阐述,当执行到某时刻时将线程interrupt-1设置为中断状态,也就是通知interrupt-1线程。
线程中断标记复位 :
在上述
interrupt-1代码中如果加入sleep方法,那么我们会发现程序报出InterruptedException错误,同时,线程interrupt-1也不会停止,这里就是因为中断标记被复位了 ,下面我们来介绍一下关于中断标记复位相关的内容
在线程类中提供了** **
Thread.interrupted的静态方法,用来对线程中断标识的复位,在上面的代码中,我们可以做一个小改动,对interrupt-1线程创建的代码修改如下:
上述代码中 我们可以看到,判断当前线程是否处于中断标记为
true, 如果有其他程序通知则为true此时进入if语句中,对其进行复位操作,之后再次判断。执行代码后我们发现interrupt-1线程不会终止,而会一直执行
Thread.interrupted进行线程中断标记复位是一种主动的操作行为,其实还有一种被动的复位场景,那就是上面说的当程序出现InterruptedException异常时,则会将当前线程的中断标记状态复位,在抛出异常前,JVM会将中断标记isInterrupted设置为false
在程序中,线程中断复位的存在实际就是当前线程对外界中断通知信号的一种响应,但是具体响应的内容有当前线程决定,线程不会立马停止,具体是否停止等都是由当前线程自己来决定,也就是开发者。
3.3 线程终止 interrupt 的原理
首先我们先来看一下在
Thread中关于interrupt的定义:
上面代码中我们可以看到,在
interrupt方法中最终调用了Native方法interrupt0,这里相关在线程启动时说过,不再赘述,我们直接找到hotspot中jvm.cpp文件中JVM_Interrupt方法

JVM_Interrupt方法比较简单,其中我们可以看到直接调用了Thread.cpp的interrupt方法,我们进入其中查看

我们可以看到这里直接调用了
os::interrupt(thread)这里是调用了平台的方法,对于不同的平台实现是不同的,我们这里如下所示,选择Linux下的实现os_linux.cpp中,

在上面代码中我们可以看到,在
1处拿到OSThread,之后判断如果interrupt为false则在2处调用OSThread的set_interrupted方法进行设置,我们可以进入看一下其实现,发现在osThread.hpp中定义了一个成员变量volatile jint _interrupted;而set_interrupted方法其实就是将_interrupted设置为true,之后再通过ParkEvent的unpark()方法来唤醒线程。具体的过程在上面进行的简单的注释介绍,











评论