本文参考汪文君著:Java高并发编程详解。

1、线程的命名

在构造现成的时候可以为线程起一个名字。但是我们如果不给线程起名字,那线程会有一个怎样的命名呢?

这里我们看一下Thread的源代码:

publicThread(ThreadGroup group, Runnable target) {

init(group, target,"Thread-" + nextThreadNum(), 0);

}/**

* Allocates a new {@code Thread} object. This constructor has the same

* effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}

* {@code (null, null, name)}.

*

* @param name

* the name of the new thread*/

publicThread(String name) {

init(null, null, name, 0);

}/**

* Allocates a new {@code Thread} object. This constructor has the same

* effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}

* {@code (group, null, name)}.

*

* @param group

* the thread group. If {@code null} and there is a security

* manager, the group is determined by {@linkplain

* SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}.

* If there is not a security manager or {@code

* SecurityManager.getThreadGroup()} returns {@code null}, the group

* is set to the current thread's thread group.

*

* @param name

* the name of the new thread

*

* @throws SecurityException

* if the current thread cannot create a thread in the specified

* thread group*/

publicThread(ThreadGroup group, String name) {

init(group,null, name, 0);

}

如果没有为线程起名字,那么线程将会以“Thread-”作为前缀与一个自增数字进行组合,这个自增数字在整个JVM进程中将会不断自增:

如果我们执行以下代码:

import java.util.stream.IntStream;public classTest {public static voidmain(String[] args) {

IntStream.range(0,5).boxed()

.map(

i->newThread(

()->System.out.println(

Thread.currentThread().getName()

)

)

).forEach(Thread::start);

}

}

这里使用无参的构造函数创建了5个线程,并且分别输出了各自的名字:

其实Thread同样提供了这样的构造函数。如下

Thread(Runnable target,String name);

Thread(String name);

Thread(ThreadGroup group,Runnable target,String name);

Thread(ThreadGroup group,Runnable target,String name,long stackSize);

Thread(ThreadGroup group,String name);

下面是实现代码:

import java.util.stream.IntStream;public classTest2 {private final static String PREFIX="ALEX-";public static voidmain(String[] args) {

IntStream.range(0,5).mapToObj(Test2::createTHREAD).forEach(Thread::start);

}private static Thread createTHREAD(final intintName) {return new Thread(()->System.out.println(Thread.currentThread().getName()),PREFIX+intName);

}

}

运行效果:

需要注意的是,不论你使用的是默认的命名还是特殊的名字,在线程启动之后还有一个机会可以对其进行修改,一旦线程启动,名字将不再被修改,下面是setName源码:

public final synchronized voidsetName(String name) {

checkAccess();if (name == null) {throw new NullPointerException("name cannot be null");

}this.name =name;if (threadStatus != 0) {

setNativeName(name);

}

}

2、线程的父子关系

Thread的所有构造函数,最终都会调用一个init,我们截取代码片段对其分析,不难发现新创建的任何一个线程都会有一个父线程:

private voidinit(ThreadGroup g, Runnable target, String name,longstackSize, AccessControlContext acc,

boolean inheritThreadLocals) {if (name == null) {throw new NullPointerException("name cannot be null");

}this.name =name;

Thread parent=currentThread();//在这里获取当前线程作为父线程

SecurityManager security=System.getSecurityManager();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();

}

}/*checkAccess regardless of whether or not threadgroup is

explicitly passed in.*/g.checkAccess();/** Do we have the required permissions?*/

if (security != null) {if(isCCLOverridden(getClass())) {

security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);

}

}

g.addUnstarted();this.group =g;this.daemon =parent.isDaemon();this.priority =parent.getPriority();if (security == null ||isCCLOverridden(parent.getClass()))this.contextClassLoader =parent.getContextClassLoader();else

this.contextClassLoader =parent.contextClassLoader;this.inheritedAccessControlContext =acc!= null ?acc : AccessController.getContext();this.target =target;

setPriority(priority);if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/*Stash the specified stack size in case the VM cares*/

this.stackSize =stackSize;/*Set thread ID*/tid=nextThreadID();

}

上面的代码中的currentThread()是获取当前线程,在线程的生命周期中,线程的最初状态为NEW,没有执行start方法之前,他只能算是一个Thread的实例,并不意味着一个新的线程被创建,因此currentThread()代表的将会是创建它的那个线程,因此我们可以得出以下结论:

一个线程的创建肯定是由另一个线程完成的

被创建线程的父线程是创建它的线程

我们都知道main函数所在的线程是由JVM创建的,也就是main线程,那就意味着我们前面创建的所有线程,其父线程都是main线程。

