1、java线程生命周期:
线程的5个状态
1、新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2、就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,获取cpu 的使用权,并不是说执行了t.start()此线程立即就会执行
3、运行状态(Running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。 当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4、阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,,也即让出了cpu timeslice, 停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。才有机会再次获得cpu timeslice 转到运行(running)状态 根据阻塞产生的原因不同,阻塞状态又可以分为三种:
- 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;JVM会把该线程放入等待队列(waitting queue)中
- 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,获取synchronized同步锁失败 , 它会进入同步阻塞状态 ,则JVM会把该线程放入锁池(lock pool)中
- 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
2、sleep、yield、join、wait总结
- sleep:释放cpu资源,不释放锁资源。Thread类的方法,必须带一个时间参数。会让当前线程休眠进入阻塞状态并释放CPU,提供其他线程运行的机会且不考虑优先级,但如果有同步锁则sleep不会释放锁即其他线程无法获得同步锁 可通过调用interrupt()方法来唤醒休眠线程。
- yield:让出CPU调度,Thread类的方法,类似sleep只是不能由用户指定暂停多长时间 ,并且yield()方法只能让同优先级的线程有执行的机会。 yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。调用yield方法只是一个建议,告诉线程调度器我的工作已经做的差不多了,可以让别的相同优先级的线程使用CPU了,没有任何机制保证采纳。
- wait:释放cpu资源,也释放锁资源。Object类的方法(notify()、notifyAll() 也是Object对象),必须放在循环体和同步代码块中,执行该方法的线程会释放锁,进入线程等待池中等待被再次唤醒(notify随机唤醒,notifyAll全部唤醒,线程结束自动唤醒)即放入锁池中竞争同步锁
- join:Thread类方法,一种特殊的wait,当前运行线程调用另一个线程的join方法,则当前线程进入阻塞状态直到另一个线程运行结束等待该线程终止。 注意该方法也需要捕捉异常。等待调用join方法的线程结束,再继续执行。如:t.join();主要用于等待 t 线程 运行结束,若无此句,main则会执行完毕,导致结果不可预测。
join() 一直等待
join(long millis) 等待指定毫秒数
join(long millis, int nanos) 等待指定毫秒数
3、interrupt总结
背景:
- 在Java中没有办法立即停止一个线程(Thread.stop()方法已经被标记为废弃,因为stop方法停止线程太过于粗暴,立即停止线程,不给释放资源后续处理等操作留有余地,会出现安全隐患)。
- 如果在一个线程中想控制另外一个线程的状态,例如:取消一个耗时操作。这个时候就可以使用java的中断机制
什么是中断:
- 中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。若要中断一个线程,你需要手动调用该线程的interrupted方法,该方法也仅仅是将线程对象的中断标识设成true;接着你需要自己写代码不断地检测当前线程的标识位;如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。
- 每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;
- 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
中断的相关方法:
public void interrupt() 将调用者线程的中断状态设为true。
public boolean isInterrupted() 判断调用者线程的中断状态。
public static boolean interrupted 只能通过Thread.interrupted()调用。 它会做两步操作:
返回当前线程的中断状态;将当前线程的中断状态设为false;
中断方法的使用:
/**
* @Description 测试不同状态下线程被中断后的行为
* @Author lhy
* @Date 2019/11/21 15:01
*/
public class TheadStatusInterruptDemo {
public static void main(String[] args) throws InterruptedException {
testRunningInterrupt();
testBlockedInterrupt();
}
/**
* 测试阻塞状态下被interrupt
* @throws InterruptedException
*/
private static void testBlockedInterrupt() throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
System.out.println("catch:" + Thread.currentThread().isInterrupted());
break;
}
}
//经过catch InterruptedException 处理后的线程中断状态已经被重置
System.out.println("interrupted!!!:" + Thread.currentThread().isInterrupted());
}
});
t1.start();
Thread.currentThread().sleep(2000);
t1.interrupt();
t1.join();
}
/**
* 测试运行中状态的线程被interrupt
* @throws InterruptedException
*/
private static void testRunningInterrupt() throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("running...");
}
//中断处理的代码
System.out.println("interrupted!!!" + Thread.currentThread().isInterrupted());
}
});
t1.start();
Thread.currentThread().sleep(2000);
t1.interrupt();
t1.join();
}
}
/**
* 测试运行中状态的线程被interrupt
* @throws InterruptedException
*/
private static void testRunningAfterBlockedInterrupt() throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (true) {
System.out.println("running...:"+i++);
if(i>5000){
try {
System.out.println("sleep:" +
Thread.currentThread().isInterrupted());
//阻塞之前,interrupt被设置为true,此次会抛出异常
Thread.sleep(1000);
} catch (InterruptedException e) {
//catch中 interrupt状态位已经被自动重置为false
System.out.println("interrupted!!!" +
Thread.currentThread().isInterrupted());
break;
}
}
}
}
});
t1.start();
t1.interrupt();
t1.join();
}
总结:
- interrupt方法简单的把中断状态设置为ture,被通知中断的线程在阻塞时,这个时候抛出异常,并且中断状态为false。
- interrupted方法做两件事,返回当前中断状态,和清除中断状态,不能恢复false为true,只能把true清除为false。
要真正理解interrupt()方法,要先了解stop()方法。在以前通过thread.stop()可以停止一个线程,注意stop()方法是可以由一个线程去停止另外一个线程,这种方法太过暴力而且是不安全的,怎么说呢,线程A调用线程B的stop方法去停止线程B,调用这个方法的时候线程A其实并不知道线程B执行的具体情况,这种突然间地停止会导致线程B的一些清理工作无法完成,还有一个情况是执行stop方法后线程B会马上释放锁,这有可能会引发数据不同步问题。基于以上这些问题,stop()方法被抛弃了。
在这样的情况下,interrupt()方法出现了,它与stop不同,它不会真正停止一个线程,它仅仅是给这个线程发了一个信号告诉它它应该结束了(设置一个停止标志)。真正符合安全的做法,就是让线程自己去结束自己,而不是让一个线程去结束另外一个线程。
通过interrupt()和.interrupted()方法两者的配合可以实现正常去停止一个线程,线程A通过调用线程B的interrupt方法通知线程B让它结束线程,在线程B的run方法内部,通过循环检查.interrupted()方法是否为真来接收线程A的信号,如果为真就可以抛出一个异常,在catch中完成一些清理工作,然后结束线程。Thread.interrupted()会清除标志位,并不是代表线程又恢复了,可以理解为仅仅是代表它已经响应完了这个中断信号然后又重新置为可以再次接收信号的状态。从始至终,理解一个关键点,interrupt()方法仅仅是改变一个标志位的值而已,和线程的状态并没有必然的联系。