java线程状态的切换、守护线程、线程的中断

线程的几种状态及其切换

线程的状态

  • 新建(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的值来进行中断操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值