3、Thread与ThreadGroup

在Thread的构造函数中,可以显式地指定线程的Group,也就是ThreadGroup。

在Thread的源码中,我们截取片段。

SecurityManager security =System.getSecurityManager();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();

}

}

通过对源码的分析,我们不难看出,如果没指定一个线程组,那么子线程将会被加入到父线程所在的线程组,下面写一个简单的代码来测试一下:

package concurrent.chapter02;public classThreadConstruction {public static voidmain(String[] args) {

Thread t1= new Thread("t1");

ThreadGroup group= new ThreadGroup("TestGroup");

Thread t2= new Thread(group,"t2");

ThreadGroup mainThreadGroup=Thread.currentThread().getThreadGroup();

System.out.println("Main thread belong group:"+mainThreadGroup.getName());

System.out.println("t1 and main belong the same group:"+(mainThreadGroup==t1.getThreadGroup()));

System.out.println("t2 thread group not belong main group:"+(mainThreadGroup==t2.getThreadGroup()));

System.out.println("t2 thread group belong main TestGroup:"+(group==t2.getThreadGroup()));

}

}

运行结果如下所示:

通过上面的例子,我们不难分析出以下结论:

main 线程所在的ThreadGroup称为main

构造一个线程的时候如果没有显示地指定ThreadGroup,那么它将会和父线程拥有同样的优先级,同样的daemon。

在这里补充一下Thread和Runnable的关系。

Thread负责线程本身的职责和控制,而runnable负责逻辑执行单元的部分。

4、Thread与JVM虚拟机栈

stacksize

在Thread的构造函数中,可发现有一个特殊的参数,stackSize,这个参数的作用是什么呢?

一般情况下,创建线程的时候不会手动指定栈内存的地址空间字节数组,统一通过xss参数进行设置即可,一般来说stacksize越大,代表正在线程内方法调用递归的深度就越深,stacksize越小代表着创建的线程数量越多,当然这个参数对平台的依赖性比较高,比如不同的操作系统,不同的硬件。

在有些平台下,越高的stack设定,可以允许的递归深度就越多;反之,越少的stack设定,递归深度越浅。

JVM内存结构

虽然stacksize在构造时无需手动指定,但是我们会发现线程和栈内存的关系非常密切,想要了解他们之间到底有什么必然联系,就需要了解JVM的内存分布机制。

JVM在执行Java程序的时候会把对应的物理内存划分成不同的内存区域,每一个区域都存放着不同的数据,也有不同的创建与销毁时机,有些分区会在JVM启动的时候就创建,有些则是在运行时才会创建,比如虚拟机栈,根据虚拟机规范,JVM内存结构如图所示。

1、程序计数器

无论任何语言,其实最终都说需要由操作系统通过控制总线向CPU发送机器指令,Java也不例外,程序计数器在JVM中所起的作用就是用于存放当前线程接下来将要执行的字节码指令、分支、循环、跳转、异常处理等信息。在任何时候,一个处理器只执行其中一个线程的指令,为了能够在CPU时间片轮转切换上下文之后顺利回到正确的执行位置,每条线程都需要具有一个独立的程序计数器,各个线程互不影响,因此JVM将此块内存区域设计成了线程私有的。

2、Java虚拟机栈

这里需要重点介绍内存,因为与线程紧密关联,与程序计数器内存相类似,Java虚拟机栈也是线程私有的,他的生命周期与线程相同,是在JVM运行时所创建的,在线程中,方法在执行的时候都会创建一个名为stack frame的数据结构,主要用于存放局部变量表、操作栈、动态链接,方法出口等信息。

每一个线程在创建的时候,JVM都会认为其创建对应的虚拟机栈,虚拟机栈的大小可以通过-xss来配置,方法的调用是栈帧被压入和弹出的过程,同等的虚拟机栈如果局部变量表等占用内存越小,则可被压入的栈帧就会越多,反之则可被压入的栈帧就会越少,一般将栈帧内存的大小成为宽度,而栈帧的数量称为虚拟机栈的深度。

3、本地方法栈

Java中提供了调用本地方法的接口(java Native Interface),也就是可执行程序,在线程的执行过程中,经常会碰到调用JNI方法,JVM为本地方法所划分的内存区域便是本地方法栈,这块内存区域其自由度非常高,完全靠不同的JVM厂商来实现,Java虚拟机规范并未给出强制的规定,同样他也是线程私有的内存区域。

4、堆内存

