上篇分享中主要是对线程的基本概念和基本操作做了一个分享,同时提出了两种常用的创建多线程的方法,当然在后期的分享中也会提及到更多的创建线程的方式,到后期的分享的时候再说。
这次主要是深入的理解一下Thread的构造函数,通过构造函数对于Thread有一个更加深入的了解。

这里首先提供一个JDK1.6的ThreadAPI截图

线程命名规范

  从源码分析可以看到在Thread类中默认提供了线程的命名方式,这个名方式跟上篇文章中的查看的结果是一样的效果。通过Thread-数字的组合来为线程命名,而这个命名也会在JVM自动增加。

public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);
}
 private static synchronized int nextThreadNum() {return threadInitNumber++;}

  当然除了通过默认的命名方式Thread还提供了一个构造函数,但是这几个构造函数统统都实现了一个方法,作为线程初始化的方法,该方法给我们提供了几个参数

  • 第一个参数表示线程组
  • 第二个参数表示线程的Runable接口实现类
  • 第三个参数表示线程名称
  • 第四个参数表示栈大小
  • 第五个参数表示上下文 用于基于它所封装的上下文作出系统资源访问决定。
  • 第六个参数表示如果为true, 则从构造线程继承的线程局部变量,继承的初始值;
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();//如果线程组为空if (g == null) {//判断一线安全管理器if (security != null) {//获取系统线程组g = security.getThreadGroup();}//如果安全管理器,继续为空,也就说没有添加到系统组里面就添加到父类线程组中if (g == null) {g = parent.getThreadGroup();}}//检查安全性g.checkAccess();//如果安全管理器不为空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);//继承构造函数的初始变量,这里使用到了一个ThreadLocal在以后会遇到if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);//指定的堆栈大小this.stackSize = stackSize;//获取到线程命名的ID号tid = nextThreadID();}

  可以根据上面的注释理解一下线程初始化方法的执行的流程。当然,既然有既然线程的名称是作为一个成员变量来实现的,所以线程应该有对应的独立的设置线程名称的方法。

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);}}
private native void setNativeName(String name);
    /* Java thread status for tools,* initialized to indicate thread 'not yet started'*/private volatile int threadStatus = 0;

  通过上面源码的分析,可以得到一个结论,就是说当我们线程没有启动的时候我们是可以调用底层的方法对线程进行命名操作。但是如果线程是启动的话就不可以进行这个操作。

线程之间的父子关系

  在之前的分析中,我们知道所有的线程构造函数最后都调用了一个私有的方法init()当然在这个方法中也实现了很多的功能,在这个方法中看到最多的字眼就是parent,对于每个线程来说都有一个属于自己的父类线程。这里我们拿JDK1.8的源码来作为分析的。在init方法中有如下的方法

public static native Thread currentThread();

这个方法是用来获取当前线程,在线程的生命周期中,线程的最初的状态是new这个状态,在没有被调用start方法的时候,只能是算作是Java中的普通的实例。并没有真正的调用start0方法进行底层的调用。所以说currentThread方法得到的就是通过调用start0方法才能实现的那个线程。
  也就是说一个线程必须由另外的一个线程来创建完成。对于其他所有的线程最终都是由main线程来创建的。而此时调用currentThread方法的时候获取到的就是main线程。

线程与线程组

  在init函数中有一段源码,这段源码看上去很难理解,但是仔细分析看来并不过是需要给当前线程找到一个合适的父线程

   SecurityManager security = System.getSecurityManager();if (g == null) {/* Determine if it's an applet or not *//* If there is a security manager, ask the security managerwhat to do. */if (security != null) {g = security.getThreadGroup();}/* If the security doesn't have a strong opinion of the matteruse the parent thread group. */if (g == null) {g = parent.getThreadGroup();}}

测试代码

public class ThreadConstruction {public static void main(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("主线程所属的线程组是:"+mainThreadGroup.getName());System.out.println("T1 线程和 主线程 是否属于同一组 "+(mainThreadGroup==t1.getThreadGroup()));System.out.println("T2 线程和 主线程 是否属于同一组 "+(mainThreadGroup==t2.getThreadGroup()));System.out.println("T2 线程和  group 是否属于同一组 "+(group==t2.getThreadGroup()));}
}

测试结果

