文章目录
- 1. 线程名称
- 2. 线程的sleep操作
- 3. interrupt操作
- 4. join操作
- 5. yield操作
- 6. daemon操作
- 守护线程与用户线程
- 要点
- 7. 线程状态总结
1. 线程名称
线程的名称
- 线程名称一般在启动前设置,但也允许为运行的线程设置名称
- 线程名称不能为null
- 允许两个线程有相同的名称,但是应该避免这种情况
- 如果没有为线程指定名称,系统会自动为线程设置名称,名称为
Thread-编号的形式
- 为线程指定名称的方法有:
- 通过构造方法
- 通过setName()方法
接下来看一下部分源码:
6. 不指定名称的时候,构造方法会自动指定名称
/** 内部调用了四个参数的构造方法,
第一个:指定group
第二个:指定target
第三个:线程名称
第四个:指定stackSize
*/
public Thread() {
this(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
//获取下一个编号
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
- 可以设置线程名称的方法,设置前会对name进行null判断
// 其他设置name的构造方法都是通过这个构造方法设置的name属性
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 如果name是null,抛出异常
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
...
}
// set方法设置name
// 设置前会进行null判断,然后为线程设置name,如果线程正在运行,还会设置底层线程的name
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);
}
}
注意:
创建线程或线程池的时候,需要指定有意义的名称,方便出错的时候进行回溯,查找原因。
2. 线程的sleep操作
让本线程进行休眠,让CPU去执行其他的线程。
从线程状态来说,就是从RUNNABLE
变成了 TIMED_WAITING
可以使用jstack
工具查看线程的相关信息。
例如:执行如下代码:
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A线程");
thread.start();
// 等待异步线程启动
Thread.sleep(10);
System.out.println(thread.getState());
}
}
在程序运行过程中,可以使用命令查看线程的状态,当然,首先需要获取到对应的JVM进程的id。可以使用命令 jps
查看程序运行时候的id。
例如:
D:\Even\jdk-17.0.1\bin>jps
9648 Jps
10468 Test
8484 Launcher
10232
根据结果可以看到,Test的进程id是 10468
然后根据进程id使用jstack
查看线程的信息
jstack是jdk自带的一个工具
省略部分信息后输出如下:
"A线程" #15 prio=5 os_prio=0 cpu=0.00ms elapsed=14.07s tid=0x00000280ffd46000 nid=0x2420 waiting on condition [0x00000081808ff000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(java.base@17.0.1/Native Method)
at com.wang.thread.Test.lambda$main$0(Test.java:7)
at com.wang.thread.Test$$Lambda$14/0x0000000800c01200.run(Unknown Source)
at java.lang.Thread.run(java.base@17.0.1/Thread.java:833)
可以看到线程的状态为:TIMED_WAITING (sleeping)
3. interrupt操作
为什么不推荐使用线程的stop()方法?
因为 stop() 方法会将线程强制结束,不管线程现在是什么状态。
但是如果线程正持有某把锁,就可能导致锁无法释放。在操作数据库就可能导致数据库状态不一致等情况。所以不推荐使用stop() 方法
interrupt()
方法只是将线程标记成为了中断状态,并不会停止线程的运行。
当调用interrupt()
方法的时候,有两个作用:
- 如果线程处于阻塞状态,调用这个方法会退出阻塞,并且抛出
InterruptedException
异常 - 如果线程处于运行中,则会继续运行,仅仅是中断标记被置为true,程序可以使用
isInterrupted()
方法检查自己的中断标记是否为true,以此判断自己是否被中断,并执行推出操作。
线程睡眠时被打断代码:
public class TestInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
Thread.sleep(10000);
System.out.println("异步线程执行完成!");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
Thread.sleep(100);
thread.interrupt();
System.out.println("主线程执行完毕");
}
}
运行结果:
主线程执行完毕
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at com.wang.thread.TestInterrupt.lambda$main$0(TestInterrupt.java:7)
at java.base/java.lang.Thread.run(Thread.java:833)
从运行结果中可以看出,线程在阻塞时被打断之后,阻塞之后的代码不会被执行,异步线程会被结束。
若是正常执行的代码,不会被强行结束,测试代码:
public class TestInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
System.out.println(i);
}
});
thread.start();
Thread.sleep(10);
// 线程正常运行时,不会被中断,不受影响
thread.interrupt();
System.out.println("主线程执行完毕");
}
}
运行结果是:程序会将等待所有的结果输出之后再结束。
程序中使用isInterrupted()
可以检查线程是否被中断了,如果被中断执行退出操作。
例如:
Thread thread = new Thread(() -> {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
System.out.println(i);
// 检查线程是否被中断,若被中断,则执行退出操作
if (Thread.currentThread().isInterrupted()) {
System.out.println("异步线程被中断,将会退出");
return;
}
}
});
当线程被中断的时候,将会执行if()
中的操作,执行退出操作
4. join操作
假设当前线程叫做“A线程”,A线程对B线程执行join操作。也就是在A线程的代码块中调用b.join()。可以理解成:A线程执行完 b.join() 之后就会进入 WAITING
状态,等待B线程执行完毕之后,A线程才会恢复成RUNNABLE
状态,然后继续执行。
也可以使用 join(final long millis)
或 join(long millis, int nanos)
指定等待时间,如果B线程在等待时间内没有执行完成,A线程会在等待时间到达之后,继续向下执行。
可以查看下join的部分源码,可以发现join操作其实是通过wait方法实现等待的。
例如:
// join是一个同步方法
public final synchronized void join(final long millis)
throws InterruptedException {
if (millis > 0) {
if (isAlive()) {
final long startTime = System.nanoTime();
long delay = millis;
do {
// 调用wait方法进行等待
wait(delay);
} while (isAlive() && (delay = millis -
TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
}
} else if (millis == 0) {
while (isAlive()) {
// 调用wait方法进行等待
wait(0);
}
} else {
throw new IllegalArgumentException("timeout value is negative");
}
}
所以使用join
方法导致的阻塞,在阻塞期间被interrupt()
也会抛出InterruptedException
异常。
5. yield操作
yield操作是让当前线程让出CPU时间片的执行权,让操作系统重新调度一次线程。
yield操作并不会让当前线程进入阻塞状态,而是会进入就绪状态,在Thread的状态中,仍然是RUNNABLE
状态。
总结如下:
- yield操作并不会阻塞线程
- yield操作并不能保证当前线程立即让出时间片,切换到就绪状态
- yield操作完成之后,线程会对CPU时间片再次进行抢占,刚yield的线程也会参与抢占
6. daemon操作
Java中的线程分为两种:用户线程 和 守护线程
在Thread类中,使用属性 daemon
标记是否是守护线程
// 是否是守护线程,默认不是守护线程
// 不过在创建线程时,会根据当前线程的daemon状态设置本线程的状态. 可以在构造方法中看到
private boolean daemon = false;
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// 省略了部分代码
Thread parent = currentThread();
this.daemon = parent.isDaemon();
}
可以通过setDaemon(boolean on)
方法设置deamon的状态,使用isDaemon()
获取daemon的状态。
守护线程与用户线程
守护线程是在后台默默工作的,JVM进程关闭后,所有的守护线程都会被关闭。
用户线程是主要的线程,如果全部的用户线程都运行完毕了,JVM进程也会随之关闭。
JVM进程会等待用户线程的执行,只要还有一个用户线程没有执行完毕,理论上JVM就不会关闭。JVM关闭的时候,所有的守护线程都会被关闭,不管守护线程是否运行结束。
要点
- 线程是否是守护线程,需要在线程启动前进行设置,否则会抛出
InterruptedException
- 守护线程存在被JVM虚拟机强制终止的风险,所以守护线程中尽量不要操作一些系统资源
- 用户线程创建的线程依然是用户线程,守护线程创建的线程依然是守护线程
7. 线程状态总结
- NEW:线程被创建出来了,但是还没有执行
start()
- RUNNABLE:正在运行或可以运行的时候
- 调用
start()
方法 -
sleep()
结束后 -
join()
结束后 - 等待用户输入结束后
- 抢到对象锁之后
- BLOCKED:等待获取锁或IO阻塞的时候成为
BLOCKED
状态 - WAITING:无限时的等待的时候,成为
WAITING
状态
-
Object.wait()
方法,使用Object.notify()
或Object.notifyAll()
唤醒 -
Thread.join()
方法,被合并的线程执行完毕之后唤醒 -
LockSupport.park()
方法,使用LockSupport.unpark(Thread)
唤醒
- TIMED_WAITING:限时等待的时候成为
TIMED_WAITING
状态
-
Thread.sleep(time)
方法,睡眠时间结束后唤醒 -
Object.wait(time)
方法,使用Object.notify()
或Object.notifyAll()
主动唤醒,或等待时间到达指定的等待时间 -
LockSupport.parkNanos(time)
或LockSupport.parkUntil(time)
方法,使用LockSupport.unpark(Thread)
方法唤醒,或到线程停止时限结束
- TERMINATED:线程任务执行结束或出现异常停止,变成
TERMINATED
状态