堆内存是JVM中最大的一块内存区域,被所有线程所共享,Java在运行期间创造的所有对象几乎都放在该内存区域,该内存区域也是垃圾回收器重点照顾的区域,因此有时候堆内存被称为“GC堆”。堆内存一般会被细分为新生代和老年代,更细致的划分为Eden区,FromSurvivor区和To Survivor区。

5、方法区

方法区也是被多个线程所共享的内存区域,它主要用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,虽然在Java虚拟机规范中,将堆内存划分为对内存的一个逻辑分区,但是它还是经常被称作“非堆”,有时候也被称为“持久代”,主要是站在垃圾回收器的角度进行划分,但是这种叫法比较欠妥,在HotSpot JVM中,方法区还会被细划分为持久代和代码缓存区,代码缓存区主要用于存储编译后的本地代码(和硬件相关)以及JIT 编译器生成的代码,当然不同的JVM会有不同的实现。

6、Java 8 元空间

上述内容大致的介绍了JVM的内存划分,在JDK1.8版本以前的内存大致都是这样划分的,但是从JDK1.8来,JVM的内存区域发生了一些改变,实际上是持久代内存被彻底删除,取而代之的是元空间。

综上,虚拟机栈内存是线程私有的,也就是说每一个线程都会占有指定的内存大小,我们粗略的认为一个Java进程的内存大小为:堆内存+线程数量*栈内存。

不管是32位操作系统还是64位操作系统,一个进程最大内存是有限制的。简单来说 线程的数量和虚拟机栈的大小成反比。

5、守护线程

守护线程是一类比较特殊的线程,一般用于处理一些后台的工作,比如JDK的垃圾回收线程。

JVM在什么情况下会退出。

在正常情况下,JVM中若没有一个非守护线程,则JVM的进程会退出。

这和操作系统的线程概念如出一辙。

什么是守护线程?我们看下下面的代码:

public classDaemonThread {public static voidmain(String[] args) throws InterruptedException {

Thread thread= new Thread(()->{while(true) {try{

Thread.sleep(1);

}catch(Exception e) {

e.printStackTrace();

}

}

});

thread.start();

Thread.sleep(2_000L);

System.out.println("Main thread finished lifestyle");

}

}

执行这段代码之后,我们会发现,JVM永远不会结束。

package concurrent.chapter02;public classDaemonThread {public static voidmain(String[] args) throws InterruptedException {

Thread thread= new Thread(()->{while(true) {try{

Thread.sleep(1);

}catch(Exception e) {

e.printStackTrace();

}

}

});

thread.setDaemon(true);

thread.start();

Thread.sleep(2_000L);

System.out.println("Main thread finished lifestyle");

}

}

我们加了个thread.setDaemon(true)之后,程序就在main结束后正常推出了。

注意:

设置守护线程的方法很简单,调用setDaemon方法即可,true代表守护线程,false代表正常线程。

线程是否为守护线程和他的父线程有很大的关系,如果父线程是正常的线程,则子线程也是正常线程,反之亦然,如果你想要修改他的特性则可借助setDaemon方法。isDaemon方法可以判断该线程是不是守护线程。

另外要注意的是,setDaemon方法旨在线程启动之前才能生效,如果一个线程已经死亡,那么再设置setDaemon就会抛出IllegalThreadStateException异常。

守护线程的作用:

在了解了什么是守护线程以及如何创建守护线程之后,我们来讨论一下为什么要有守护线程,以及何时使用守护线程。

通过上面的分析,如果一个JVM进程中没有一个非守护线程,那么JVM就会退出,就是说守护线程具备自动结束生命周期的特性,而非守护线程则不具备这个特点,试想一下弱国JVM进程的垃圾回收线程是非守护线程,如果main线程完成了工作,则JVM无法退出,因为垃圾回收线程还在正常的工作。再比如有一个简单的游戏程序,其中有一个线程正在与服务器不断地交互以获得玩家最新的金币,武器信息,若希望在退出游戏客户端的时候,这些数据的同步工作也能够立即结束等等。

守护线程经常用作与执行一些后台任务,因此有时称他为后台线程,当你希望关闭某些线程的时候,这些数据同步的工作也能够立即结束,等等。

守护线程经常用作执行一些后台任务,因此有时它也被称为后台线程,当你希望关闭这些线程的时候,或者退出JVM进程的时候,一些线程能够自动关闭,此时就可以考虑用守护线程为你完成这样的工作。

总结:

学习了Thread的构造函数,能够理解线程与JVM内存模型的关系,还明白了什么是守护线程。

