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

2022-04-11  本文已影响0人  NoBugException

(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 线程也会结束;

[本章完]

上一篇 下一篇

猜你喜欢

热点阅读