网站首页 > java教程 正文
1 线程的意义
操作系统支持多个应用程序同时执行,每个应用至少对应一个 进程 ,彼此之间的操作和数据不受干扰。当一个进程需要磁盘IO的时候,CPU就切换到另外的进程,提高了CPU利用率。
有了进程,为什么还要线程?因为进程的成本太高了。
启动新的进程必须分配独立的内存空间,建立数据表维护它的代码段、堆栈段和数据段,这是昂贵的多任务工作方式。如果两个进程之间需要通信,要采用管道通信、消息队列、共享内存等等方式。线程可以看作轻量化的进程,或者粒度更小的进程。线程之间使用相同的地址空间,切换线程的时间远远小于切换进程的时间。一个进程的开销大约是线程开销的30倍左右。
随着操作系统的发展,进程已经演变成了线程容器的角色。进程是资源分配的最小单位,线程是CPU调度的最小单位。每一个进程中至少有一个线程,同一进程的所有线程共享该进程的所有资源。
2 详解Java线程
我们以Java语言和JVM为例,了解一下线程的实现原理。
2.1 线程的底层实现
启动一个Java程序会创建一个JVM进程,JVM创建、管理线程本质都是调用操作系统接口。
public class TestThreadStart {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("start thread now ");
}, "TestThreadStart");
thread.run();
System.out.println("the state of thread is " + thread.getState().name());
thread.start();
System.out.println("the state of thread is " + thread.getState().name());
}
}
以上代码演示了使用start方法启动线程,run方法只是执行同步方法,输出结果如下:
start thread now
the state of thread is NEW
the state of thread is RUNNABLE
start thread now
JVM源码文件
https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/Thread.java 中,可以看到线程启动的start方法调用本地方法start0。
public void start() {
synchronized (this) {
// zero status corresponds to state "NEW".
if (holder.threadStatus != 0)
throw new IllegalThreadStateException();
start0();
}
}
private native void start0();
源码文件
https://github.com/openjdk/jdk/blob/master/src/java.base/share/native/libjava/Thread.c 中,实现了start0方法映射到JVM本地方法。
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"stop0", "(" OBJ ")V", (void *)&JVM_StopThread},
{"isAlive0", "()Z", (void *)&JVM_IsThreadAlive},
{"suspend0", "()V", (void *)&JVM_SuspendThread},
{"resume0", "()V", (void *)&JVM_ResumeThread},
{"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
{"yield0", "()V", (void *)&JVM_Yield},
{"sleep0", "(J)V", (void *)&JVM_Sleep},
{"currentCarrierThread", "()" THD, (void *)&JVM_CurrentCarrierThread},
{"currentThread", "()" THD, (void *)&JVM_CurrentThread},
{"setCurrentThread", "(" THD ")V", (void *)&JVM_SetCurrentThread},
{"interrupt0", "()V", (void *)&JVM_Interrupt},
{"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock},
{"getThreads", "()[" THD, (void *)&JVM_GetAllThreads},
{"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads},
{"getStackTrace0", "()" OBJ, (void *)&JVM_GetStackTrace},
{"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName},
{"extentLocalCache", "()[" OBJ, (void *)&JVM_ExtentLocalCache},
{"setExtentLocalCache", "([" OBJ ")V",(void *)&JVM_SetExtentLocalCache},
{"getNextThreadIdOffset", "()J", (void *)&JVM_GetNextThreadIdOffset}
};
在源码文件
https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/thread.cpp 可以看到启动线程依赖系统级方法 os::start_thread(thread) 。
void Thread::start(Thread* thread) {
// Start is different from resume in that its safety is guaranteed by context or
// being called from a Java method synchronized on the Thread object.
if (thread->is_Java_thread()) {
// Initialize the thread state to RUNNABLE before starting this thread.
// Can not set it after the thread started because we do not know the
// exact thread state at that time. It could be in MONITOR_WAIT or
// in SLEEPING or some other state.
java_lang_Thread::set_thread_status(JavaThread::cast(thread)->threadObj(),
JavaThreadStatus::RUNNABLE);
}
os::start_thread(thread);
}
在源码文件
https://github.com/openjdk/jdk/blob/master/src/hotspot/share/runtime/os.cpp 找到 os::start_thread 方法,可以看到系统创建了线程,并且状态设置为RUNNABLE。
void os::start_thread(Thread* thread) {
OSThread* osthread = thread->osthread();
osthread->set_state(RUNNABLE);
pd_start_thread(thread);
}
Linux系统并没有把线程和进程区别对待,无论线程还是进程都是一个数据结构,用 task_struct 结构体表示,唯一的区别是共享的数据区域不同。
struct task_struct {
// 进程状态
long state;
// 虚拟内存结构体
struct mm_struct *mm;
// 唯一进程号
pid_t pid;
// 指向父进程的指针
struct task_struct *parent;
// 子进程列表
struct list_head children;
// 存放文件系统信息的指针
struct fs_struct *fs;
// 进程/线程打开的文件指针
struct files_struct *files;
};
以上代码是 task_struct 的极少部分字段。mm_struct是进程的虚拟内存空间,files_struct是进程将要读写的文件。Linux系统将一切外设和磁盘文件都当做文件处理,files_struct代表所有的IO操作。
从上图可以看到,Linux创建进程和子进程会申请不同的内存空间,读写不同的文件;创建进程和进程下的线程,共享了内存空间,读写一样的文件。因此多线程应用程序要利用锁机制,避免在同一区域写入错乱数据的问题。
2.2 线程的生命周期
操作系统的线程生命周期可以分为五种状态。分别是:初始状态、可运行状态、运行状态、休眠状态和终止状态。JVM将线程等待状态细分成两种,一共六种状态。
- NEW:创建。
- RUNNABLE:运行中。
- BLOCKED:受阻塞并等待某个监视器锁。
- WAITING:无限期地等待。
- TIMED_WAITING:等待指定时间。
- TERMINATED:终止。
2.3 线程的优先级
操作系统调度线程有两种方式:
- 协作式调度:当前线程完全占用CPU时间,执行时间由线程本身控制,直到运行结束,系统才执行下一个线程。可能出现一个线程一直占有CPU,而其他线程等待,导致整个系统崩溃。
- 抢占式调度:操作系统决定下一个占用CPU时间的是哪一个线程,定期的中断当前正在执行的线程,任何一个线程都不能独占。不会因为一个线程而影响整个进程的执行,但是频繁阻塞和调度会造成系统资源的浪费。
JVM的线程调度默认是抢占式调度,线程调度器按照优先级决定调度哪个线程来执行。线程优先级的范围是1~10,默认的优先级是5,10极最高。线程优先级高的不一定先执行,优先级低只是获得调度的概率低,并不是一定最后被调度。通过 setPriority() 可以改变线程优先级。
2.4 JVM守护线程
守护线程是一种JVM中特殊的线程,在后台完成一些系统性的服务,比如垃圾回收。应用程序创建的线程叫做用户线程,完成具体的业务操作。程序中所有的用户线程执行完毕之后,不管守护线程是否结束,JVM都会自动结束。任何线程都可以通过 setDaemon() 设置为守护线程和用户线程,如下代码所示:
public class DaemonThreadDemo {
public static void main(String[] args) {
System.out.println("--主线程开始--");
Thread thread = new Thread(() -> {
while (true) {
System.out.println("执行守护线程");
}
});
thread.setDaemon(true);
thread.start();
System.out.println("--主线程结束--");
}
}
程序运行结果:
--主线程开始--
--主线程结束--
执行守护线程
执行守护线程
执行守护线程
执行守护线程
执行守护线程
Process finished with exit code 0
当一个应用程序需要在后台持续做某件事情,就是守护线程的典型应用场景。比如开发一款社交软件,开启守护线程持续监听聊天消息。当应用程序退出时,守护线程一定会终止。
- 上一篇: 什么!还不懂Java多线程编程?快来看看阿里资深架构师的熬夜总结
- 下一篇: Java多线程超详解
猜你喜欢
- 2025-05-30 线程池的使用及ThreadPoolExecutor源码分析
- 2025-05-30 面试官:什么是虚拟线程?为什么要有虚拟线程?
- 2025-05-30 「超级详细」Java线程实现原理
- 2025-05-30 并发编程之ThreadPoolExecutor线程池原理解析
- 2025-05-30 阿里资深架构推荐学习四本实战书籍:MySQL+Redis+Kfaka+多线程
- 2025-05-30 杰哥教你面试之一百问系列:java多线程
- 2025-05-30 面试突击29:说一下线程池7个参数的含义?
- 2025-05-30 一个 tomcat 项目使用多个线程池还是一个线程池 ?
- 2025-05-30 一个注解 —— 完美实现分布式锁
- 2025-05-30 Java 线程的生命周期及各阶段状态
你 发表评论:
欢迎- 06-04C++优先级调度队列(Priority Queue)
- 06-04数据结构与算法-优先队列(优先队列 数组实现)
- 06-04什么是优先队列?(优先队列原理)
- 06-04终于有架构大牛把分布式系统概念讲明白了,竟然用了足足800页
- 06-04分布式事物如何保证接口请求顺序性?
- 06-04微服务下分布式事务模式的详细对比
- 06-04彻底掌握分布式事务2PC、3PC模型(分布式事务 三阶段)
- 06-04分布式事务最全详解(看这篇就够了)
- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)