美文网首页
Java线程<第一篇>:Thread生命周期和构造方法

Java线程<第一篇>:Thread生命周期和构造方法

作者: NoBugException | 来源:发表于2022-04-11 22:15 被阅读0次

    (1)线程的生命周期

    new 状态(即,创建对象状态):可以使用关键字 new 创建一个 Thread 对象 ,调用 start 方法之后可以从 new 状态进入到 runnable 状态;
    
    runnable(即,可执行状态):调用 start 之后,线程进入可执行状态,它的执行时机由 CPU 决定;
              线程进入runnable的可能有:
             (1)刚刚执行 start 方法,CPU 还没来得及调度;
             (2)CPU的调度器轮询使线程放弃执行;
             (3)线程主动调用 yield 方法,放弃 CPU 的执行权;
             (4)sleep休眠期结束、其它耗时操作结束、被线程主动唤醒(notify、notifyall)
             (5)线程突然在阻塞过程中被打断,比如调用了 interrupt 方法;
    
    running(即,执行状态):CPU 从任务可执行队列中选中了线程,此时线程才真正的被执行;
              执行状态的线程可能进入runnable、blocked、terminated状态;
    
    blocked:线程被阻塞的原因有:
             (1)线程调用了sleep方法;
             (2)线程调用了wait方法;
             (3)执行IO操作,比如因网络数据的读写而进入阻塞状态;
             (4)获取锁资源,从而加入到阻塞队列中,从而进入阻塞状态;
    
    terminated:线程停止的原因有:
             (1)线程调用了stop方法,stop方法已经被弃用,默认不推荐使用;
             (2)线程意外终止(JVM Crash);
             (3)线程正常执行完成;
    

    (2)线程的命名

    如果没有主动给线程设置名称,那么线程会有默认的名称,比如:

    Thread-0
    Thread-1
    Thread-2
    Thread-3
    Thread-4
    

    没有默认线程名称的构造方法有:

    public Thread();
    public Thread(Runnable target);
    public Thread(ThreadGroup group, Runnable target);
    

    有默认线程名称的构造方法有:

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

    我们还可以在线程 start 之前修改线程的名称:

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
    
            }
        });
        thread.setName("ThreadName"); // 在 start 之前设置
        thread.start();
    

    如果在线程启动之后设置线程名称,则无效。

    (3)线程的父子关系

    Thread 构造方法调用了 init 方法,首先我们阅读以下 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();
        ....
    }
    

    其中, currentThread() 是获取当前正在执行的线程,注意,重点是:正在执行

    打一个比方,有这样一段代码:

    public static void main(String[] args) {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
    
            }
        });
        thread.start();
    }
    

    在main方法里面新建了 Thread 对象,并启动线程。在 new Thread 的时候, thread 并没有调用 start 方法,currentThread() 已经在 init 方法里面被执行,currentThread() 所指定的线程并不是 threadA ,因为thread 还没有被启动(执行)。

    其实,main 方法由 JVM 启动,启动了一个 main 线程(主线程),在 main 线程中又创建并启动了新的线程。main 线程是父线程,threadA 是子线程。
    因此,可以得出以下结论:

    (1)一个线程的创建肯定是由另一个线程完成的;
    (2)被创建线程的父线程是创建它的线程;
    

    (4)ThreadGroup

    ThreadGroup 通常作为 Thread 构造器的参数传递,线程的创建都会加入某一个线程组。

    (1)main 线程所在的 ThreadGroup 称为 main;
    (2)构造一个线程的时候,如果没有显示地指定 ThreadGrop,那么它还会和父线程同属一个 ThreadGroup。
    

    在默认设置中,除了子线程会和父线程同属一个 Group 之外,它还会和父线程拥有同样的优先级,同样的 daemon。

    (5)虚拟机栈

    每一个线程在创建的时候,JVM 都会为其创建对应的虚拟机栈,虚拟机栈的大小可以通过 -xss 来配置,方法的调用是栈帧被压入和弹出的过程。
    虚拟机栈内存是线程已有的,也就是说每一个线程都会占有指定的内存大小,可以大概计算以下 Java 内存的大小:

    当前内存 = 堆内存 + 线程数量 * 栈内存
    

    当然,这个公式只是粗略的计算,并不是专业的,只是为了方便理解。

    需要注意的是:内存是有上限的,我们不仅需要想办法排除堆内存浪费,还需要排除栈空间的浪费。尽量减少线程的数量是一个不错的方案。

    (6)守护线程

    Thread 可以将非守护线程设置为守护线程。

    thread.setDaemon(true);
    

    首先看下如下代码:

        public static void main(String[] args) {
    
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            Thread.sleep(1000);
                            System.out.println("111111111111111");
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            // thread.setDaemon(true);
            thread.start();
            try {
                Thread.sleep(2222);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    

    main 线程的生命时长超过2000毫秒;
    thread 线程生命无限长,不会结束,并且每秒打印一个字符串;
    也就是说,即使 父(main) 线程结束了,子(thread)线程也不会结束;

    但是,一旦将 thread 线程设置了守护线程(即,将thread.setDaemon(true);的注释放开),父与子就会捆绑在一起,当 main 线程生命周期结束的时候,thread 线程也会结束;

    [本章完]

    相关文章

      网友评论

          本文标题:Java线程<第一篇>:Thread生命周期和构造方法

          本文链接:https://www.haomeiwen.com/subject/hkbkdrtx.html