java 线程的构造函数_深入理解Thread构造函数相关推荐

  1. java 线程 基类_关于Linux 系统下 C++ 的多线程基类 Thread

    最近在Linux 下用C++做的东西,原来是使用"多进程"方式实现,现在随着工程的增大,"多进程"的"变量共享"问题已经十分突出了,虽然可以 ...

  2. java visualvm远程监控_深入理解JVM虚拟机12:JVM性能管理神器VisualVM介绍与实战

    本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutori ...

  3. java 线程一直运行状态_详解JAVA 线程-线程的状态有哪些?它是如何工作的?

    线程(Thread)是并发编程的基础,也是程序执行的最小单元,它依托进程而存在. 一个进程中可以包含多个线程,多线程可以共享一块内存空间和一组系统资源,因此线程之间的切换更加节省资源.更加轻量化,也因 ...

  4. java 线程组作用_浅析Java中线程组(ThreadGroup类)

    一.概念 Java中使用ThreadGroup类来代表线程组,表示一组线程的集合,可以对一批线程和线程组进行管理.可以把线程归属到某一个线程组中,线程组中可以有线程对象,也可以有线程组,组中还可以有线 ...

  5. java 线程 获取消息_获取java线程中信息

    怎样获取java线程中信息? 在进行多线程编程中,比较重要也是比较困难的一个操作就是如何获取线程中的信息.大多数人会采取比较常见的一种方法就是将线程中要返回的结果存储在一个字段中,然后再提供一个获取方 ...

  6. java线程池案例_使用Executors 和 ThreadPoolExecutor实现Java线程池案例

    并发主题 使用Executors 和 ThreadPoolExecutor实现Java线程池案例 首先需要一个工作线程: package com.journaldev.threadpool; publ ...

  7. java线程工作原型_深度解析Java内存的原型及工作原理

    本文主要通过分析Java内存分配的栈.堆以以及常量池详细的讲解了其的工作原理. 一.java虚拟机内存原型 寄存器:我们在程序中无法控制栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是 ...

  8. java 线程状态监控_干货:教你如何监控 Java 线程池运行状态

    之前写过一篇 Java 线程池的使用介绍文章<线程池全面解析>,全面介绍了什么是线程池.线程池核心类.线程池工作流程.线程池分类.拒绝策略.及如何提交与关闭线程池等. 但在实际开发过程中, ...

  9. java线程卡住排查_基于 Java 线程栈 排查问题

    除日志外,还有没有别的方式跟踪线上服务问题呢?或者,跟踪并排除日志里无法发现的问题? 方法当然是有的,就是通过现场快照定位并发现问题.我们所说的现场,主要指这两方面: Java 线程栈.线程栈是Jav ...

最新文章

  1. Ant Design 入门-引用自己命名的组件
  2. 给研发工程师的代码质量利器 | SOFAChannel#5 直播整理
  3. Zuul1.0和2.0我们该如何选择?
  4. CSS设置段落的水平对齐
  5. 一款非常好用的Linux下的C/C++ IDE
  6. 计算机网络 | 网络层 :IP协议详解
  7. 标准C函数库的使用方法
  8. 计算机体系结构--第一章1----体系结构的分类
  9. node:爬虫爬取网页图片
  10. 25 PP配置-生产车间控制-工序-定义生产计划参数文件
  11. 树莓派教程 - 1.1 树莓派GPIO库wiringPi 硬件PWM可调频率
  12. 微信语音麦克风静音_智能语音专题(二):语音信号处理
  13. 在线制作车牌效果图_在线快速生成,苹果设备在线样机
  14. 智慧园区弱电系统集成建设方案
  15. 各省份国内、入境旅游人数 (2007-2018年)
  16. Origin图选择性粘贴到word出现问题,提示‘word出现问题’解决方法
  17. 硬盘安装win10,笔者教你如何一步步从硬盘安装win10系统
  18. 360进攻搜狗后院!研发输入法 搜狗称无压力
  19. Linux发行版本及常用国产系统+系统优化
  20. 解锁AI技能:深度学习利用OCT图像诊断眼内视网膜疾病

热门文章

  1. 若依ruoyi——手把手教你制作自己的管理系统【二、修改样式】
  2. 街景地图工作是如何工作的
  3. 第四期:如何通过知晓云自动回复客服消息
  4. 后台配置税码(进项税或者销项税税码)
  5. CSS实现的带头像的彩色垂直菜单源码
  6. 日常一记(3)--禁止excel自动运行宏
  7. 离散数学学习笔记-01-随机试验与随机事件
  8. 京东炸年兽脚本--亲测有效 2021最新版
  9. 利用CSS设置文字的阴影效果
  10. 单目标跟踪CVPR 2018 ECO+