本文已参与「新人创作礼」活动,一起开启掘金创作之路。
1.4 等待线程执行终止的join方法
引用场景
在我们一些情况下,我们需要线程A先完成,然后再完成线程B,如果不加约束的话,两个线程就会抢占式执行,这个是不行的,那我们这个时候就需要用到join了,它能让运行这个方法的线程等待,等待调用它的线程执行完成之后,运行这个方法的线程才能运行。具体的我们可以看一段代码。
public static void main(String[] args) {
Thread t1 = new Thread(()->{
for(int i = 0;i<10;i++){
System.out.println("ThreadI: " + i);
}
});
t1.start();
for(int i = 0;i<10;i++){
System.out.println("mainI: " + i);
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for(int i = 0;i<10;i++){
System.out.println("ThreadI: " + i);
}
});
t1.start();
t1.join();
for(int i = 0;i<10;i++){
System.out.println("mainI: " + i);
}
}
我们可以看到,现在是先打印t1里面的,然后打印main函数的。
我们可以知道,使用这个方法的是main这个线程,调用这个方法的是t1这个线程。
使用join方法的线程会等待,调用join方法的线程。
1.5 让线程睡眠的sleep方法
当一个执行中道德线程调用了Thread的sleep方法后,调用线程会暂时让出指定时间的执行权,但是线程所持有的一些资源是不让出的,比如锁。
private static Object lock = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
try {
synchronized (lock){
System.out.println("t1 get lock");
Thread.sleep(1000);
System.out.println("t1 lose lock");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
Thread t2 = new Thread(()->{
try {
synchronized (lock){
System.out.println("t2 get lock");
Thread.sleep(1000);
System.out.println("t2 lose lock");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
t2.start();
}
从上述代码就可以看出线程在休眠的时候是不会释放锁的。
不管你运行几次,t1和t2都不会交错打印
在指定时间睡眠之后该函数就会正常返回,线程就会处于就绪的状态,然后参与CPU的调度,如果线程在睡眠阶段被其他线程调用了这个线程的interrupt()方法中断该线程,则sleep方法会抛出异常返回。
1.6 让出CPU执行权的yield方法
yield是Thread类中的一个静态方法。当一个线程调用这个函数的时候,就会暗示线程调度器,当前线程想要让出CPU的执行权,让出执行权之后,线程就会处于就绪状态。
这个跟上面介绍的sleep还是有点像的,但是他们两个还是有区别
和sleep的区别
当线程调用sleep这个方法的时候,线程会被阻塞挂起到指定时间结束,在这个期间线程调度器是不会去调度这个线程的
当线程调用yield方法的时候,线程只是让出了自己剩余的时间片,并没有阻塞挂起,只是从执行状态变成就绪状态,线程调度器还是有可能调度这个线程的。
1.7 线程中断
void interrupt()方法
中断线程。
当线程A在运行的时候,线程B调用了线程A的interrupt方法的时候,只是改变了线程A的中断标志位true。线程A并不会受到影响,只是一个标志被改了而已。
public static void main(String[] args) {
Thread t = new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
System.out.println("hello");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t.interrupt();
System.out.println("end");
}
这段代码就是main线程调用了t线程的interrupt,然后改变了while()里面判断的标志,导致了线程t的中断。
还有要注意的是,当线程A在调用wait系列函数,join方法或者sleep方法而被阻塞挂起,这个时候其他线程再调用A的interrupt的时候,就会报错返回
boolean isInterrupted()方法
检测当前线程是否被中断,如果是就返回true,否则就是false
boolean interrupted()方法
检测当前线程是否被中断,如果是就返回true,否则就返回false,跟上面那个方法不一样的是,这个方法如果发现当前线程被中断,就会清除中断标志,并且这个方法是静态方法,可以用Thread直接调用。而且这个方法获得的中断标志是当前线程的,跟调用的实例对象没有关系。
1.8 理解线程上下文切换
在多线程编程中,线程一般都是多于CPU的个数的,而每个CPU同一时刻只能被一个线程使用,但是又为了让用户使用起来感觉所有线程是同时执行的,这个时候就用到了时间片轮转的策略,也就是给每个线程发送一个时间片,线程在时间片内调用CPU执行任务,当前线程使用完时间片之后就会让给下一个线程,这个就是上下文切换。
那么当线程再一次执行的时候,它是怎么知道上一次自己执行到哪了呢?
所以在切换上下文的时候需要保存当前线程的执行现场,等下一次再调用的时候就可以复原现场
线程上下文切换的时机有:当前线程的CPU时间片使用完,当前线程被其他线程中断。