目录
3.sleep()、yield()和wait()方法的区别?
1. 线程的生命周期 6种状态
1.1 线程的生命周期
线程的生命周期也就是线程从生到死的过程中,经历的各种状态及状态转换
1.2 线程的6种状态
NEW: 新建状态,线程还没有启动
RUNNABLE: 可以运行状态,线程调用了start()方法后处于这个状态
BLOCKED: 锁阻塞状态,没有获取到锁处于这个状态
WAITING: 无限等待状态,线程执行时被调用了wait方法处于这个状态
TIMED_WAITING: 计时等待状态,线程执行时被调用了sleep(毫秒)或者wait(毫秒)方法处于这个状态
TERMINATED: 终止状态, 线程执行完毕或者遇到异常时,处于这个状态。
1.3 线程的6种状态之间是如何变化的?
这几种状态之间切换关系如下图所示
注意:sleep不会释放锁, wait会把锁释放
2. 如何保证线程顺序执行?join()
当一个线程调用另一个线程的 join()
方法时,调用 join()
的这个线程会暂停,直到被 join()
的线程执行完毕
例如:
package com.xcw.codefriend.controller;
public class A {
Thread t1 = new Thread(()->{
System.out.println("t1执行");
});
Thread t2 = new Thread(()->{
try {
t1.join();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("t2执行");
});
Thread t3 = new Thread(()->{
try {
t2.join();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("t3执行");
});
public static void main(String[] args) {
A a = new A();
a.t1.start();
a.t2.start();
a.t3.start();
}
}
结果:
代码分析
代码中的线程 t1
、t2
、t3
是按依赖关系来启动和执行的:
-
t2
依赖t1
:线程t2
调用了t1.join()
,这意味着t2
会等待t1
执行完后,t2
才能继续。 -
t3
依赖t2
:线程t3
调用了t2.join()
,这意味着t3
会等待t2
执行完后,t3
才能继续。
3. 3种休眠线程的方式
1.Thread.sleep()
2.Object.wait() sleep()、wait()的用法看4.2
3.LockSupport.park()
Java中的线程休眠大法系列(三)LockSupport.park()-优快云博客
面试 LockSupport.park()会释放锁资源吗? - 彤哥读源码 - 博客园 (cnblogs.com)
4.sleep()、yield()和wait()方法的区别?
4.1 面试回答(★):
共同点:
wait(),wait(long)、sleep(long)和yield() 的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态
区别:
Object.wait():必须搭配锁使用,wait()调用会放弃锁 和 资源(当前线程放弃cpu,别的线程可以使用),线程进入阻塞状态可以使用notify() 和 notifyAll()唤醒
Thread.sleep() :不必搭配锁也可使用,sleep()只会放弃cpu但是锁是拿着的(当前线程放弃cpu,别的线程也用不了)
Thread.yield()当前线程愿意让出cpu时间片,当前线程并没有被阻塞,处于可运行(RUNNABLE)的状态,只有优先级等于或者高于当前线程的线程才可能获取cpu时间片,没有,则继续占用cpu。
4.2 wait()、sleep() 、sleep() 详细解释
Object.wait(): 使当前线程进入休眠状态(TIMED_WAITING状态),wait()的调用必须先获取wait()对象的锁,而sleep则无此限制,方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃cpu,但你们还可以用)进入 wait 状态的线程能够被 notify 和 notifyAll 线程唤醒,而 sleep 状态的线程不能被 notify 方法唤醒;
Thread.sleep() : 使当前线程进入休眠状态(TIMED_WAITING状态),休眠指定时间内暂时放弃对cpu的占用,线程会被唤醒并进入可运行状态,等待CPU时间片的重新分配。sleep()不获取sleep()对象的锁也可以使用,sleep()如果在synchronized代码块中执行,并不会释放对象锁(我放弃cpu,你们也用不了)
Thread.sleep() :只是通知线程调度器当前线程愿意让出cpu时间片,当前线程并没有被阻塞,处于可运行(RUNNABLE)的状态,只有优先级等于或者高于当前线程的线程才可能获取cpu时间片。
- Thread.sleep()必须传入时间,Object.wait()可传可不传,不传表示一直阻塞下去;
- Thread.sleep()到时间了会自动唤醒,然后继续执行;
- Object.wait()不带时间的,需要另一个线程使用Object.notify()唤醒;
- Object.wait()带时间的,假如没有被notify,到时间了会自动唤醒,这时又分好两种情况,一是立即获取到了锁,线程自然会继续执行;二是没有立即获取锁,线程进入同步队列等待获取锁;
调用 wait
方法之后,线程会变为 WATING
状态,而调用 sleep
方法之后,线程会变为 TIMED_WAITING
状态。
4.3 LockSupport.park()详细解释
1. LockSupport.park()休眠线程,LockSupport.unpark()唤醒线程,两个方法配合使用。也可以通过LockSupport.parkNanos()指定休眠时间后,自动唤醒。
2. LockSupport.park()不会释放monitor锁。
3. 线程被打断,LockSupport.park()不会抛出异常,也不会吞噬掉interrupt的状态,调用者可以获取interrupt状态,自行进行判断,线程是由于什么原因被唤醒了。
4. LockSupport.park()会是线程进入WAITING状态,而LockSupport.parkNanos(long nanos) 会进入TIMED_WAITING状态。
5. LockSupport.park(Object blocker)和LockSupport.getBlocker(t1)配合使用,可以进行自定义数据传输。
5.如何停止一个正在运行的线程?
5.1 三种方式可以停止线程
- 使用volatile修饰的退出标志,使线程正常退出,也就是当run方法完成后线程终止
- 使用stop方法强行终止(不推荐,方法已作废)
- 使用interrupt方法中断线程
5.1.1 volatile关键字的作用
volatile它的主要作用是保证变量的可见性和禁止指令重排优化。
1)可见性(Visibility):
volatile关键字确保变量的可见性。当一个线程修改了volatile变量的值,新值会立即被刷新到主内存中,其他线程在读取该变量时可以立即获得最新的值。这样可以避免线程间由于缓存一致性问题导致的"看见"旧值的现象。
2)禁止指令重排序(Ordering):
volatile还通过内存屏障来禁止特定情况下的指令重排序,从而保证程序的执行顺序符合预期。对
volatile变量的写操作会在其前面插入一个StoreStore屏障,而对volatile变量的读操作则会在其后
插入一个LoadLoad屏障。这确保了在多线程环境下,某些代码块执行顺序的可预测性。
5.2 代码举例
5.2.1 使用退出标志(flag),使线程正常退出
public class MyInterrupt1 extends Thread {
volatile boolean flag = false ; // 线程执行的退出标记
@Override
public void run() {
while(!flag) {
System.out.println("MyThread...run...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws
InterruptedException {
// 创建MyThread对象
MyInterrupt1 t1 = new MyInterrupt1() ;
t1.start();
// 主线程休眠6秒
Thread.sleep(6000);
// 更改标记为true
t1.flag = true ;
}
}
5.2.2 使用stop方法强行终止(不推荐,方法已作废)
与方法1类似,只不过这里不是主线程修改flag,而是子线程执行stop()强制关闭当前线程。
public class MyInterrupt2 extends Thread {
@Override
public void run() {
while(true) {
System.out.println("MyThread...run...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws
InterruptedException {
// 创建MyThread对象
MyInterrupt2 t1 = new MyInterrupt2() ;
t1.start();
// 主线程休眠2秒
Thread.sleep(6000);
// 调用stop方法
t1.stop();
}
}
为什么stop()
方法不推荐使用:
不安全:
stop()
方法会立即停止线程,可能会导致线程持有的锁没有释放,造成死锁。不可预测:线程可能在执行到一半的时候被强制停止,并且不会清理线程正在执行的任务,这可能会导致数据不一致或者资源泄露。
违反线程协作模型:线程应该通过协作的方式来中断,而不是强制停止。
5.2.3 使用interrupt方法中断线程
1.两种情况
分两种情况:
- 打断阻塞的线程(sleep,wait,join)的线程,线程会抛出InterruptedException异常
- 打断正常的线程,可以根据打断状态来标记是否退出线程
2.interrupt相关常用API
上面提到的标记其实就是一个true或false标签,例如:
Thread current = Thread.currentThread(); //获取当前线程
boolean interrupted = current.isInterrupted(); //判断线程是否被打断,没有则返回false
详细的看下面例2
举例:
1. 打断阻塞的线程(sleep,wait,join)的线程
package com.itheima.basic;
public class MyInterrupt3 {
public static void main(String[] args) throws
InterruptedException {
//1.打断阻塞的线程
Thread t1 = new Thread(()->{
System.out.println("t1 正在运行...");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
t1.start();
Thread.sleep(500);
t1.interrupt();
System.out.println(t1.isInterrupted());
}
}
运行结果:
打断阻塞的线程(sleep,wait,join)的线程,线程会抛出InterruptedException异常
2. 打断正常的线程,可以根据打断状态来标记是否退出线程
package com.itheima.basic;
public class MyInterrupt3 {
public static void main(String[] args) throws
InterruptedException {
//2.打断正常的线程
Thread t2 = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
boolean interrupted = current.isInterrupted();
if (interrupted) {
System.out.println("打断状态:" + interrupted);
break;
}
}
}, "t2");
t2.start();
Thread.sleep(500);
t2.interrupt();
}
}
刚开始while(true)
Thread current = Thread.currentThread(); //获取当前线程
boolean interrupted = current.isInterrupted(); //判断线程是否被打断,没有则返回false
则if( interrupted)为false不执行
下面t2.interrupt( ),打断线程,则boolean interrupted = current.isInterrupted();返回true,则while循环死循环里面的if会执行,最后break跳出死循环,线程t2 被打断运行。
线程基础知识可以参考我以前的笔记,都有详细的介绍