(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 线程也会结束;
[本章完]
网友评论