1. 线程名称2. 线程的sleep操作3. interrupt操作4. join操作5. yield操作6. daemon操作
守护线程与用户线程要点 7. 线程状态总结
1. 线程名称线程的名称
- 线程名称一般在启动前设置,但也允许为运行的线程设置名称线程名称不能为null允许两个线程有相同的名称,但是应该避免这种情况如果没有为线程指定名称,系统会自动为线程设置名称,名称为Thread-编号的形式为线程指定名称的方法有:
- 通过构造方法通过setName()方法
接下来看一下部分源码:
6. 不指定名称的时候,构造方法会自动指定名称
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);
}
}
注意:
创建线程或线程池的时候,需要指定有意义的名称,方便出错的时候进行回溯,查找原因。
让本线程进行休眠,让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:Evenjdk-17.0.1bin>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的线程也会参与抢占
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虚拟机强制终止的风险,所以守护线程中尽量不要操作一些系统资源用户线程创建的线程依然是用户线程,守护线程创建的线程依然是守护线程
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状态



