线程的几种状态及其切换
线程的状态
- 新建(New):线程被创建但尚未开始运行。此状态的线程在调用 start() 方法后进入下一状态。
- 可运行(Runnable):线程可以运行,但并不一定在执行。它可能正在等待 CPU 资源。这个状态可以分为两种:
- 就绪(Ready):线程已准备好运行,等待 CPU 分配时间。
- 运行(Running):线程正在执行其任务。
- 阻塞(Blocked):线程因请求一个锁而被阻塞,无法继续执行,直到获得该锁。
- 等待(Waiting):线程处于等待状态,等待其他线程执行特定的操作(如调用 wait()、join() 或 LockSupport.park())。
- 超时等待(Timed Waiting):线程在等待某个条件时设置了超时,例如调用 sleep(millis)、wait(millis) 或 join(millis)。
- 终止(Terminated):线程已经完成执行,进入终止状态。此状态的线程不能再运行。
线程状态切换图
那么这些状态在什么情况下会相互转换呢?通过下图想必大家就可以一目了然的看清楚,线程之间相互转换。
面试题:睡眠和等待的区别是什么(sleep和wait的区别是什么)
首先sleep和wait都会立刻让出cpu,
任意线程都可以进入sleep状态,
但是只有加锁的线程才可以wait(等待),同时释放锁
如果sleep持有锁,则睡眠的时候不会释放锁,sleep线程依旧持有锁
下面我们通过代码来学习一下线程的状态
static class TimeWaiting implements Runnable {
@Override
public void run() {
while (true) {
SleepUtils.second(100);
}
}
}
- 该线程一直处于睡眠状态
其中 SleepUtils.second(100); 是封装的一个类就是下面这个类,
public class SleepUtils {
public static final void second(long seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);//这个是java自带的进入睡眠状态的函数
} catch (InterruptedException e) {
}
}
}
- 线程进入等待态
static class Waiting implements Runnable {
@Override
public void run() {
while (true) {
synchronized (Waiting.class) {
try {
Waiting.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
synchronized (Waiting.class) 是一个加锁的步骤,在这一步骤中Waiting.class是“Waiting”类的一个静态对象,强调一下这里锁的是一个类的静态对象,而不是锁上了这个类,由于是静态对象,自然地Waiting.class.wait(); 中的 Waiting.class和加锁的对象是同一个对象。
同时只有上锁的资源才可以调用wait方法,这也就对应了前面提到的,要想wait就必须要要先加锁。
- 如果线程拿到锁然后直接进入睡眠状态会怎样呢?
static class Blocked implements Runnable {
public void run() {
synchronized (Blocked.class) {
while (true) {
SleepUtils.second(100);
}
}
}
}
该线程给Blocked.class加上锁之后进入了睡眠状态,这时如果有其他的线程想要访问Blocked.class,那么该线程会直接进入阻塞状态。
咱们现在到主函数里去运行一下这几个线程吧!
public static void main(String[] args) {
//new TimeWaiting ()是继承接口的对象
//"TimeWaitingThread"是线程的名字
new Thread(new TimeWaiting (), "TimeWaitingThread").start();//线程1
new Thread(new Waiting(), "WaitingThread").start();//线程2
// 使用两个Blocked线程,一个获取锁成功,另一个被阻塞
new Thread(new Blocked(), "BlockedThread-1").start();//线程3
new Thread(new Blocked(), "BlockedThread-2").start();//线程4
}
线程1,一直处于睡眠状态
线程2,一直处于等待中
线程3和4,其中一个一直处于睡眠,另一个则一直处于阻塞状态,
原因是: Blocked.class是静态资源,所以3和4必将会去竞争同一资源,其中拿到资源的一方,将会带着锁进入睡眠态,而没有竞争成功的则进入阻塞状态。
Daemon线程(守护线程)
Daemon线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。
什么是支持性线程呢?
举个栗子,比如说我们创建了一个普通线程何时入栈何时出栈,何时加锁,这些操作这个线程是无法完成的,需要其他的线程来帮忙完成,这些线程就是支持性线程。
就像你要去参加马拉松长跑,你不需要去自己准备跑道,这就和我们的线程一样,我们线程怎么就绪怎么运行,这些事情都是通过支撑性线程去完成,
面试题分析java线程的常用指令有哪些?
jstack
线程中断
什么是线程中断?
中断就是让线程停下来
public class Interrupted {
public static void main(String[] args) throws Exception {
// sleepThread不停的尝试睡眠
Thread sleepThread = new Thread(new SleepRunner(), "SleepThread");
sleepThread.setDaemon(true);
// busyThread不停的运行
Thread busyThread = new Thread(new BusyRunner(), "BusyThread");
busyThread.setDaemon(true);
sleepThread.start();
busyThread.start();
// 休眠5秒,让sleepThread和busyThread充分运行
TimeUnit.SECONDS.sleep(5);
sleepThread.interrupt();//****中断1
busyThread.interrupt();//****中断2
System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
// 防止sleepThread和busyThread立刻退出
SleepUtils.second(2);
}
static class SleepRunner implements Runnable {
@Override
public void run() {
while (true) {
SleepUtils.second(10);
}
}
}
static class BusyRunner implements Runnable {
@Override
public void run() {
while (true) {
}
}
}
}
这段代码其实就很容易懂了,有两个继承了Runnable接口的类,创建两个线程一个线程(busy)会不停运行,另外一个线程则一直睡眠(sleep),然后分别调用两个线程的 interrupt() 函数,然后查看两个线程的中断状态。
这里有一个重点,大家觉得执行过 interrupt() 函数的两个线程是被中断,已经停止运行了吗?
其实并没有,执行了 interrupt() 函数后,只是会在线程对象中改变“interrupt”属性的值而线程并不会停止,那么我们想要让线程真正的中断该怎么办呢?
我们在下面的安全终止线程中会提到.
输入结果如下:
SleepThread interrupted is false
BusyThread interrupted is true
为什么会出现这样的结果呢?这里解释一下,只有持有CPU的线程在执行 interrupt() 方法时会直接将线程的“interrupt”变量(或者说是属性)改为false。对于没有持有cpu的线程无效
安全终止线程
怎么样可以让线程终止呢?看到这里我们了解了好几种线程的代码写法了,其中每个 run() 方法中我们都可以看到一个 while(true) 的死循环,来确保线程可以一直运行,只有将true变为false的时候才能让一个线程停下来。
下面我们提供了两种方法
public class Shutdown {
public static void main(String[] args) throws Exception {
Runner one = new Runner();
Thread countThread = new Thread(one, "CountThread");
countThread.start();
// 睡眠1秒,main线程对CountThread进行中断,使CountThread能够感知中断而结束
TimeUnit.SECONDS.sleep(1);
countThread.interrupt();
Runner two = new Runner();
countThread = new Thread(two, "CountThread");
countThread.start();
// 睡眠1秒,main线程对Runner two进行取消,使CountThread能够感知on为false而结束
TimeUnit.SECONDS.sleep(1);
two.cancel();
}
private static class Runner implements Runnable {
private long i;
private volatile boolean on = true;
@Override
public void run() {
while (on && !Thread.currentThread().isInterrupted()){
i++;
}
System.out.println("Count i = " + i);
}
public void cancel() {
on = false;
}
}
}
方法一:
就是将通过,定义boolean类型变量on,调用 cancel() 方法将on的值改为false,来实现。
方法二:
通过java自带的 interrupt(); 函数更改变量值,通过 !Thread.currentThread().isInterrupted() 的来判断interrupt的值来进行中断操作。