线程的生命周期
第三节 线程的生命周期
文章目录
前言
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中,也有一个从产生到死亡的过程。
在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。
一、线程的生命周期
- 新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
- 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
- 阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
- 死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
一、新建状态
- 新建状态就是进行线程的创建,但是要注意的是,这里的新建仅仅在JAVA的这种编程语言层面被创建,真正的线程需要通过调用start()方法后,才可以真正的创建一个线程。
创建线程代码如下:
Thread t1 = new Thread();
二、就绪状态
- 调用start()方法后,JVM 进程会去创建一个新的线程,而此线程不会马上被 CPU 调度运行,进入Running状态,这里会有一个中间状态,就是Runnable状态,你可以理解为等待被 CPU 调度的状态。Java中线程采用的是抢占式方式,即所有线程都有概率可以抢到被CUP的执行权。
代码如下:
t1.start()
三、运行状态
- 运行状态即是处于就绪状态的某个线程获得CPU的执行权后,就会从就绪状态变为运行状态,执行run()方法中的任务。
- 该线程失去了CPU执行权后,就会又从运行状态变为就绪状态。重新等待系统分配资源。 也可以对在运行状态的线程调用yield()方法,它就会让出cpu资源,再次变为就绪状态。
- 可以通过
一、线程调用sleep方法主动放弃所占用的系统资源
二、线程试图获得一个同步监视器,但更改同步监视器正被其他线程所持有
三、线程在等待某个通知(wait()方法)
…
使得线程进入阻塞状态。 - 当线程的run()方法执行完,或者被强制性地终止,例如出现异常,或者调用了stop()、desyory()方法等等,就会从运行状态转变为死亡状态。
四、阻塞状态
- 在阻塞状态的线程不能进入就绪队列。可以理解为该线程在睡觉或者发呆,在阻塞期间该线程不可以进行任何操作。
五、死亡状态
- 当线程的run()方法执行完,或者被强制性地终止,就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
二、线程生命周期中相关方法
1、sleep() 线程休眠
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。休眠的线程进入阻塞状态。
代码如下:
public class ThreadC extends Thread {
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
try {
//单位为毫秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.currentThread().getName()用来获取当前线程的名称
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
public static void main(String[] args) {
ThreadC threadC = new ThreadC();
//设置当前线程名字为one
threadC.setName("one");
threadC.start();
}
}
2、yield()线程让步
调用yield方法的线程,会释放当前CPU的执行权,让其他优先级相同或者高于其优先级的线程运行,即也有可能自己又会抢到CPU的执行权。(大概率其他线程先运行,小概率自己会运行)
代码如下:
public class ThreadyieldTest {
static class Thread1 extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
//开启线程让步,让出CPU执行权
Thread1.yield();
System.out.println(Thread1.currentThread().getName()+"--->"+i);
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread2.currentThread().getName()+"--->"+i);
}
}
}
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
thread1.setName("aaa");
thread1.start();
Thread2 thread2 = new Thread2();
thread2.setName("bbb");
thread2.start();
}
}
3、join()线程完全阻塞
在线程a中调用线程b的join()方法,此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态,继续执行。
代码如下:
public class ThreadjoinTest {
static class Thread1 extends Thread{
Thread2 thread2;
public Thread1(Thread2 thread2) {
this.thread2 = thread2;
}
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
//当i == 20 时,开始执行线程thread2的程序,直到结束后,才继续执行当前线程
//join:线程实例的方法join()方法可以使得一个线程在另一个线程结束后再执行。
//如果join()方法在一个线程实例上调用,当前运行着的线程将阻塞直到这个线程实例完成了执行。
if (i == 20){
try {
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread1.currentThread().getName()+"--->"+i);
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread2.currentThread().getName()+"--->"+i);
}
}
}
public static void main(String[] args) {
Thread2 thread2 = new Thread2();
thread2.setName("bbb");
Thread1 thread1 = new Thread1(thread2);
thread1.setName("aaa");
thread1.start();
thread2.start();
}
}
结果如下:
4、wait()线程等待
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
- wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。
- wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
否则,会出现IllegalMonitorStateException异常 - wait(),notify(),notifyAll()三个方法是定义在java.lang.Object类中。
代码如下:
//使用两个线程打印 1-100。线程1, 线程2 交替打印
class Number implements Runnable{
private int number = 1;
private Object obj = new Object();
@Override
public void run() {
while(true){
/*执行流程为
* 1.假设线程1获得CPU执行权,进入run方法,进入while循环,同时拿到线程锁。
* 2.判断是否在100内,在的话,打印线程1的名字加数字1,同时数字加1
* 3.线程1调用wait方法进入阻塞状态
* 4.线程2进入run方法,进来就使用notify方法唤醒线程1,线程1就跑出该循环,重新等待CPU的调度
* 5.因为线程2拿到了同步监视器锁,所以,线程1不可以进来。这样就保证了线程安全
* 6.线程2同线程1一样继续执行,然后wait堵塞
* 7.线程1进入,同样唤醒线程2,一直循环直到i==100,完成需求。
* */
synchronized (obj) {
//使用notify来唤醒线程等待的线程
obj.notify();
if(number <= 100){
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得调用如下wait()方法的线程进入阻塞状态
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
}
public class ThreadwaitTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
三、线程生命周期中相关方法区别
1、yield() 和 join()的异同
- 相同点:一旦执行方法,都可以使得放弃当前的线程。
- 不同点:
1)yield()是会释放当前CPU的执行权,让其他优先级相同或者高于其优先级的线程运行,即也有可能自己又会抢到CPU的执行权。
2)join()则直接阻塞当前线程,等待其他线程运行结束才继续执行。
2、sleep() 和 wait()的异同
- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
- 不同点:
1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。