线程的生命周期状态和Object中的wait( )方法

线程生命周期
NEW:一个已创建而未启动的线程处于该状态。一个线程只可能有一次处于该状态。
RUNNABLE:包括两个子状态:READY和RUNNING。前者表示处于该状态的线程可以被线程调度器(Scheduler)进行调度而使之处于RUNNING状态。后者表示处于该状态的线程正在运行,即相应线程对象的run方法所对应的指令正在由处理器执行。执行Thread.yield( )的线程,其状态可能会由RUNNING转换为READY。处于READY状态的子线程也被称为活跃线程。
BLOCKED:一个线程发起一个阻塞式I/O操作后,或者申请一个由其他线程持有的独占资源时,相应的线程会处于该状态。当阻塞式I/O操作完成后,或者线程获得了其申请的资源,该线程的状态又可以转换为RUNNABLE。
WAITING:等待其他线程执行。能够使其执行线程变更为WAITING状态的方法包括:Object.wait( )、Thread.join( )和LockSupport.park(Object)。能够使相应线程从WAITING变更为RUNNABLE的相应方法包括:Object.notify( )和LockSupport.unpark(Object)。
TIMED_WAITING:带有时间限制的等待状态。当其他线程没有在在指定时间内执行该线程所期望的特定操作时,该线程的状态自动转换为RUNNABLE。
TERMINATED:已经执行结束的线程处于该状态。一个线程也只能有一次处于该状态。Thread.run( )正常返回或者由于抛出异常而提前终止都会导致相应线程处于该状态。

WAITING

Object.wait( )的作用是使其执行线程被暂停(其生命周期状态变更为WAITING),该方法用来实现等待;Object.notify( )的作用是唤醒一个被暂停的线程,调用该方法可实现通知。相应地,Object.wait( )的执行线程被称为等待线程;Object.notify( )的执行线程被称为通知线程。

wait代码模版:

// 在调用wait方法前获得相应对象的内部锁
synchronized(someObject){
    while(保护条件不成立){
        // 调用Object.wait( )暂停当前线程
        someObject.wait();
    }
    // 代码执行到这里说明保护条件已经满足
    // 执行目标动作
    doAction( );
}

上述模版代码的方法被称为受保护方法(Guarded Method)。受保护方法包括3个要素:保护条件、暂停当前线程和目标动作。

同一个对象的同一个方法(someObject.wait( ))可以被多个线程执行,因此一个对象可能存在多个等待线程。

someObject.wait( )会以原子操作的方式使其执行线程(当前线程)暂停并使该线程释放其持有的someObject对应的内部锁。
当前线程被暂停的时候其对someObject.wait( )的调用并未返回

注意
  • 等待线程对保护条件的判断、Object.wait( )的调用总是应该放在相应对象所引导的临界区中的一个循环语句中。
  • 等待线程对保护条件的判断、Object.wait( )的执行以及目标动作的执行必须放在同一个对象(内部锁)所引导的临界区之中。
  • Object.wait( )暂停当前线程时释放的锁只是与该wait方法所属对象的内部锁。当前线程所持有的其他内部锁、显式锁并不会因此而被释放。

notify代码模版:

synchronized(someObject){
    // 更新等待线程的保护条件涉及的共享变量
    updateShareState( );
    // 唤醒其他线程
    someObject.notify( );
}

上述模版代码的方法被称为通知方法。通知方法包括2个要素:更新共享变量、唤醒其他线程。
由于一个线程只有在持有一个对象的内部锁的情况下才能够执行该对象的notify方法,因此Object.notify( )调用总是放在相应对象内部锁所引导的临界区之中。

Object.wait( )在暂停其执行线程的同时必须释放相应的内部锁;否则通知线程无法获得相应的内部锁,也就无法执行相应对象的notify方法来通知等待线程!

Object.notify( )的执行线程持有的相应对象的内部锁只有在Object.notify( )调用所在临界区代码执行结束后才会被释放,Object.notify( )本身并不会将这个内部锁释放,所以要尽可能的将Object.notify( )调用放在靠近临界区结束的地方。

等待线程和通知线程在其实现等待和通知的时候必须是调用同一个对象的wait( )方法、notify( )方法,而这两个方法都要求其执行线程必须持有该方法所属对象的内部锁,因此等待线程和通知线程是同步在同一对象之上的两种线程。

注意

等待线程和通知线程必须调用同一个对象的wait方法、notify方法来实现等待和通知。调用一个对象的notify方法锁唤醒的线程仅是该对象上的一个任意等待线程。notify方法调用应该尽可能地放在靠近临界区结束的地方。

Object.wait( )/notify( )的内部实现

知识点补充:Java虚拟机会为每个对象维护一个入口集(Entry Set)用于存储申请该对象内部锁的线程。此外,Java虚拟机还会为每个对象维护一个被称为等待集(Wait Set)的队列,该队列用于存储该对象上的等待线程。

