为了支持多线程之间的协做,JDK提供了两个很是重要的接口线程等待wait()方法和通知notify()方法。这两个方法并不在Thread类中,而是输出Object类。这也就意味着任何对象均可以调用这两个方法。java
public final void wait() throws InterruptedException {wait(0);} public final native void notify();
当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。这是什么意思呢?好比线程A中,调用了obj.wait()方法,那么线程A就会中止继续执行,而转为等待状态。等待到什么时候结束呢?线程A会一直等到其余线程调用了obj.notify()方法为止。这时obj对象就俨然成为多个线程之间的有效通信手段。多线程
这里还须要强调一点,Object.wait()方法并非能够随便调用。它必须包含在对应的synchronzied语句中,而且都须要先得到目标对象的一个监视器。显示了wait()与notify()的工做流程细节。其中T1和T2表示两个线程。T1在正确执行wait()方法前,首先必须得到object对象的监视器,执行后会释放这个监视器。为了方便你们理解,这里给出一个简单的例子:ide
public class ThreadTest3 { final static Object obj = new Object(); public static class T1 extends Thread{ public void run(){ synchronized (obj){ System.out.println(System.currentTimeMillis()+":T1 Start!"); try { System.out.println(System.currentTimeMillis()+":T1 wait for object"); obj.wait(); }catch (Exception e){ e.printStackTrace(); } System.out.println(System.currentTimeMillis()+":T1 end"); } } } public static class T2 extends Thread{ public void run(){ synchronized (obj){ System.out.println(System.currentTimeMillis()+":T2 Start!"); System.out.println(System.currentTimeMillis()+":T2 wait for object"); obj.notify(); System.out.println(System.currentTimeMillis()+":T2 end"); try { Thread.sleep(20000); }catch (Exception e){ e.printStackTrace(); } } } } public static void main(String[] args) { Thread t1 = new T1(); Thread t2 = new T2(); t1.start(); t2.start(); } }
执行结果以下所示:spa
1496717793778:T1 Start! 1496717793778:T1 wait for object 1496717793779:T2 Start! 1496717793779:T2 wait for object 1496717793779:T2 end 1496717813779:T1 end
从程序打印的时间能够到出,在T2通知T1继续执行后,T1并不能当即继续执行,而是要等待T2释放obj的锁,并从新成功获取锁后,才能继续执行。所以T2休息了20秒。线程
注意:object.wait()和Thread.sleep()方法均可以让线程等待若干时间,除了wait()能够被唤醒外,另一个主要区别就是wait()方法会释放目标对象锁,而是Thread.sleep()方法不会释听任何资源。接下来要介绍code
的suspend也不会释听任何锁资源。对象
不推荐使用suspend()去挂起线程的缘由,是由于suspend()在致使县城暂停的同时,并不会释听任何锁资源。此时,其余线程想要访问被它暂用的锁时,都会没法正常继续运行。直到对应的线程进行了resume()操做,被挂起的线程才能继续执行。在此不进行详细描述。接口
隔壁的一时候,一个线程的输入可能很是依赖于另一个或是多个线程的输出,此时,这个线程就须要等待依赖线程执行完毕,才能继续执行。JDK提供了join()操做来实现这个功能,以下资源
//此方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。 public final void join() throws InterruptedException {join(0);} //该方法给出一个最大等待时间,若是超过给定时间目标线程还在执行,当前线程也会由于“等不及”而继续往下执行。 public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
给出一个简单点join()实例,给你们参考虚拟机
public class ThreadTestJoin { public volatile static int i=0; public static class addThread extends Thread{ public void run(){ for (i = 0; i < 1000000; i++); } } public static void main(String[] args) throws InterruptedException { addThread thread = new addThread(); thread.start(); System.out.println("等待前的:i"+i); thread.join(); System.out.println("等待后的:i"+i); } }
0 1000000
从上能够看出,join()没有等待addThread,i极可能是0或是一个很是小的数字。由于addThread尚未开始执行i的值就已经被输出了。可是使用join()方法后,标识主线程愿意等待addThread执行完毕,故join()返回時,addThread已经执行完毕,故i老是1000000。
另一个比较有趣的方法yield(),定义以下:
public static native void yield();
这是一个静态方法,一旦执行,他会使当前线程让出CPU。但要注意,让出CPU并不表示当前线程不执行。当前线程在让出CPU后,还会进行CPU资源的争夺。可是是否可以再次被分配到,就不必定了。所以对yield()的调用就好像在说:我已经完成一些重要的工做了,我应该是能够休息一下了,能够给其余线程一些工做机会啦。若是你以为一个线程不那么重要,或者优先级很是低,并且又惧怕它占用太多的CPU资源,这时你能够在适当时候调用yield()方法,给予其余重要线程更多的工做机会。
上一个例子有用到volatile关键字,volatile英文解释为"异变的,不稳定的"这也正是这个关键词的语义。当你用volatile去申明一个变量时,就等于你告诉了虚拟机,这个变量极有可能会被某些程序或线程所修改。为了确保修改后的变量可让全部线程都可以“看到”这个改动,虚拟机就必须采用一些特殊的手段来保证这个变量的可见性等特色。
public class DiscoveryApplicaion { public static volatile int i = 0; static class Plustask implements Runnable{ @Override public void run() { for (int k = 0; k < 1000; k++) { i++; } } } public static void main(String[] args) throws InterruptedException { Thread[] task = new Thread[10]; for (int j = 0; j <task.length ; j++) { task[j] = new Thread(new Plustask()); task[j].start(); task[j].join(); } // for (int j = 0; j < task.length; j++) { // task[j].join(); // } System.out.println(i); } }
执行上述代码,若是i++是原子性的,那么最终的值应该是10000(10个线程各累加1000次)。但实际上放开注释输出老是会小于10000。