java线程的状态
线程的状态包括 新建状态,运行状态,阻塞等待状态和消亡状态。其中阻塞等待状态又分为BLOCKED, WAITING和TIMED_WAITING状态。
源码:注意看我的注释
public enum State {
NEW,//这一个已经创建的线程,但是还没有调用start方法启动的线程所处的状态。
RUNNABLE,//有可能正在运行,或者正在等待CPU资源。总体上就是当我们创建线程并且启动之后,就属于Runnable状态。
BLOCKED,//阻塞状态,当线程准备进入synchronized同步块或同步方法的时候,需要申请一个监视器锁而进行的等待,会使线程进入BLOCKED状态。
WAITING,//该状态的出现是因为调用了Thread.sleep(),Object.wait()或者Thread.join()或者LockSupport.park()。处于该状态下的线程在等待另一个线程 执行一些其余action来将其唤醒。
TIMED_WAITING,//该状态和上一个状态其实是一样的,是不过其等待的时间是明确的。
TERMINATED;//线程执行结束了,run方法执行结束表示线程处于消亡状态了。
}
wait和sleep的区别
- sleep方法:是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进入可运行状态,等待CPU的到来。睡眠不释放锁(如果有的话)。
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
public static native void sleep(long millis) throws InterruptedException;
- wait方法:是Object的方法,必须与synchronized关键字一起使用,线程进入阻塞状态,当notify或者notifyall被调用后,会解除阻塞。但是,只有重新占用互斥锁之后才会进入可运行状态。睡眠时,会释放互斥锁。
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final native void wait(long timeout) throws InterruptedException;
join和yield
- join 方法:当前线程调用,则其它线程全部停止,等待当前线程执行完毕,接着执行。
- yield 方法:该方法使得线程放弃当前分得的 CPU 时间。但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。
查看线程状态方式
- 打开target
- 右击class文件选择Open in terminal
- 输入jps查看进程
- 输入jstack 想要查看的java程序的端口号
- 找到对应线程名的日志即可看到状态
线程的停止
在正常情况下,线程执行完run方法就会自动销毁,这是不需要人为干预去结束的。
而以下情况需要人为干预
- 线程中存在无限循环执行,比如while(true)循环
- 线程中存在一些阻塞的操作,比如sleep、wait、join等。
stop方法可以来停止线程(但不建议使用)
原因:很可能出现线程的任务执行了一半就被强制中断,最终导致数据产生问题。这种行为类似于在linux系统中执行 kill -9类似,它是一种不安全的操作。
比较好的方式是
- 停止接收新的请求
- 把已经接收到的请求处理完毕
- 停止线程
这种比较比较好的方式就可以通过interrupt方法来实现。
interrupt方法(原理就是靠标识位)
事先说明
若线程是正常运行状态,则当线程发送中断请求后,线程的中断状态被置为true,即isInterrupted()返回true。
当其他线程通过调用当前线程的interrupt方法,表示向当前线程打个招呼,告诉他可以中断线程的执行了,至于什么时候中断,取决于当前线程自己。线程通过检查自身是否被中断来进行相应,可以通过isInterrupted()来判断是否被中断。
案例
public class InterruptDemo {
private static int i; public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
//默认情况下 isInterrupted返回false、通过thread.interrupt变成了true
i++;
}
System.out.println("Num:"+i);
},"interruptDemo");
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
我们可以看到,线程收到中断信号后,仍然打印了数据,说明线程是把自己的任务执行完了才退出的!
这种interrupt中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅。与这种方法一样道理的还有通过标识位(即false和ture),很简单就不演示了
处于阻塞状态下的线程中断
事先说明
这个跟上面不一样,处于阻塞的线程(即在执行Object对象的wait方法,或者线程类的join,sleep方法后线程的状态),当线程调用interrupt()方法后,这些方法将抛出InterruptedException异常,并清空线程的中断状态,即isInterrupted()返回false。抛出异常的目的是为了线程从阻塞状态醒过来。而返回false的目的是将真正结束线程的权利交给程序员。
public class BlockedDemo {
private static int i;
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
// Thread.currentThread().interrupt();
}
}
System.out.println("Num:"+i);
},"interruptDemo");
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
执行结果
可以看到线程只抛出了异常,并没有被中断,因为isInterrupted()返回false,会继续一直循环。
如果要真正停止,需要在捕获到异常的地方再次真正中断
public class BlockedDemo {
private static int i;
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
while(!Thread.currentThread().isInterrupted()){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
System.out.println("Num:"+i);
},"interruptDemo");
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
可以看到线程真的结束了。