二、JAVA多线程:深入理解Thread构造函数(Thread、Runnable、守护线程、ThreadGroup)

本章主要介绍了所有与Thread有关的构造函数,线程的父子关系(并非继承关系,而是一种包含关系),Thread和ThreadGroup之间的关系,Thread与虚拟机栈的关系(学习这部分内容需要读者有JVM的相关基础,尤其是对栈内存要有深入的理解),最后还介绍了守护线程的概念、特点和使用场景。

线程命名

构造函数:

Constructor and Description
Thread()

Allocates a new Thread object.

Thread(Runnable target)

Allocates a new Thread object.

Thread(Runnable target, String name)

Allocates a new Thread object.

Thread(String name)

Allocates a new Thread object.

Thread(ThreadGroup group, Runnable target)

Allocates a new Thread object.

Thread(ThreadGroup group, Runnable target, String name)

Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group.

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

Allocates a new Thread object so that it has target as its run object, has the specified name as its name, and belongs to the thread group referred to by group, and has the specified stack size.

Thread(ThreadGroup group, String name)

Allocates a new Thread object.

扫描二维码关注公众号,回复: 4856505 查看本文章

线程在启动之后,名称不允许修改:

/**
 * Changes the name of this thread to be equal to the argument
 * <code>name</code>.
 * <p>
 * First the <code>checkAccess</code> method of this thread is called
 * with no arguments. This may result in throwing a
 * <code>SecurityException</code>.
 *
 * @param      name   the new name for this thread.
 * @exception  SecurityException  if the current thread cannot modify this
 *               thread.
 * @see        #getName
 * @see        #checkAccess()
 */
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);
    }
}

线程的父子关系

Thread类所有的构造函数都会调用init方法,都会调用init方法,如下代码

/**
 * Allocates a new {@code Thread} object. This constructor has the same
 * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread}
 * {@code (null, null, gname)}, where {@code gname} is a newly generated
 * name. Automatically generated names are of the form
 * {@code "Thread-"+}<i>n</i>, where <i>n</i> is an integer.
 */
public Thread() {
    init(null, null, "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
 */
public Thread(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 (null, target, name)}.
 *
 * @param  target
 *         the object whose {@code run} method is invoked when this thread
 *         is started. If {@code null}, this thread's run method is invoked.
 *
 * @param  name
 *         the name of the new thread
 */
public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}

查看init方法

/**
 * Initializes a Thread.
 *
 * @param g the Thread group
 * @param target the object whose run() method gets called
 * @param name the name of the new Thread
 * @param stackSize the desired stack size for the new thread, or
 *        zero to indicate that this parameter is to be ignored.
 * @param acc the AccessControlContext to inherit, or
 *            AccessController.getContext() if null
 * @param inheritThreadLocals if {@code true}, inherit initial values for
 *            inheritable thread-locals from the constructing thread
 */
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();

    ..................


}

任何一个线程都有一个父线程

currentThread 代表的是创建它的那个线程,我们创建的线程都是main函数创建的,意味着我们所有的创建的线程的父线程都是main线程

结论:

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

          被创建线程的父线程就是创建他的线程

Thread和ThreadGroup

在线程初始化的时候(init方法)可以指定ThreadGroup

/**
 * Initializes a Thread.
 *
 * @param g the Thread group
 * @param target the object whose run() method gets called
 * @param name the name of the new Thread
 * @param stackSize the desired stack size for the new thread, or
 *        zero to indicate that this parameter is to be ignored.
 * @param acc the AccessControlContext to inherit, or
 *            AccessController.getContext() if null
 * @param inheritThreadLocals if {@code true}, inherit initial values for
 *            inheritable thread-locals from the constructing thread
 */
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    ...................................

    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();
}

测试代码:

结论:

main线程所在的ThreadGroup成为main

构造一个线程的时候,如果没有显示的指明ThreadGroup,那么她和所在的父线程同处一个ThreadGroup,并且具有同样的优先级

Thread和Runnable

Thread: 负责线程本身相关的职责和控制

Runnable: 逻辑执行单元的部分

Thread和JVM虚拟机栈

在Thread的构造函数中有一个stackSize , 代码如下:

/*
 * The requested stack size for this thread, or 0 if the creator did
 * not specify a stack size.  It is up to the VM to do whatever it
 * likes with this number; some VMs will ignore it.
 */
private long stackSize;

Thread和stackSize

一般情况下,创建线程的时候,不会手动指定栈内存的地址空间字节数组,统一通过xss参数设置即可

stacksize 越大代表着 线程内方法调用递归的深度就越深

stacksize 越小代表着 创建线程数量就越多(依赖平台)

对平台的依赖测试:

         

JVM内存结构


 

Java堆(Heap,Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

方法区(Method Area,方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

程序计数器(Program Counter Register,程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。

JVM栈(JVM Stacks,与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

本地方法栈(Native Method Stacks,本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

 

 

 

太多了,就只是放了几张图。

Thread与虚拟机栈

虚拟机栈内存是线程私有的,也就是说每一个线程都会占有指定的内存大小。

可以粗略的理解一个JAVA进程的内存大小为: 堆内存+线程数量*栈内存

线程数量=(最大地址空间(MaxProcessMemory)-  JVM堆内存 - ReserverdOsMemory / ThreadStackSize(Xss))

ReserverdOsMemory: 系统保留内存,一般在136Mb左右

线程数量还与操作系统的一些内核配置有很大的关系,如Linux下

         /proc/sys/kernel/threads-max

         /proc/sys/kernel/pid-max

         /proc/sys/vm/max_map_count

守护线程

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

当你希望关闭某些线程,或者退出JVN进程的时候,一些线程能够自动关闭。

代码:

public class DamomThread {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread( ()-> {
            while (true){
                try {
                    Thread.sleep(1000);
                    System.out.println("内部线程:正在执行。。。。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }

        }  ) ;

        //开启守护进程
//        thread.setDaemon(true);
        thread.start();

        Thread.sleep(2_000L );


        System.out.println("Main thread finished lifecycle !!!!!!!!!!");

    }


}

该代码一共开启了两个线程,一个是main线程,另一个是里面执行的线程thread

未开始守护进程:    thread.setDaemon(false);

外面的线程main线程,结束退出,里面的线程,依旧继续执行,如截图

开始守护进程:    thread.setDaemon(true);

main线程退出,内部线程一起退出。

本文整理来源于:

《Java高并发编程详解:多线程与架构设计》 --汪文君

猜你喜欢

转载自blog.csdn.net/zhanglong_4444/article/details/85809081