1 从start一个线程说起
在 Java 中,Thread
类是用于创建和管理线程的核心类。通过调用 Thread
类的 start()
方法,可以启动一个新的线程,并执行线程的 run()
方法。下面我们来详细分析一下 start()
方法的实现。
1.1 代码示例
首先,我们来看一个简单的示例,如何通过 Thread
类启动一个线程:
public class ThreadBaseDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
// 线程执行的代码
System.out.println("Thread t1 is running");
}, "t1");
t1.start();
}
}
在这个示例中,我们创建了一个名为 t1
的线程,并通过 start()
方法启动它。线程启动后,会执行 run()
方法中的代码。
1.2 start()
方法的源码分析
接下来,我们来看看 start()
方法的源码:
public synchronized void start() {
// 检查线程状态,如果线程已经被启动过,则抛出异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 将线程添加到当前线程的线程组中
group.add(this);
boolean started = false;
try {
// 调用 native 方法 start0() 启动线程
start0();
started = true;
} finally {
try {
if (!started) {
// 如果线程启动失败,从线程组中移除
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
// 忽略异常
}
}
}
1.2.1 线程状态检查
start()
方法首先检查 threadStatus
的值。threadStatus
是一个内部状态变量,用于表示线程的状态。如果 threadStatus
不等于 0,说明线程已经被启动过或已经结束,此时再次调用 start()
方法会抛出 IllegalThreadStateException
异常。
1.2.2 线程组管理
group.add(this)
将当前线程添加到当前线程的线程组中。线程组是一种管理线程的机制,可以将多个线程组织在一起进行统一管理。
1.2.3 启动线程
start0()
是一个 native 方法,由 C++ 实现。这个方法负责实际的线程启动工作。start0()
方法会创建一个新的操作系统线程,并调用线程的 run()
方法。
1.2.4 异常处理
在 finally
块中,如果线程启动失败(即 started
为 false
),则会将线程从线程组中移除。如果移除过程中发生异常,则忽略该异常。
1.3 start0()
方法
start0()
方法是一个 native 方法,它的实现是由 C++ 编写的。这个方法负责与操作系统的线程管理机制进行交互,创建一个新的操作系统线程,并将其与 Java 线程关联起来。
private native void start0();
start0()
方法的具体实现依赖于底层的操作系统,不同的操作系统可能有不同的实现方式。例如,在 Linux 系统上,start0()
可能会调用 pthread_create
函数来创建一个新的线程。
1.4 小结
通过 Thread
类的 start()
方法,我们可以启动一个新的线程。start()
方法首先检查线程的状态,然后将线程添加到线程组中,最后调用 start0()
方法启动线程。start0()
是一个 native 方法,由 C++ 实现,负责实际的线程创建和启动工作。
2 Java多线程相关概念
-
1把锁
- synchronized(后面细讲)
-
2个并
-
并发:是在同一实体上的多个事件,是在一台处理器上“同时"处理多个任务,同一时刻,其实是只有一个事件在发生
-
并行:是在不同实体上的多个事件,是在多台处理器上同时处理多个任务,同一时刻,大家真的都在做事情,你做你的,我做我的,但是我们都在做
-
-
3个程
-
进程: 简单的说,在系统中运行的一个应用程序就是一个进程,每一个进程都有它自己的内存空间和系统资源
-
线程:也被称为轻量级进程,在同一个进程内会有1个或多个线程,是大多数操作系统进行时序调度的基本单元
-
管程:Monitor(监视器),也就是我们平时所说的锁。Monitor其实是一种同步机制,它的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码,JVM中同步是基于进入和退出监视器(Monitor管程对象)来实现的,每个对象实例都会有一个Monitor对象,Monitor对象和Java对象一同创建并销毁,底层由C++语言实现
-
3 用户线程和守护线程
在 Java 中,线程分为用户线程(User Thread)和守护线程(Daemon Thread)。
3.1 用户线程(User Thread)
用户线程是系统的工作线程,它们负责完成程序需要完成的业务操作。默认情况下,创建的线程都是用户线程。用户线程的生命周期通常与程序的生命周期一致,即只要用户线程还在运行,程序就不会退出。
3.2 守护线程(Daemon Thread)
守护线程是一种特殊的线程,它们为其他线程提供服务,通常在后台默默地完成一些系统性的任务,比如垃圾回收线程。守护线程的生命周期与用户线程不同,如果所有的用户线程都结束了,守护线程也会随着 JVM 一同结束工作。换句话说,如果只剩下守护线程,JVM 会自动退出。
3.3 线程的 daemon
属性
每个线程都有一个 daemon
属性,用于判断该线程是用户线程还是守护线程。如果 daemon
属性为 true
,则该线程为守护线程;如果为 false
,则该线程为用户线程。
public final boolean isDaemon() {
return daemon;
}
3.4 代码演示
下面是一个简单的代码示例,演示如何创建和使用守护线程:
public class DaemonDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 开始运行, " +
(Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
while (true){
}
}, "t1");
t1.setDaemon(true);//通过设置属性Daemon来设置当前线程是否为守护线程
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"\t ----end 主线程");
}
}
public class DaemonDemo {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 开始运行, " +
(Thread.currentThread().isDaemon() ? "守护线程" : "用户线程"));
while (true){
}
}, "t1");
t1.setDaemon(true);
t1.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"\t ----end 主线程");
}
}
- 运行结果
输出:t1 开始运行,守护线程
main 主线程结束
- 在main主线程结束后,守护线程会伴随着JVM一同结束工作,即使还有循环没有结束
3.5 小结
- 用户线程:负责完成程序的业务操作,只要用户线程还在运行,程序就不会退出。
- 守护线程:为其他线程提供服务,如果所有用户线程都结束了,守护线程会随着 JVM 一同结束工作。
setDaemon(true)
方法:必须在start()
方法之前调用,否则会抛出IllegalThreadStateException
异常。
4 思维导图
5 参考链接
【尚硅谷JUC并发编程(对标阿里P6-P7)】