一个线程的生命周期是线程的建立到线程的最终销毁的整个过程,线程的生命周期可以分为以下几个状态:新建状态,就绪状态,运行状态,阻塞状态,死亡状态。
1.线程的新建状态
在java中使用new关键字用于生成一个新的对象,这时的线程没有调用start方法,出现新建状态。
2.线程的就绪状态
当线程类使用new关键字生成了一个对象,调用该对象的start()的方法,这时该线程处于就绪状态,注意,调用了start方法后线程不会马上进入运行状态,进行执行,我们并不能控制线程何时开始执行,这时候,我们只能等到JVM虚拟机调度相关cpu资源以执行该线程。
3.线程的运行状态
当cpu开始执行该线程的时候,此时线程处于运行状态,线程执行的只有线程内部的唯一的执行体,就是run函数内部的内容,当run方法执行完成了,我们也就认为该线程的运行状态结束,但是我们常常需要在某种特殊的的情况下,停止线程的执行,通常这时候我有两种方式可以实现,设标示和中断线程,另外还有一种方式stop,不过该方法已经被舍弃了,因为该方法存在内容竞争,可能会引起死锁。
以下是一个简单的例子:
public class ThreadLifeCycle {
public static void main(String[] args) {
TestThread myTestThread = new TestThread();
myTestThread.setStop(false);
myTestThread.start();
try {
Thread.sleep(10000);
myTestThread.setStop(true);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static class TestThread extends Thread {
private boolean isStop;
private int testNum = 1;;
public boolean isStop() {
return isStop;
}
public void setStop(boolean isStop) {
this.isStop = isStop;
}
public void run() {
super.run();
System.out.println("isStop-----------> " + isStop);
while(!isStop){
System.out.println("Test Number is -----> " + testNum++);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("isStop-----------> " + isStop);
}
}
}
在这里设置了一个标志位isStop,来决定线程什么时候结束,我们可以通过调用setStop方法来改变isStop的值,从而结束线程,通过测试发现一个现象就是运行的结果可能出现两种情况,以下是两种运行效果:
第一种:
isStop-----------> false
Test Number is -----> 1
Test Number is -----> 2
Test Number is -----> 3
Test Number is -----> 4
Test Number is -----> 5
Test Number is -----> 6
isStop-----------> true
第二种运行结果为:
isStop-----------> false
Test Number is -----> 1
Test Number is -----> 2
Test Number is -----> 3
Test Number is -----> 4
Test Number is -----> 5
isStop-----------> true
这是为什么呢,原来,通过设置标志位的方式来结束线程,子线程必须等待外部调用setStop方法,改变了标志位isStop的值后才能停止while循环,运行完run方法,从而结束该线程,而线程的执行顺序我们是不知道的,所以这里就可能出现延迟,并且延迟是无法避免的,但是我们必须想办法减少延迟,于是就有线程内的另外一个方法interrupt(),使用interrupt()也存在一定的风险,它会抛出InterruptedException,所以我们在使用时必须捕获这个异常,而通常我们捕获到这个异常后并不会做任何有意义的操作,这可以减少延迟,但是并不能消除延迟,这是因为这个方法的调用也是在其他的线程中,所以我们并不能保证两者的先后顺序。
下面的例子相关的代码:
public class ThreadLifeCycle {
/**
* @param args
*/
public static void main(String[] args) {
TestThread myTestThread = new TestThread();
myTestThread.start();
try {
Thread.sleep(100);
myTestThread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static class TestThread extends Thread {
private int testNum = 1;;
public void run() {
super.run();
while(!isInterrupted()){
System.out.println("Test Number is -----> " + testNum++);
}
}
}
}
另外一种情况是在run方法中存在sleep,wait方法这时的处理方式如下面的例子:
public class ThreadLifeCycle {
public static void main(String[] args) {
TestThread myTestThread = new TestThread();
myTestThread.start();
try {
Thread.sleep(10000);
myTestThread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static class TestThread extends Thread {
private int testNum = 1;;
public void run() {
super.run();
while(true){
System.out.println("Test Number is -----> " + testNum++);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("sleep is interrupted ");
return;
}
}
}
}
}
那么这两种方式有什么不同之处呢?
1.假设在第二种情况下,我们调用了interrupt()方法后,捕获到Thread.sleep()的位置出现异常时不结束掉该线程的运行,该线程是不会停止下来,我们必须在Thread.sleep()出现异常时,捕获到相关的异常,并且在这里结束run方法的运行,从而使线程进入到死亡状态,这是因为在调用了sleep或者wait方法后,线程进入了阻塞状态,这时调用interrupt方法会打断该线程原有的阻塞状态,使线程醒过来,并且会抛出一个InterruptedException的异常,我们在异常的catch块结束掉run方法的运行,从而使线程进入死亡状态。
如假如我们将第二种情况下的代码变成这个样子:
public class ThreadLifeCycle {
public static void main(String[] args) {
TestThread myTestThread = new TestThread();
myTestThread.start();
try {
Thread.sleep(10000);
myTestThread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static class TestThread extends Thread {
private int testNum = 1;;
public void run() {
super.run();
while(!isInterrupted()){
System.out.println("Test Number is -----> " + testNum++);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("sleep is interrupted ");
// return;
}
}
}
}
}
我们会发现执行的结果为:
Test Number is -----> 1
Test Number is -----> 2
Test Number is -----> 3
Test Number is -----> 4
Test Number is -----> 5
java.lang.InterruptedException: sleep interrupted
sleep is interrupted
Test Number is -----> 6
at java.lang.Thread.sleep(Native Method)
at ThreadLifeCycle$TestThread.run(ThreadLifeCycle.java:31)
Test Number is -----> 7
Test Number is -----> 8
Test Number is -----> 9
Test Number is -----> 10
Test Number is -----> 11
Test Number is -----> 12
Test Number is -----> 13
Test Number is -----> 14
可以看到当我们捕获到异常后,没有退出而是继续执行下去了。
2.第一种方式,因为没有sleep或者wait操作,线程并没有进入到阻塞状态的可能性,那么这时候,我们怎么样判断线程已经被打断了呢,JDK已经给我们提供了一个方法isInterrupted(),所以我们必须把while(ture),替换成while(!isInterrupted()),但是这里需要注意的是,当存在sleep或者wait操作时,即使我们把while(ture),替换成while(!isInterrupted())也是不能结束掉该线程的。
当然,无论我们采用上面的那种方式都无法消除延迟这是线程处理中永远无法避免的问题,我们只有在实际的使用中尽量的避免我们的业务逻辑会受到这个问题所带来的影响。
4.线程的阻塞状态
当处于就绪状态的线程得了cup时间片就开始运行,这时线程处于运行状态,当CPU时间片用完后,该线程有进入到了就绪状态,但是,还有以下几种情况可能是线程进入到阻塞状态:
1.线程调用了sleep方法,主动的放弃了CPU的相关资源;
2.线程在进行某种IO操作,在操作过程中线程被阻塞掉了;
3.线程在执行的过程中,需要某一资源,而该资源正被另外的线程占用,这时候该线程必须等待,其他线程释放该资源;
4.线程调用了wait方法,在等待notify方法的通知;
那么和上面4种情况,对应的4种让线程恢复到就绪状态的方式如下(注意结束阻塞状态的线程,线程进入的是就绪状态,而不是马上进入运行状态,它必须等待下一次,CPU时间片的到达。
1.调用sleep休眠的时间到了;
2.IO操作完成;
3.得到另一线程占用的资源;
4.另一对象通过notify()或notifyAll()方法通知唤醒;
4.线程的死亡状态
当线程的run方法运行完毕,线程就进入了死亡状态。当然还有一些意外的情况了,如线程在执行过程中出现了异常或者错误,而我们的程序有没有捕获该异常或错误,那这时该线程也就结束运行了;当然还有一个方法stop,不过,这个方法我们前面已经提到过,java已经废弃了这个方法,因为,这个方法可能会引起死锁。
可以通过isAlive方法来判断线程是否还活着,在线程处于就绪,运行,阻塞状态时,调用该方法的返回值都是ture,在调用start方法前和run方法执行完成之后,调用该方法返回值为false,当然,一个处于死亡状态的线程,不能再调用start方法,start方法只能调用一次,且调用时线程必须处于新建状态。