java 多线程基础(三)

等待(wait)和通知(notify)

    为了支持多线程之间的协做,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)和继续执行(resume)

    不推荐使用suspend()去挂起线程的缘由,是由于suspend()在致使县城暂停的同时,并不会释听任何锁资源。此时,其余线程想要访问被它暂用的锁时,都会没法正常继续运行。直到对应的线程进行了resume()操做,被挂起的线程才能继续执行。在此不进行详细描述。接口

等待线程结束(join)和谦让(yield)

    隔壁的一时候,一个线程的输入可能很是依赖于另一个或是多个线程的输出,此时,这个线程就须要等待依赖线程执行完毕,才能继续执行。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英文解释为"异变的,不稳定的"这也正是这个关键词的语义。当你用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。

相关文章
相关标签/搜索