一文搞定线程的生命周期

话不多说,直奔主题,看图:
在这里插入图片描述
线程的6个状态

  1. new:已经创建没有调用start方法启动,已经做好准备
  2. Runnable:调用start方法,可运行,即使在运行也是Runnable而不是running
  3. Blocked:monitor被其他线程占有,也就是被synchronized修饰
  4. Waiting:一般习惯而言,把Blocked(被阻塞)、Waiting(等待)、Timed Waiting(计时等待)、都称为阻塞状态,不仅仅是Blocked。
  5. Timed Waiting:记时等待
  6. Terminated:终止

注意
1.新创建只能往下走到可运行,而不能直接跳转到其他状态
2.线程生命周期不可回头:一旦到了可运行就不能回到新创建,一旦终止,就不能再有任何状态变化,所以一个线程只能有一次新创建和已终止。线程是不可以重复执行的,当它运行完了便会结束,一旦一个线程进入dead状态,它便不可以重新回到Runnable等状态,这个不可重复执行的性质和线程池一样的,如果我们还想执行该任务,可以选择重新创建一个线程,而原来的对象会被JVM回收

特殊情况:
1.如果发生异常,可以直接跳转到终止TERMINATED,不必再遵循路径。比如可以直接从Waiting到TERMINSTED
2.从Object.wait()刚被唤醒时,通常不能立即抢到monitor锁,那么会从waiting先进入blocked状态,抢到锁后再转换到runnable状态

Object类和线程先关的方法

/**
     *使当前线程等待,直到另外一个线程调用此对象的notify()方法或notifyAll()方法唤醒去继续获得cpu执行     
     *权,或者等待指定时间重新去继续获得cpu执行权,调用此方法会释放锁资源
     *
     * @param      millis   时间毫秒值
     * @throws  IllegalArgumentException      如果参数为负数
     *               
     * @throws  IllegalMonitorStateException 
     *              如果当前线程不是对象监视器的所有者。
     * @throws  InterruptedException 
     *             如果被其他线程打断报异常,而且清除中断状态
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    public final void wait(long millis) throws InterruptedException {
        wait(millis, 0);
    }
    

    /**
     * 引起当前线程等待,释放锁,直到另一个线程为此对象调用notify()方法或notifyAll()方法。线程继续设               
     * 法获得执行权
     * 
     * 此方法只能由锁的获得者的线程调用
     *
     * @throws  IllegalMonitorStateException 如果当前线程不是对象监视器的所有者。
     * @throws  InterruptedException 
     *               线程被其他线程打断抛出异常,并清除中断状态
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    @FastNative
    public final native void wait() throws InterruptedException;



/**
     * 随机唤醒等待集中某一个线程
     *
     * @throws  IllegalMonitorStateException 如果当前线程不是此对象监视器的所有者。
     * @see        java.lang.Object#notifyAll()
     * @see        java.lang.Object#wait()
     */
    @FastNative
    public final native void notify();




/**
     * 唤醒等待集中所有线程
     *
     *
     * @throws  IllegalMonitorStateException  如果当前线程不是此对象监视器的所有者。
     *           
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#wait()
     */
    @FastNative
    public final native void notifyAll();

Wait原理图:在这里插入图片描述
入口集Entry Set
等待集Wait Set

为什么wait必须在同步代码块中使用?
反过来思考如果不要求wait必须在同步代码块中调用,而是在外面调用会发生什么?
比如消费者和生产者模式,如果消费者判断没有数据,还没调用wait方法,而生产者已经生产而且已经调用notify方法,那么消费者就会无限等待下去,而synchronized能避免出现这种情况

Wait/notify、sleep异同:
相同:
阻塞:都可以让线程阻塞,对应线程状态是waiting或者Time——waiting
响应中断:都可以响应中断
不同
Wait方法的执行必须在被synchronized修饰的代码块内,而sleep不需要
在同步方法里面执行sleep方法时,不会释放monitor锁,但是,wait方法会释放锁
Sleep方法短暂的休眠之后会主动退出阻塞,而没有指定的时间的wait方法则需要被其他线程唤醒或者中断后才能退出阻塞
Wait()和notify(),notifyall()是object类的方法,sleep和yield方法Thread类中的方法

