程序中执行的线程。JVM允许应用程序拥有多个并发运行的执行线程。
每个线程都有一个优先级。优先级高的线程优先于优先级低的线程执行。每个线程可能被标记为守护线程,也可能不被标记为守护线程。
当在某个线程中运行的代码创建一个新 Thread 对象时,新线程的优先级最初设置为创建线程的优先级,并且只有在创建线程是一个守护线程时,新线程才是守护线程。
当JVM启动时,通常有一个非守护的线程(它通常调用某个指定类的main方法)。JVM 继续执行线程,直到发生以下任何一种情况时停止:
每个线程都有名字,多个线程可能具有相同的名字,Thread 有的构造器如果没有指定名字,会自动生成一个名字。
源码中一共枚举了六种线程状态
NEW
表示线程创建成功,但还没有运行,在 new Thread
后,没有 start
前,线程的状态都是 NEW
;start()
,进入RUNNABLE
,当前线程sleep()结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入RUNNABLE
RUNNABLE
变成 TERMINATED
RUNNABLE
转至 BLOCKED
WAITING
和 TIMED_WAITING
类似,都表示在遇到 Object#wait、Thread#join、LockSupport#park 这些方法时,线程就会等待另一个线程执行完特定的动作之后,才能结束等待,只不过 TIMED_WAITING
是带有等待时间的优先级代表线程执行的机会的大小,优先级高的可能先执行,低的可能后执行,在 Java 源码中,优先级从低到高分别是 1 到 10,线程默认 new 出来的优先级都是 5,源码如下:
分别为最低,普通(默认优先级),最大优先级
创建的线程默认都是非守护线程。
无返回值的线程主要有两种初始化方式:
看下 start 方法的源码:
public synchronized void start() {
/**
* 对于由VM创建/设置的主方法线程或“系统”组线程,不调用此方法。
* 将来添加到此方法中的任何新功能可能也必须添加到VM中。
*
* 零状态值对应于状态“NEW”。
* 因此,如果没有初始化,直接抛异常
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/*
* 通知组此线程即将start,以便可以将其添加到组的线程列表中
* 并且可以减少组的unstarted线程的计数
*/
group.add(this);
// started 是个标识符,在处理一系列相关操作时,经常这么设计
// 操作执行前前标识符是 false,执行完成后变成 true
boolean started = false;
try {
// 创建一个新的线程,执行完成后,新的线程已经在运行了,即 target 的内容已在运行
start0();
// 这里执行的还是 main 线程
started = true;
} finally {
try {
// 若失败,将线程从线程组中移除
if (!started) {
group.threadStartFailed(this);
}
// Throwable 可以捕捉一些 Exception 捕捉不到的异常,比如子线程抛出的异常
} catch (Throwable ignore) {
/*
* 什么也不做。
* 如果start0抛出一个Throwable,那么它将被传递到调用堆栈
*/
}
}
}
// 开启新线程使用的是 native 方法
private native void start0();
注意上面提到的的threadStatus
变量
用于工具的Java线程状态,初始化以指示线程“尚未启动”
这是实现 Runnable 的接口,并作为 Thread 构造器的入参,调用时我们使用了两种方式,可以根据实际情况择一而终
我们来看下 run 方法的源码:
线程初始化的源码有点长,我们只看比较重要的代码 (不重要的被我删掉了),如下:
// 无参构造器,线程名字自动生成
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
// g 代表线程组,线程组可以对组内的线程进行批量的操作,比如批量的打断 interrupt
// target 是我们要运行的对象
// name 我们可以自己传,如果不传默认是 "Thread-" + nextThreadNum(),nextThreadNum 方法返回的是自增的数字
// stackSize 可以设置堆栈的大小
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name.toCharArray();
// 当前线程作为父线程
Thread parent = currentThread();
this.group = g;
// 子线程会继承父线程的守护属性
this.daemon = parent.isDaemon();
// 子线程继承父线程的优先级属性
this.priority = parent.getPriority();
// classLoader
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);
// 当父线程的 inheritableThreadLocals 的属性值不为空时
// 会把 inheritableThreadLocals 里面的值全部传递给子线程
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
/* Set thread ID */
// 线程 id 自增
tid = nextThreadID();
}
从初始化源码中可以看到,很多属性,子线程都是直接继承父线程的,包括优先性、守护线程、inheritableThreadLocals 里面的值等等。
当前线程等待另一个线程执行死亡之后,才能继续操作。
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
等待最多 millis 毫秒以使该线程消失。 0 超时时间意味着永远等待。
此实现使用以 this.isAlive 为条件的 this.wait 调用循环。当线程终止时,将调用this.notifyAll方法。 建议应用程序不要在线程实例上使用 wait,notify 或 notifyAll。
令当前线程做出让步,放弃当前 cpu,让 cpu 重新选择线程,避免线程长时占用 cpu。
在写 while 死循环时,预计短时间内 while 死循环可结束的话,可在其中使用 yield 方法,防止 cpu 一直被占用。
让步不是绝不执行,重新竞争时,cpu 也有可能还重新选中自己。
根据系统计时器和调度器的精度和准确性,使当前执行的线程休眠(暂时停止执行)指定的毫秒数。但是注意,休眠期间线程并不会失去任何监视器的所有权。
native 方法
表示当前线程会沉睡多久,沉睡时不会释放锁资源,所以沉睡时,其它线程是无法得到锁的。最终调用的其实还是单参数的 sleep 方法。
中断这个线程。
除非当前线程是中断自身(这是始终允许的),否则将调用此线程的 checkAccess 方法,这可能导致抛 SecurityException。
如果这个线程被 Object 类的 wait(), wait(long), or wait(long, int) 方法或者 Thread 类的 join(), join(long), join(long, int), sleep(long), or sleep(long, int) 调用而阻塞,线程进入 WAITING
或 TIMED_WAITING
状态,这时候打断这些线程,就会抛出 InterruptedException ,使线程的状态直接到 TERMINATED
;
如果这个线程在一个InterruptibleChannel的I/O操作中被阻塞,主动打断当前线程,那么这个通道将被关闭,线程的中断状态将被设置,线程将收到一个ClosedByInterruptException。
如果这个线程在 Selector 中被阻塞,那么这个线程的中断状态将被设置,并且它将从选择的操作立即返回,可能带有一个非零值,就像调用了选择器的 wakeup 方法一样。
如果前面的条件都不成立,那么这个线程的中断状态将被设置。
中断非活动的线程不会有任何影响。
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
测试当前线程是否已被中断。 通过此方法可以清除线程的中断状态。 换句话说,如果要连续两次调用此方法,则第二个调用将返回false(除非在第一个调用清除了其中断状态之后且在第二个调用对其进行检查之前,当前线程再次被中断)。
由于此方法返回false,因此将反映线程中断,因为该线程在中断时尚未处于活动状态而被忽略。
本文主要介绍了线程的一些常用概念、状态、初始化方式和操作,这些知识是工作及面试中必备的,也是后面理解高级并发编程的基础。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。