wait()、sleep()以及yield()函数

来源:互联网 发布:安全网络格言 编辑:程序博客网 时间:2024/05/18 02:08

wait、sleep以及yield三个函数都可以实现任务的暂停执行,根据不同的出发点,调用不同的函数实现效果不同。

sleep静态函数

public static void sleep(long millis) throws InterruptedException//指定毫秒数以及精确的纳秒(第二个参数为int类型,因为纳秒数范围[0-999999])public static void sleep(long millis,int nanos) throws InterruptedException
常用为第一种,因为可能抛出中断检查异常,所以需要在调用函数的方法上声明可能抛出的异常,或者直接使用try–catch形式,sleep函数不是同步方法。所以不存在占据或释放资源的问题,会释放CPU。在等待指定时间后会发起对CPU的竞争(因为存在的线程调度,所以当前执行sleep函数的线程并不一定在休眠等待参数指定时间后就能开始运行),相当于在等待结束后,线程处于Runnable状态,获得CPU即可转为Running状态。

yield静态函数

public static void yield()
作为一个无参方法,它的主要作用就是出让当前线程的CPU,使当前线程状态由Running状态转为Runnable状态,重新参与CPU的竞争。目的是当CPU资源很少,或者极端只有一个CPU的情况下,当前任务的执行需要依赖其他任务的结果,就是执行线程之间存在依赖关系的时候,在时间片未使用完的情况下主动出让CPU增加其他任务执行的可能性,提高响应性避免活跃性问题。
在执行阻塞操作时,例如一个自定义的并发安全队列,当队列为空时执行一个取数操作时,因为不确定队列的状态发生变化的时间,如果使当前线程自旋(即重复调用,避免进入阻塞状态),如果队列的状态在一段时间内不发生变化,则会极大耗费CPU资源,如果直接使当前线程进入阻塞状态,那么休眠时间内队列的状态可能发生变化,会降低任务的响应性以及活跃性。此时可以调用yield函数,出让CPU,加快整体执行速度。(当然后面可以直接使用wait、notify唤醒机制) 。
因为java的优化越来越强,所以在不断提高并发安全性以及效率的同时,也给并发安全的测试提出了很大的难题,在API中提到yield函数可以作为测试过程中的使用函数,来检测并发过程中的概率较低的较难检测的问题。
API中对yield函数的描述:

It is rarely appropriate to use this method. It may be useful for debugging or testing purposes, where it may help to reproduce bugs due to race conditions. It may also be useful when designing concurrency control constructs such as the ones in the java.util.concurrent.locks package.

在刷新系统的作用上,sleep函数有一种实现,即sleep(0);因为sleep的调用相当于释放CPU,等待一定时间进入Runnable状态,然后进行线程调度,所以yield和sleep(0)都可以起到相同的作用,而且二者都是线程类静态f方法。

wait同步方法

public final void wait() throws InterruptedExceptionpublic final void wait(long timeout) throws InterruptedExceptionpublic final void wait(long timeout,int nanos) throws InterruptedException
wait方法属于同步方法,需要在获得锁的情况下调用,否则会抛出IllegalMonitorStateException,而且wait方法本身可能抛出中断异常,所以一般放在同步方法或同步代码块内部,并且方法需要声明可能抛出异常或者在内部利用try--catch形式。wait方法可以指定等待时间,可响应中断。当前线程调用wait方法会释放当前线程占有的对象上的锁以及CPU,当其他线程调用对象上的notify或notifyAll方法时,唤醒线程参与锁的竞争(因为当前线程不一定成功竞争得到对象的锁,所以wait函数一般放在while循环判断某个条件中,所有调用wait的等待线程在竞争锁之后都会判断自己的条件是否满足,而只有成功获得锁的线程才能跳出循环继续执行,避免因为条件不满足而在后续执行中出现异常),获得锁进入Runnable状态,参与CPU线程调度,获得CPU则进入Running状态,继续运行。

关于锁的概念

对象的内置锁和对象的状态并没有内在的关联,当某线程获得与对象关联的锁时,并不能阻止其他线程访问对象,只能阻止其他线程获得同一个锁。之所以每个对象都有一个内置锁,只是为了免去显式的创建锁对象。但是这种设计使得JVM必须在对象大小和加锁性能之间进行权衡wait方法通常与notify、notifyAll方法一起使用,通过notify来唤醒因为调用对象obj的wait方法而休眠的线程。需要注意就是在另一线程中调用obj对象的notify方法后,唤醒那些阻塞等待获取obj对象上的锁的诸多线程,而此时该对象的锁仍然在执行notify方法的代码段中,所以对于待唤醒的线程,只有在执行notify方法的代码段执行完毕,JVM自动释放锁之后,才能竞争锁。

示例:

public class t{      public static void main(String[] args) {            final Object obj=new Object();          new Thread(){              public void run(){                  synchronized(obj){                      try{                          System.out.println("subthread begin");                          obj.wait(1000);                          System.out.println("subthread:end");                      }catch(Exception e){                          e.printStackTrace();                      }                  }                }          }.start();          try{              Thread.sleep(100);            }catch(Exception e){              e.printStackTrace();          }          synchronized(obj){              try{                      System.out.println("main:begin");                     obj.notify();                  Thread.sleep(3000);              }catch(Exception e){                  e.printStackTrace();              }          }        }  }
在主线程释放obj上的锁之前,即使子线程的wait已经超时,仍然处于阻塞等待获取锁状态。
(此处可以看到关于对象内置锁的一个特性,锁的获取操作与释放操作都处于同一个代码块中)

总结

这三个函数都可以使得当前线程暂停,sleep、wait函数可以通过参数指定线程大约的等待时间,yield方法则只是释放CPU并重新进行一次线程调度。sleep和yield方法属于线程类静态方法,wait方法作为同步方法需要在获取锁的条件下使用。




2 0
原创粉丝点击