主线程所属的线程组是:main
T1 线程和 主线程 是否属于同一组 true
T2 线程和 主线程 是否属于同一组 false
T2 线程和  group 是否属于同一组 tru

  就如注释中解释的那样,当我们创建一个线程,但是没有指定对应的线程组的时候,它会和父线程属于同一个线程组。

Thread的stackSize参数

  在我们分析init方法的时候提到了一个参数stackSize,在我们看到JDK1.6API的时候也有构造方法对这个参数进行了修改。这个参数到底代表什么意义呢,既然是栈那么就按照栈的性质理解即可。也就是说这个参数越大线程内方法调用的递归深度越深,参数越小就表明递归深度小,需要创建线程数量多。
  但是在很多的平台一些平台性能不好的情况下这个参数并不会起到什么样的作用,而是通过-XSS参数进行设置。

public Thread(ThreadGroup group,Runnable target,String name,long stackSize)参数:
group - 线程组。
target - 其 run 方法被调用的对象。
name - 新线程的名称。
stackSize - 新线程的预期堆栈大小,为零时表示忽略该参数。
抛出:
SecurityException - 如果当前线程无法在指定的线程组中创建线程。
从以下版本开始:
1.4 

线程内存与堆内存分代


对于这个分代操作在后面讲到垃圾回收机制的时候有更为详细的介绍。
在JDK1.8之后持久代被彻底的删除,取代它的被称为是元空间,这里就有一个问题了,什么是持久代呢?在Java虚拟机规范中将堆内存中的一个逻辑分区称为是持久代,这个持久代如果站在内存回收的角度上讲就是方法区。在方法区中的实例在执行垃圾回收的时候是永远不会被回收的。

这里我们可以使用一个jstat命令来查看
JDK1.7

JDK1.8

在这里我们比较两个命令执行的效果会发现有一个东西发生了变化,在中间部分的地方JDK1.7中是使用P表示,而JDK1.8 中使用的是M表示,而JDK1.8中的元空间同样是作为堆内存的一部分,JVM为每个类加载器分配一块内存类表,进行线性分配,块的大小决定于类加载器的类型,反射/代理/对应的类加载块会小一些,而其他的类加载会多一些。现在的GC操作是:如果发现某个类加载器具备回收的条件就把整个类加载器相关的元空间全部进行回收。节省了GC的效率。

线程与虚拟机栈

  JVM虚拟机的内存分布在之前的博客中已经详细分享介绍了。在多线程中影响线程多少的因素直接取决于JVM的虚拟机栈。

通过上面的表格可以看到,线程创建数量随着虚拟机栈内存的增多而减少。在之前的博客中有一个比较经典的图,也就说虚拟机栈内存是线程私有的。也就说每个线程都会占用指定的内存大小。堆内存+线程数量*栈内存这个就表示Java中的一个进程的内存大小。
如果按照这个样子来算的话,如果存在栈内存越来越大的话,在总内存数量不变的情况下线程数量只能越来越少。

线程数=(最大地址空间-JVM堆内存-ReservedOsMemory)/XSS(线程栈大小)

守护线程

在分析init方法的时候

  this.daemon = parent.isDaemon();

这里有一个这样的操作,是否是守护线程。那么什么是守护线程呢?

