深入理解 Java Thread 构造函数
1、线程的默认命名
打开 JDK 的源码可以看到我们构造 Thread 的时候,默认的线程的名字是
- 以
Thread-
开头,从0
开始计数; - 即
Thread-0、Thread-1、Thread-2...
;
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
/* For autonumbering anonymous threads. */
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
修改线程的名字,在线程启动之前,还有一个而已修改线程名字的机会,一旦线程启动,名字就不可以修改:
下面是在 Thread 中修改名字的代码:
public final synchronized void setName(String name) {
checkAccess();
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
if (threadStatus != 0) {
setNativeName(name);
}
}
2、线程的父子关系
Thread 的所有构造函数,最终都会去调用一个静态方法 init
,我们截取片段代码对其进行分析,不难发现新创建的任何一个线程都会有一个父线程:
这里截取 Thread
的 init
的部分代码:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();//获取当前运行的线程作为赴现场
SecurityManager security = System.getSecurityManager();
this.group = g;// 设置线程组
this.daemon = parent.isDaemon();// 当前线程是否为守护线程,取决于父线程
this.priority = parent.getPriority(); // 设置优先级
setPriority(priority);
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
上面代码中的 currentThread()
是获取当前线程,在线程生命周期中,我们说过线程的最初状态为 NEW,没有执行 start 方法之前,它只能算是一个 Thread 的实例,并不意味着一个新的线程被创建,因此 currentThread()
代表的将会是创建它的那个线程,因此我们可以得出以下结论。
- 一个线程的创建肯定是由另一个线程完成的。
- 被创建线程的父线程是创建它的线程。
我们都知道 main 函数所在的线程是由 JVM 创建的,也就是 main 线程,那就意味着我们前面创建的所有线程,其父线程都是 main 线程。
3、Thread 和 ThreadGroup
在 Thread 的构造函数中,可以显示的指定线程的 Group,也就是 ThreadGroup,下面看 init
方法的中间部分:
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
源码的意思: 如果在构造 Thread 的时候没有显示的指定一个 ThreadGroup,那么子线程将会被加入父线程所在的线程组。
简单测试代码:
public class Code_04_ThreadGroupTest {
public static void main(String[] args){
PrintStream out = System.out;
Thread t1 = new Thread("t1");// 没有给 t1 指定 group
ThreadGroup group1 = new ThreadGroup("group1");
Thread t2 = new Thread(group1, "t2");
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
out.println("Main Thread Group : " + mainGroup.getName());
out.println(t1.getThreadGroup() == mainGroup); // true // 默认就是 main 线程的
out.println(t2.getThreadGroup() == mainGroup); // false
out.println(group1 == t2.getThreadGroup()); // true // 指定了就是这个了
}
}
输出:
Main Thread Group : main
true
false
true
得出结论:
- main 线程所在的
ThreadGroup
称为main
; - 构造一个线程的时候如果没有显示的指定
ThreadGroup
,那么它将会和父线程属于同一个ThreadGroup
(且拥有同样的优先级);
4、Thread 和 JVM 虚拟机栈
1)、Thread 与 Stacksize
看下列 Thread
构造函数
Thread(ThreadGroup group, Runnable target, String name, long stackSize)
//分配新的 Thread 对象,以便将 target 作为其运行对象,将指定的 name 作为其名称,作为 group 所引用的线程组的一员,并具有指定的堆栈大小。
构造 Thread 的时候传入 stackSize
代表着线程占用的 stack
大小,如果没有指定 stackSize
的大小,默认是 0
, 0
代表着会忽略该参数,改参数会被 JNI 函数( Native
) 去使用。
2)、JVM 内存结构
详见 JVM
相关知识。可看我的[另一篇博客](../../Java 基础/JVM/JVM 总结(一) - 内存区域与内存管理.md )。
3)、Thread 与虚拟机栈
虚拟机栈的大小大概是可以存放 21456 个栈桢(栈桢中存放局部变量表、操作数栈、动态链接...),而自己创建的线程的虚拟机栈只有大概 15534 个栈桢,但是我们可以在创建线程的时候指定 stackSize
;
5、守护线程
基本性质:
- 在正常的情况下,如果 JVM 没有一个非守护线程,JVM 的进程才会退出。(当只有
Daemon
线程运行的时候才会退出); setDaemon()
方法必须在start()
方法之前调用(否则会抛出IllegalThreadException
);
线程是否为守护线程和它的父线程有很大的关系,如果父线程是正常线程,则子线程也是正常线程,反之亦然,如果你想要修改它的特性则可以借助 setDaemon
方法。 isDaemon()
方法可以判断该线程是不是守护线程。
另外需要注意的就是, setDaemon()
方法只在线程启动之前才能生效,如果一个线程已经死亡,那么再设置 setDaemon()
则会抛出 IllegalThreadStateException
异常。
看一个 daemonThread
的例子:
public class Code_05_DaemonThread {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while(true){
System.out.println("t running...");
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// t.setDaemon(true);
t.start();
Thread.sleep(2_000);
System.out.println("Main 线程结束!");
}
}
上面注释了 t.setDaemon(true);
运行结果如下,发现 main
线程结束了,但是里面的线程没有结束。
但是如果不注释 t.setDaemon(true);
,当 main
线程结束,里面的线程就会结束。
守护线程的作用
如果一个 JVM 进程中没有一个非守护线程,那么 JVM 会退出,也就是说守护线程具备自动结束生命周期的特性,而非守护线程则不具备这个特点, 试想一下如果 JVM 进程的垃圾回收线程是非守护线程,如果 main 线程完成了工作,则 JVM 无法退出,因为垃圾回收线程还在正常的工作。再比如有一个简单的游戏程序,其中有一个线程正在与服务器不断地交互以获取玩家最新的金币、武器信息,若希望 在退出游戏客户端的时候,这些数据同步的工作也能够立即结束,等等。
守护线程经常用作与执行一些后台任务,因此有时它也被称为后台线程,当你希望关闭某些线程的时候,或者退出 JVM 进程的时候,一些线程能够自动关闭,此时就可以考虑用守护线程( setDaemon()
) 为你完成这样的工作。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Java 线程介绍
下一篇: 彻底找到 Tomcat 启动速度慢的元凶
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论