线程的常用方法
本文介绍了Java多线程编程中的几个关键方法:1)join()方法实现线程"插队",控制执行顺序,有阻塞主线程、释放锁等特点;2)interrupt()方法通过中断标志实现线程中断,需配合中断状态检测;3)yield()方法让出CPU执行权,但不保证切换;4)守护线程(setDaemon(true))在用户线程结束后自动终止。文章对比了这些方法与sleep()、wait()的区
1.线程的插队:join()方法
1.1.作用
join() 是多线程编程中的一个重要方法,用于控制线程执行顺序。并不影响同一时刻处于运行状态的其他线程。
package Day02_Thread的方法;
//插队线程:插队对象。join()
public class Demo01 {
public static void main(String[] args) {
System.out.println("main线程开始执行...");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (char c = 'A'; c < 'Z'; c++) {
System.out.printf("[%s]->%s\n",Thread.currentThread().getName(),c);
}
}
});
t1.setName("线程A");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (char c = 'a'; c < 'z'; c++) {
System.out.printf("[%s]->%s\n",Thread.currentThread().getName(),c);
}
}
});
t2.setName("线程B");
t1.start();
t2.start();
//线程插队
try {
t1.join();//t1线程插队到main线程之前(t1线程执行结束后,才会继续执行main线程)
t2.join();//t2线程插队到main线程之前(t2线程执行结束后,才会继续执行main线程)
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("main线程结束...");
}
}
执行顺序:
1.主线程启动子线程t1,t2
2.主线程调用t1和t2的join()方法后,主线程进入阻塞状态,要等t1,t2分别执行完再继续进行主线程。
PS:谁调用join()方法,谁被阻塞!上示例中主线程调用,则是主线程被t1和t2插队
3.主线程执行输出
1.2重载形式
1.join():无限等待,知道目标线程终止
2..join(long millis):等待指定毫秒数,若是超时则不等待。
public final synchronized void join(long millis)
3. join(long millis,int nanos):精确到纳秒的等待(实际精度取决于OS(操作系统))
public final synchronized void join(long millis, int nanos)
1.3 join()和sleep()的区别
两个方法都可以实现类似"线程等待”的效果,但是仍然存在区别;
join()执行过程中,会释放当前线程持有的锁;sleep()休眠没有结束前,不会释放锁;
join()是通过在内部使用 wait/notify 机制来实现;sleep()通过操作系统的定时器( Timer )实现精确休眠,到期后线程重新进入就绪状态;
join()主要用于实现线程任务编排;sleep()用于模拟耗时操作;
2.线程的中断:interrupt()方法
如果线程需要执行一个长时间任务,就可能需要能中断线程。中断线程就是其他线程给该线程发一个信号,该线程收到信号后,结束执行run()方法使当前线程能立刻结束。
中断一个线程非常简单,只需要在其他线程中对目标线程调用 interrupt()方法,目标线程需要反复检测自身状态是否是 interrupted 状态,如果是,就立刻结束运行。
2.1作用
interrupt()方法的作用是设置该线程的中断状态为 true ,线程是死亡还是等待新的任务或是继续运行至下一步,就取决于中断状态。线程会不时地检测这个中断状态值,以判断线程是否应该被中断(中断状态值是否为 true )。
2.2实现原理
情况1:线程目前执行的方法或状态,支持中断,实现InterrupteException的方法或者状态
package Day02_Thread的方法;
import java.util.UUID;
public class Demo02 {
public static void main(String[] args) {
Thread thread=new Thread(){
public void run(){
System.out.println("子线程开始执行,进入Runable状态");
//情况1:线程目前执行的方法或状态,支持中断
//实现InterrupteException的方法或者状态
try {
Thread.sleep(1000*6);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("子线程被中断");
return;
}
//子线程启动
thread.start();
//main主线程休眠3秒
try {
Thread.sleep(1000*3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//主线程中,子线程thread中断,设置中断标志为true
thread.interrupt();
}
}
2.情况2:在线程逻辑中,判断中断标志(是否已经设置为中断true)
package Day02_Thread的方法;
import java.util.UUID;
public class Demo02 {
public static void main(String[] args) {
Thread thread=new Thread(){
public void run(){
System.out.println("子线程开始执行,进入Runable状态");
//情况1:线程目前执行的方法或状态,支持中断
//实现InterrupteException的方法或者状态
// try {
// Thread.sleep(1000*6);
// } catch (InterruptedException e) {
// e.printStackTrace();
// System.out.println("子线程被中断");
// return;
// }
//情况2:在线程逻辑中,判断中断标志(是否已经设置为中断true)
while(!isInterrupted()){
System.out.println(UUID.randomUUID().toString());
}
System.out.println("子线程结束执行(通过中断结束),进入TERMINATED状态");
}
};
//子线程启动
thread.start();
//main主线程休眠3秒
try {
Thread.sleep(1000*3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//主线程中,子线程thread中断,设置中断标志为true
thread.interrupt();
}
}
总结:
interrupt()方法只是改变中断状态,不会像 stop()中断一个正在运行的线程。
支持线程中断的方法(Thread.sleep()、join()、wait()等方法)在执行过程中,会监视线程的中断状态,一旦发现线程的中断状态值被置为“tru,就会抛出线程中断异常InterruptedException,给WAITING或者 TIMEpD WAITING 等待状态的线程发出一个中断信号,从而让当前线程识别到中断标识,就会退出 WAITING或者TIMED WAITING 等待状态;
3.线程的让出:yield()方法
线程通过调用yield()方法告诉JVM的线程调度,当前线程愿意让出CPU给其他线程使用
3.1yield()方法的作用()
线程通过调用 yield()方法告诉 JVM 的线程调度,当前线程愿意让出CPU 给其他线程使用。
至于系统是否采纳,取决于 JVM 的线程调度模型:分时调度模型和抢占式调度模型
分时调度模型:所有的线程轮流获得cpu 的使用权,并且平均分配每个线程占用的 CPU 时间片;
抢占式调度模型:优先让可运行池中优先级高的线程占用CPU,如果可运
行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。(JVM 虚拟机采用的是抢占式调度模型)
package 线程的方法;
//线程的让出:线程通过调用yield()方法告诉JVM的线程调度,当前线程愿意让出CPU给其他线程使用
public class Demo05 {
public static void main(String[] args) {
//创建普通的用户线程
Thread t1=new Thread("线程"){
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(getName()+":"+i);
}
}
};
//创建守护线程
Thread t2=new Thread("线程2"){
@Override
public void run() {
for (char i = 'A'; i < 'Z'; i++) {
System.out.println(getName()+":"+i);
Thread.yield();
}
}
};
t1.start();
t2.start();
}
}
注意事项:
-
yield()只是一个提示,不是强制命令 -
不能保证其他线程一定会获得执行机会
3.2 yield(), sleep(), join() 和 wait()的对比
| 特性 | yield() | sleep() | join() | wait() |
|---|---|---|---|---|
| 所属类 | Thread |
Thread |
Thread |
Object |
| 作用对象 | 当前线程 | 当前线程 | 其他线程 | 当前线程 |
| 是否释放锁 | ❌ 不释放 | ❌ 不释放 | ✅ 释放(底层调用wait()) |
✅ 释放 |
| 唤醒条件 | 立刻进入就绪状态 | 时间结束或被中断 | 目标线程结束或超时或被中断 | notify()/notifyAll()或被中断 |
| 是否静态方法 | ✅ 是 | ✅ 是 | ❌ 否 | ❌ 否 |
| 参数要求 | 无参数 | 必须指定时间(毫秒/纳秒) | 可指定超时时间(可选) | 可指定超时时间(可选) |
| 主要用途 | 让出CPU给同级/更高优先级线程 | 延迟执行 | 等待其他线程完成 | 线程间通信(需配合synchronized) |
| 是否抛出异常 | ❌ 不抛异常 | ✅ 抛出InterruptedException |
✅ 抛出InterruptedException |
✅ 抛出InterruptedException |
| 示例代码 | Thread.yield(); |
Thread.sleep(1000); |
thread.join(500); |
obj.wait(1000); |
| 适用场景 | 长时间循环中避免独占CPU | 定时任务、模拟耗时操作 | 线程结果依赖或顺序控制 | 生产者-消费者模型等协作场景 |
4.守护线程,俗称:舔狗线程(Daemon Thread)
4.1用户线程和守护线程的区别
用户线程:我们平常创建的普通线程;
守护线程:用来服务于用户线程的线程,在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出;而守护线程执行结束后,虚拟机不会自动退出。
4.2守护线程的设置
在调用start()方法之前,调用setDaemon(true)方法把该方法标记为守护线程

更多推荐



所有评论(0)