• 13.3 线程的生命周期



    当线程并创建并启动后,它既不是一启动就进入执行状态,也不是一直处于执行状态,再次线程的生命周期中,他要经过新建(New)、就绪(Ready)、阻塞(Blocked)和死亡(Dead)5种状态。

    一、新建和就绪状态

    1.1 新建状态

    当程序使用new关键字创建了一个线程后,该线程就处于新建状态,此时它与其他Java对象一样,仅仅由Java虚拟机为其分配内存,并初始化其成员变量的值。此时线程对象没有表现出任何动态特征,程序也不会执行线程的线程执行体。

    1.2 就绪状态

    当程序调用start()方法后,该程序处于就绪状态,Java虚拟机会为其创建方法调用栈和程序计算器,处于这个状态中的线程并没有开始运行,只是表示该线程可以运行了。至于该线程何时开始运行,取决于JVM里线程调度器的调度。

    1.3 注意事项

    注意:启动线程使用start()方法,而不是run()方法!永远不要调用线程对象的run()方法!调用start()方法来启动线程,系统会把该run()方法当成线程执行体来处理;但如果直接用线程的run()方法,则run()方法立即会被执行,而且再run()方法返回之前其他线程无法并发执行——也就是说,如果直接调用run()放啊,系统把线程对象当成一个普通对象,而run()方法也是一个普通方法,而不是线程的执行体

    package section3;
    
    public class InvokeRun extends Thread
    {
        private int i;
        //重写run()方法
        public void run()
        {
            for(;i<100;i++)
            {
                //直接调用run()方法时,Thread的getName()返回的就是该对象的名字
                //而不是当前线程的名字
                //使用Thread.currentThread().getName()总是获取当前线程的名字
                System.out.println(Thread.currentThread().getName()+" "+i);
            }
        }
        public static void main(String[] args)
        {
            for(var i=0;i<100;i++)
            {
                System.out.println(Thread.currentThread().getName()+" "+i);
                if(i==20)
                {
                    //直接调用线程的run()方法,系统会把线程对象当成普通对象,把run()方法当成普通方法
                    //所以下面两个方法不会启动线程,而是依次执行两个Run()方法
                    new InvokeRun().run();
                    new InvokeRun().run();
                }
            }
        }
    }
    

    上面程序创建线程对象后直接调用线程run()方法,程序运行的结果是整个程序只有一个线程:主线程。如果直接调用线程的run()方法,则run()方法不能直接通过getName()方法来获取当前执行线程的名字,而是需要使用Thread.currentThread()方法先获取当前线程对象,再调用线程对象的getName()方法来获取当前线程的名字。
    调用线程的run()方法后,该线程已经不再处于新建状态,不要再次调用线程对象的start()的方法。
    调用线程对象的start()方法之后,该线程就会立即进入就绪状态——就绪状态就是“等待执行”,该线程并未真正进入运行状态。
    将上面的程序中的new InvokeRun().run();改为new InvokeRun().start();后重新运行上面的程序,可以看到以下输出:

    可以看出等到主线程的成员变量i=46后才启动子线程,这种切换由底层平台控制(具有一定随机性)。
    如果希望调用子线程的start()方法后子线程立即执行,程序可以使用Thread.sllep(1)来让当前运行的线程睡眠1毫秒——这1毫秒CPU不会空闲,他会去执行另一个处于就绪状态的的线程,这样可以让子线程立即开始运行。

    二、运行和阻塞状态

    2.1 运行状态

    如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,该线程处于运行状态,如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态。当然,在一个多处理器的计算机上,将会由多个线程并行执行;当线程数大于处理器数,依然会存在多个线程在同一个CPU上轮换的现象。
    当线程开始运行后,它不会一直处于运行状态,线程在运行的过程中需要被抢断,目的是使其他线程获得执行的机会。对于抢占式策略的系统而言,系统会给每一个线程一个先小时间段来处理任务;当该时间段用完,系统就会剥夺该线程所占的资源,让其他线程获得执行机会。在选择下一个线程时,系统会考虑线程的优先级。
    对于一些小型设备如手机可能采用协作式调度策略,在这样的系统中,只有当一个线程调用它的sleep()或yield()方法后才会放弃所占用的资源——也就是必须由线程主动放弃所占有的资源。

    2.2 阻塞状态

    如果发生以下情况时,线程将会进入阻塞状态
    1、线程调用sleep()方法主动放弃所占有的处理器资源。
    2、线程调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞。
    3、线程视图获取一个同步监听器,但该同步监听器正被其他线程所持有。
    4、线程在等待某个通知(notify)。
    5、程序调用线程的suspend()方法将该线程挂起。但该方法容易导致死锁,所以应该尽量避免使用该方法。
    当正在执行的线程被阻塞之后,其他线程就可以获得执行机会。被阻塞的线程就会再合适的时候重新进入就绪状态。也就是说,阻塞线程的阻塞接触之后,必须重新等待线程调度器再次调度它。
    正对上面的情况,当发生如下特点的情况可以解除上面的阻塞,让线程重新进入就绪状态。
    1、调用sleep()方法的线程经过了指定时间。
    2、线程调用阻塞式IO方法已返回。
    3、线程成功获取了视图取得的同步监听器
    4、线程正在等待某个通知,其他线程发了一个通知。
    5、处于挂起状态的线程被调用了resume()恢复方法。
    下图展示了线程状态的转换图:

    从上图可以看出,线程从阻塞状态只能进入就绪状态,无法直接进入运行状态。而就绪而运行状态之间的的装欢不受程序控制,而是由系统线程调度决定的,当处于就绪状态的线程获得了处理器资源时,该线程进入运行状态;当处于运行状态的线程失去处理器资源时,该线程进入就绪状态。但有一个方法例外,调用yield()放啊可以让运行状态的线程进入就绪状态。

    三、线程死亡

    3.1 线程结束的三种方式

    线程会以以下三种方式之一结束,一个结束后就处于死亡状态:
    1、run()方法执行完成,线程正常结束。
    2、线程抛出一个未捕获的 Exception或Error。
    3、直接调用该线程的stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用。
    注意:当主线程结束时,其他线程不受任何影响,并不会随之结束。。一旦子线程启动起来,它就拥有和主线程相同的地位,它不受主线程的影响。

    3.2 判断线程死亡的方法

    isAlive():当线程处于就绪、运行、阻塞三种状态时,该方法将返回ture;当线程处于新建、死亡两种状态时,该方法将返回false。

    3.3 代码举例

    package section3;
    
    public class StartDead extends Thread
    {
        private int i;
        public void run()
        {
            for (;i<100;i++)
            {
                System.out.println(getName()+" "+i);
            }
        }
        public static void main(String[] args)
        {
            //创建线程
            var sd=new StartDead();
            for(var i=0;i<100;i++)
            {
                //调用Thread的currentThread()方法获取当前线程
                System.out.println(Thread.currentThread().getName()+" "+i);
                if(i==20)
                {
                    //启动线程
                    sd.start();
                    //判断线程的isAlive()值,输出true
                    System.out.println(sd.isAlive());
                }
                //当线程处于新建、死亡两种状态时,isAlive()就返回false
                //当i大于20时,该线程已经启动过了,如果sd.Alive()返回false
                //那就是死亡状态
                if(i>20 &&!sd.isAlive())
                {
                    //视图再次启动线程
                    sd.start();
                }
            }
        }
    }
    

    运行上面的程序,在线程已经死亡的状态下再次调用start()方法来启动该线程,将会引发IllegalThreadStateException异常,这表明处于死亡状态的线程无法再次运行了。

  • 相关阅读:
    [No0000199]设计模式总结
    [No0000197]Windows用户都应该知道的运行命令
    [No000017F]如何监控注册表的修改
    [No0000196]一文读懂Java 11的ZGC为何如此高效
    [No0000195]NoSQL还是SQL?这一篇讲清楚
    [No0000194]聊聊 Chrome DevTools 中你可能不知道的调试技巧
    [No000018A]改善C#程序的建议11-20
    [No000018C]Vim清除上次的搜索高亮结果-Vim使用技巧(1)
    [No000018D]Vim快速注释/取消注释多行的几种方法-Vim使用技巧(2)
    [No000018E]Vim快速跳转任意行、任意列以及高亮显示当前行、当前列方法-Vim使用技巧(3)
  • 原文地址:https://www.cnblogs.com/weststar/p/12864175.html
Copyright © 2020-2023  润新知