public class DaemonThread {public static void main(String[] args) throws InterruptedException {Thread thread = new Thread(()->{while (true){try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}});//thread.setDaemon(true);thread.start();Thread.sleep(2000L);System.out.println("主线程结束 thread");}
}

其实这个代码我们分析一下就可以知道,当我们的主线程结束生命周期的时候,程序并不会停止下来,那么为什么会出现这种情况呢?在JVM如果没有一个非守护线程,JVM就会结束。从这个角度上看守护线程的一个作用就是具备自我结束生命周期的作用。那么什么是守护线程呢?又回到了我们最初的问题上。

守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止

总结

这次主要说了一下线程的构造、线程与线程组、父线程与子线程、线程栈、以及守护线程等。主要比较关注的几个点就是比较基础的线程栈,这个也可能会与JVM虚拟机的优化结合到一起。分析了如何使用守护线程。

上一篇[https://blog.csdn.net/nihui123/article/details/89609060]

Java高并发编程详解系列-深入理解Thread构造相关推荐

  1. Java高并发编程详解系列-Java线程入门

    根据自己学的知识加上从各个网站上收集的资料分享一下关于java高并发编程的知识点.对于代码示例会以Maven工程的形式分享到个人的GitHub上面.   首先介绍一下这个系列的东西是什么,这个系列自己 ...

  2. Java高并发编程详解系列-7种单例模式

    引言 在之前的文章中从技术以及源代码的层面上分析了关于Java高并发的解决方式.这篇博客主要介绍关于单例设计模式.关于单例设计模式大家应该不会陌生,作为GoF23中设计模式中最为基础的设计模式,实现起 ...

  3. Java高并发编程详解系列-线程上下文设计模式及ThreadLocal详解

    导语   在之前的分享中提到过一个概念就是线程之间的通信,都知道在线程之间的通信是一件很消耗资源的事情.但是又不得不去做的一件事情.为了保证多线程线程安全就必须进行线程之间的通信,保证每个线程获取到的 ...

  4. Java高并发编程详解系列-Future设计模式

    导语   假设,在一个使用场景中有一个任务需要执行比较长的时间,通常需要等待任务执行结束之后或者是中途出错之后才能返回结果.在这个期间调用者只能等待,对于这个结果Future设计模式提供了一种凭据式的 ...

  5. Java高并发编程详解系列-类加载

    之前在写关于JVM的时候提到过类加载机制,类加载机制也是在Java面试中被经常问道的一个问题,在这篇博客中就来了解一下关于类加载的知识. 类加载   在JVM执行Java程序的时候实际上执行的编译好的 ...

  6. Java高并发编程详解系列-线程安全数据同步

    在多线程中最为复杂和最为重要的就是线程安全.多个线程访问同一个对象的时候会导致线程安全问题.通过加锁可以避免这种问题.但是在串行执行的过程中又不用考虑线程安全问题,而使用串行程序效率低没有办法将CPU ...

  7. Java高并发编程详解系列-不可变对象设计模式

    导语   在开发中涉及到的所有关于多线程的问题都离不开共享资源的存在.那么什么是共享资源,共享资源就是被多个线程共同访问的数据资源,而且每个线程都会引起它的变化.伴随共享资源而生的新问题就是线程安全, ...

  8. Java高并发编程详解系列-线程上下文类加载

    前面的分享中提到的最多的概念就是关于类加载器的概念,但是当我们查看Thread源码的时候会发现如下的两个方法,这两个方法就是获取或者设置线程的上下文类加载器的方法,那么为什么要设置这两个方法呢?这个就 ...

  9. Java高并发编程详解系列-线程通信

      进程间的通信,又被称为是进程内部的通信,我们都知道每个进程中有多个线程在执行,多个线程要互斥的访问共享资源的时候会发送对应的等待信号或者是唤醒线程执行等信号.那么这些信号背后还有什么样的技术支持呢 ...

最新文章

  1. 计算机动画火柴人作业,(Flash期末作品综合实验报告.doc
  2. Storm-kafka源码分析之Config相关类
  3. 【错误记录】Visual Studio 中编译 NDK 报错 ( no matching function for call to ‘cacheflush‘ cacheflush(); )
  4. 第四章:Spring AOP
  5. rsync的安装使用01
  6. SAP Cloud for Customer Extensibility的设计与实现
  7. Linux Shell 常用命令与目录分区的学习总结
  8. k52zip shell band 流氓软件总是显示~
  9. python图片识别是否p过_Python+Opencv进行识别相似图片
  10. 【codevs1380】没有上司的舞会
  11. C/C++语言编程修养
  12. excel的mysql语言_Excel的数据库语句
  13. linux基础命令入门到精通
  14. C#学习(二十八)——ManualResetEvent的理解和使用
  15. 之前安装过Multisim14,再次安装失败的解决方法
  16. 企业内网通讯软件有哪些优势?
  17. C++厘米和英寸的换算
  18. Flink 实现Locality 模式调度
  19. 当GUSD遇上STO,全球投资格局与资金流向生变? | 链塔智库
  20. 工信部教育与考试中心-软件测试工程师考试题A卷-答

热门文章

  1. nginx 调用dll_使用DLL中的资源
  2. centos7下载elasticsearch7版本(超详细)
  3. shell的建立与执行实验报告_实验七 Shell脚本运行的优化
  4. 怎么样提高自己的口才
  5. 性能测试过程中oracle数据库报ORA-27301 ORA-27302错
  6. linux_ls命令详解
  7. js-jquery-插件开发(一)
  8. datapump跨平台升级迁移的总结
  9. Android 解析JSON
  10. ECMAScript基础(三)-关键字