public void wait( ){
   // 执行线程必须持有当前对象对应的内部锁 
   if(!Thread.holdsLock(this)){
        throws new IllegalMonitorStateException(); 
   }

   if(当前对象不在等待集中){
        // 将当前对象加入当前对象的等待集中
        addToWaitSet(Thread.currentThread());
   }

   atomic{// 原子操作开始
       // 释放当前对象的内部锁
       releaseLock(this);
       // 暂停当前线程
       block(Thread.currentThread);// 语句A
   }// 原子操作结束

   // 再次申请当前对象的内部锁
   acquireLock(this);// 语句B
   // 将当前线程从当前对象的等待集中移除
   removeFromWaitSet(Thread.currentThread());
   return;// 返回
}

等待线程在语句A被执行之后就被暂停了。被唤醒的线程在其占用处理器继续运行的时候会继续执行其暂停前调用的Object.wait( )中的其他指令,即从上述代码中的语句B开始执行:先再次申请Object.wait( )所属对象的内部锁,接着将当前线程从相应的等待集中移除,然后Object.wait( )调用才返回

代码实例:

public class SimpleWN {
    final static Object object = new Object();

    public static class T1 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + ":T1 start!");

                try {
                    System.out.println(System.currentTimeMillis() + ":T1 wait for object");
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ":T1 end!");
            }
        }
    }

    public static class T2 extends Thread{
        @Override
        public void run(){
            synchronized (object){
                System.out.println(System.currentTimeMillis()+":T2 start! notify one thread");
                object.notify();
                System.out.println(System.currentTimeMillis()+":T2 end!");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    public static class T3 extends Thread {
        @Override
        public void run() {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + ":T3 start!");

                try {
                    System.out.println(System.currentTimeMillis() + ":T3 wait for object");
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(System.currentTimeMillis() + ":T3 end!");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new T1();
        Thread t2 = new T2();
        Thread t3 = new T3();
        t1.start();
        Thread.sleep(1000);
        t3.start();
        t2.start();
    }
}
output:
1559184522923:T1 start!
1559184522923:T1 wait for object
1559184523925:T3 start!
1559184523926:T3 wait for object
1559184523926:T2 start! notify one thread
1559184523926:T2 end!
1559184525930:T1 end!
wait/notify遇到问题
  • 过早唤醒问题
  • 信号丢失问题
  • 欺骗唤醒问题
  • 上下文切换问题
注意

Object.notify( )唤醒的是其所属对象上的任意等待线程。Object.notify( )本身在唤醒线程时,是不考虑保护条件的。Object.notifyAll( )方法唤醒的是其所属对象上的所有等待线程。使用Object.notify( )替代Object.notifyAll( )时需要确保以下两个条件同时得以满足:

  • 一次通知仅需唤醒至多一个线程。
  • 相应对象上的所有等待线程都是同质等待线程。

同质等待线程指这些线程使用同一个保护条件,并且这些线程在Object.wait( )调用返回之后的处理逻辑一致。最为典型的同质线程是使用同一个Runnable接口实例创建的不同线程(实例)或者从同一个Thread子类的new出来的多个实例。

Thread.sleep( )和Object.wait( )的区别
  1. 每个对象都有一个内置锁来控制同步访问,Synchronized关键字可以和对象的锁交互,来实现同步方法或同步块。调用sleep()方法时正在执行的线程主动让出CPU(然后CPU就可以去执行其他任务),在sleep指定时间后CPU再回到该线程继续往下执行(注意:sleep方法只让出了CPU,而并不会释放同步资源锁!);wait()方法则是指当前线程让自己暂时退让出同步资源锁,以便其他正在等待该资源的线程得到该资源进而运行,只有调用了notify()方法,之前调用wait()的线程才会解除wait状态,可以去参与竞争同步资源锁,进而得到执行。(注意:notify的作用相当于叫醒睡着的人,而并不会给他分配任务,就是说notify只是让之前调用wait的线程有权利重新参与线程的调度);

  2. sleep()方法可以在任何地方使用;wait()方法则只能在同步方法或同步块中使用;

  3. sleep()是线程线程类(Thread)的方法,调用会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,调用会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才会进入锁池,不再次获得对象锁才会进入运行状态;

Thread.sleep( )和Object.wait( )的区别总结来源Blog:link

Thread.sleep(2000),2000ms后是否立即执行?

不一定,在未来的2000毫秒内,线程不想再参与到CPU竞争。那么2000毫秒过去之后,这时候也许另外一个线程正在使用CPU,那么这时候操作系统是不会重新分配CPU的,直到那个线程挂起或结束;即使这个时候恰巧轮到操作系统进行CPU 分配,那么当前线程也不一定就是总优先级最高的那个,CPU还是可能被其他线程抢占去。

Thread.sleep(0),是否有用?

Thread.sleep(0)的作用,就是触发操作系统立刻重新进行一次CPU竞争,重新计算优先级。竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。这也是我们在大循环里面经常会写一句Thread.sleep(0),因为这样就给了其他线程比如Paint线程获得CPU控制权的权利,这样界面就不会假死在哪里。

博文连接link

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值