为什么线程通信的方法wait、notify、notifyall方法被定义在Object类中,而sleep方法被定义在Thread类中?

  1. 每个对象都可以上锁,由于wait、notify、notifyall方法都是锁级别的操作,所以把他们定义在Object类中,因为锁属于对象
  2. Java的每个对象中都有一个锁(monitor,也可以称为监视器)并且wait、notify等方法用于等待对象的锁或者通知其他线程对象的监视器可用,在java中并没有可供任何对象使用的锁和同步器,这就是为什么这些方法是Object类中的一部分,在java的每一个类都有用于线程间通信的基本方法
  3. 一个很明显的原因是java提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait方法就有意义了,如果wait()方法定义在Thread类中,线程正在等待哪个锁就不明显了
  4. 每个对象都拥有monitor(即锁),所以当前线程等待某个对象的锁,当然通过某个对象来操作了。而不是当前线程来操作,因为当前线程会等待多个线程的锁,如果对象通过线程来操作就非常复杂
  5. Wait和notify不仅仅是普通的方法和同步工具,更重要的是它们是java中两个线程之间的通信机制,对语言设计者而言,如果不能通过java关键字(例如sychronized)实现通信机制,同时要确保这个机制对每个对象可用,那么Object类则是正确的生声明位置。同步和等待通知是;两个不同的领域,不要看成相同和相关。同步时提供互斥并且和确保java类的线程安全,而wait和notify是两个线程之间的通信机制
  6. 在java中为了进入代码的临界区,线程需要锁定并等待锁定,他们不知道哪些线程持有锁,而只是知道锁被某个线程持有,并且他们应该等待取得锁,而不是去了解哪个线程在同步块内,并请求他们释放锁定

如何选择用notify和notifyall?
Notify可能导致信号丢失这样的正确性问题,而notifyall虽然效率不太高(把不需要唤醒的等待线程给唤醒了),但是其在正确性方面有保障。因此实现通知的一种比较流行的保守方法是优先使用notifyall以保障正确性,只有在证据表明使用notify足够的情况下才使用notify—
条件1:一次通知唤醒至多一个线程。在不同的等待线程可能使用不同的保护条件的情况下,notify唤醒的一个任意线程可能并不是我们需要唤醒的那一个线程,因此这个问题还需要通过满足条件二来排除
条件2:相应对象的等待集中仅包含同质等待线程。所谓同资等待线程指这些线程使用同一个保护条件,并且这些线程在wait调用返回之后的处理逻辑一致。最为典型的同资线程是使用同一个Runnable接口创建不同的线程,或者从同一个Thread子类New出的多个实例
注意:notify唤醒的是其所属对象的一个任意等待的线程,notify本身在唤醒线程是不考虑保护条件的。Notifyall方法唤醒的是其所属对象上的所有等待线程。使用notify替代notifyall时需要确保一下两个条件同时满足:
1.一次通知仅需要唤醒至多一个线程
2.相应的对象上的所有等待线程是同质等待线程

Join:
作用、用法:因为新的线程加入了我们,所以我们要等他执行完再出发
主线程等待副线程执行完毕,注意谁等谁
遇到中断:
Join期间,线程到底是什么状态?:waiting,不是Time-waiting因为join的时候无法预料实际等待时间是多少
注意点:
CountDownLatch或者CyclicBarrier类类似join的工具:
Join原理:
作用、用法:因为新的线程加入了我们,所以我们要等他执行完再出发
主线程等待副线程执行完毕,注意谁等谁
遇到中断:
Join期间,线程到底是什么状态?:waiting,不是Time-waiting因为join的时候无法预料实际等待时间是多少
注意点:
CountDownLatch或者CyclicBarrier类类似join的工具:
Join原理:
底层调用wait(0),无限等待下去,但是没有调用notify,每一个thread类run方法执行完毕后,自动notify

Yield:
作用:释放我的CPU时间片,线程状态依然是runnable,并不会释放自己的锁
定位:JVM不保证遵循,一般不适用,但是并发包里面经常使用
和sleep区别:sleep期间线程调度器不会去调度该线程,而yield方法时只是让线程释放出自己的cpu时间片,线程任然处于就绪状态,随时可能再次被调度

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章