Java并发编程--线程的状态
前言
在上一篇博文中,已经介绍了volatile关键字的使用及原理,知道其具备并发编程所需的可见性,有序性以及单个volatile变量的原子性,在这篇博文我们继续来看看线程的状态。
正文
我们知道,线程作为操作系统的最小单元,并且能够让多线程同时运行,能极大地提高程序的性能,充分利用多核环境的优势。在理解线程的特性和原理之后,我们在使用多线程编程时能避免很多问题。
线程的状态
我们在使用Java编程时,用到Thread的地方应该很多,那我们应该知道线程能够被创建,也可以被销毁,那我们接下来先从线程的生命周期开始去了解线程:
线程一共有6种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)
NEW:初始状态,线程被构建,但是还没有调用start方法;
RUNNABLED:运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为“运行中”;
BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使用权,阻塞也分为几种情况:
Ø 等待阻塞:运行的线程执行wait方法,jvm会把当前线程放入到等待队列;
Ø 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么jvm会把当前的线程放入到锁池中;
Ø 其他阻塞:运行的线程执行Thread.sleep或者t.join方法,或者发出了I/O请求时,JVM会把当前线程设置为阻塞状态,当sleep结束、join线程终止、io处理完毕则线程恢复;
TIME_WAITING:超时等待状态,超时以后自动返回;
TERMINATED:终止状态,表示当前线程执行完毕;
下图为线程状态
代码示例
public class ThreadStatus {
public static void main(String[] args) {
//TIME_WAITING
new Thread(() -> {
while (true) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "timewaiting").start();
//WAITING,线程在ThreadStatus类锁上通过wait进行等待
new Thread(() -> {
while (true) {
synchronized (ThreadStatus.class) {
try {
ThreadStatus.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "Waiting").start();
//线程在ThreadStatus加锁后,不会释放锁
new Thread(new BlockedDemo(), "BlockDemo-01").start();
new Thread(new BlockedDemo(), "BlockDemo-02").start();
}
static class BlockedDemo extends Thread {
public void run() {
synchronized (BlockedDemo.class) {
while (true) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
通过命令查看线程状态
• 打开终端或者命令提示符,键入“jps”,(JDK1.5提供的一个显示当前所有java进程pid的命令),可以获得相应进程的pid;
• 根据上一步骤获得的pid,继续输入jstack pid(jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息)。
jps
jstack pid //pid为jps获取到的pid
线程的终止
线程如何启动大家都非常熟悉,但是如何终止一个线程,我想很多人都会说调Thread的stop方法。
但是这里要提醒大家的是,虽然api仍然可以调用,但是和其他的线程控制方法如suspend、resume一样都是过期了的不建议使用,就拿stop来说,stop方法在结束一个线程时并不会保证线程的资源正常释放,因此会导致程序可能出现一些不确定的状态。
要优雅的去中断一个线程,在线程中提供了一个interrupt方法:
interrupt
当其他线程通过调用当前线程的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()) {
i++;
}
System.out.println("Num:" + i);
}, "interruptDemo");
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();
}
}
这种通过标识位或者中断操作的方式能够使线程在终止时有机会去清理资源,而不是武断地将线程停止,因此这种终止线程的做法显得更加安全和优雅。
Thread.interrupted
上面的示例中,通过interrupt,设置了一个标识告诉线程可以终止了,线程中还提供了静态方法Thread.interrupted()对设置中断标识的线程复位。比如在上面的示例中,外面的线程调用thread.interrupt来设置中断标识,而在线程里面,又通过Thread.interrupted把线程的标识又进行了复位。
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
boolean ii = Thread.currentThread().isInterrupted();
if (ii) {
System.out.println("before:" + ii);
Thread.interrupted();//对线程进行复位,中断标识为false
System.out.println("after:" + Thread.currentThread().isInterrupted());
}
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();//设置中断标识,中断标识为true } }
}
}
被动的线程中断标识位复位
除了通过Thread.interrupted方法对线程中断标识进行复位以外,还有一种被动复位的场景,就是对抛出InterruptedException异常的方法,在InterruptedException抛出之前,JVM会先把线程的中断标识位清除,然后才会抛出InterruptedException,这个时候如果调用isInterrupted方法,将会返回false。
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (true) {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
//抛出该异常,会将复位标识设置为false
e.printStackTrace();
}
}
});
thread.start();
TimeUnit.SECONDS.sleep(1);
thread.interrupt();//设置复位标识为true
TimeUnit.SECONDS.sleep(1);
System.out.println(thread.isInterrupted());//false
}
}
第二种线程终止的方式
除了通过interrupt标识为去中断线程以外,我们还可以通过下面这种方式,定义一个volatile修饰的成员变量,来控制线程的终止。这实际上是应用了volatile能够实现多线程之间共享变量的可见性这一特点来实现的。
public class VolatileDemo {
private volatile static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
int i = 0;
while (!stop) {
i++;
}
});
thread.start();
System.out.println("begin start thread");
Thread.sleep(1000);
stop = true;
}
}