文章目录
1 线程的定义
1.1 线程是什么
线程是操作系统能够运算调度的最小单位。它被包含在进程中,是进程中的实际运行单位,一个进程中包含一个或多个线程,多个线程共享进程中的资源,线程之间的运行互不影响。
1.2 线程和进程的区别
线程与进程的区别可以归纳为以下4点:
1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
3)调度和切换:线程上下文切换比进程上下文切换要快得多。
4)在多线程OS中,线程不是一个可执行的实体。
2 线程的生命周期
2.1 线程的生命周期有哪些
PS:本文中主要讲述的是基于java中线程的状态
Java中的Thread状态是通过一个名为threadStatus的变量进行存储的。
private volatile int threadStatus = 0;
Thread中有一个枚举,枚举中定义线程所有的状态
public enum State {
// 初始状态,还没调用start方法
NEW,
// 运行状态,调用start方法后的状态
RUNNABLE,
// 阻塞状态
BLOCKED,
/**
* 没有时间的等待,执行下面这些方法后,线程就转换为WAITING状态
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*/
WAITING,
/**
* 有时间的等待,执行下面这些方法后,线程状态就转换为TIME_WAITING状态
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* 终止状态,当线程的run方法执行完后,就会转换为终止状态
*/
TERMINATED;
}
2.2 线程的生命周期是如何转换的
线程的生命周期进行转换的具体情况:
现在我们来用代码实现线程状态转换:
public class ThreadStatusDemo {
public static void main(String[] args) {
// 1. Test Thread Time_Waiting_Thread
new Thread(() -> {
while (true){
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}, "Time_Waiting_Thread").start();
// 2. Test Thread Waiting_Thread
new Thread(() -> {
while (true){
synchronized (ThreadStatusDemo.class){
try {
ThreadStatusDemo.class.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}, "Waiting_Thread").start();
// 3. Test Thread Blocked
new Thread(new BlockDemo(), "Blocked01_Thread").start();
new Thread(new BlockDemo(), "Blocked02_Thread").start();
}
static class BlockDemo extends Thread{
@Override
public void run() {
synchronized (BlockDemo.class){
while (true){
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
}
将程序程序后,程序会处于阻塞状态。通过以下步骤来查看各个线程的状态
- 运行ThreadStatusDemo程序
- cmd中执行JPS命令后,查看名为ThreadStatusDemo的进程,记录进程号
- cmd中执行 jstack 进程号,可以看到每个线程目前的状态 ,如图所示
名为Time_Waiting_Thread的线程执行sleep方法后,线程状态是TIME_WAITING;
名为Waiting_Thread的线程执行没有时间参数wait方法后,线程状态是WAITING;
名为Blocked01_Thread的线程成功获取到锁后,调用sleep方法,线程状态是TIME_WAITING;
名为Blocked02_Thread的线程没有获取到锁,线程状态是BLOCKED;
3 经典问题
3.1 线程的启动为什么是start方法
run方法中一般是我们线程具体要执行的业务代码,使用线程的目的就是通过创建新的线程异步执行run方法,并发执行,从而提高程序运行效率。如果直接调用run方法,相当于调用一个普通的方法,没有创建新的线程。我们来分析下Thread中start方法具体做了些什么:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
start方法中调用start0方法,而start0方法是native方法,无法看到具体实现的。可以通过查看hotspot源码查看具体流程。
首先,我们需要下载hotspot源码;Hotspot源码地址
其次,定位start0方法在hotspot中具体的位置。
从上图中可以看到start0方法对应的位置,在jvm.cpp文件找到JVM_StartThread方法。
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
该方法中有一些关键的实现
3.1.1 创建java线程
JavaThread构造中是通过操作系统来创建线程的
3.1.2 启动该java线程
同样的也是通过操作系统来进行线程的启动的
首先创建一个java线程,然后调用start方法启动该java线程。
3.2 线程的优雅终止
3.2.1 stop方法终止线程
Thread中定义了一个stop方法,调用该方法后,可以直接终止线程。但是在jdk中是不推荐使用的,这种方式相当于在linux中执行kill -9命令,直接停止线程,对程序极不友好。就像当你在吃饭的时候,别人突然打断你一样。
3.2.2 如何优雅的终止线程
3.2.2.1 方式一
下面展示一个Demo如何优雅的终止线程:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (Thread.currentThread().isInterrupted()) { // 默认的中断标志是false
i++;
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt(); // 将线程的中断标志设置为true
}
每个线程都有一个boolean类型中断标志,默认该中断标志是false。当调用线程的interrupt方法后,该中断标志会被修改为true。从上面的Demo中我们可以实现线程的优雅关闭,这种方式是利用线程本身自带的中断标志进行终止线程的。
3.2.2.2 方式二
同上,也可以自定义一个变量,在某个时刻修改该变量的值,让线程终止,道理和方式一是一样的。