线程间的调度顺序

目录

♫join和sleep

♫wait

♫notify和notifyAll


我们知道线程是抢占式执行,随机调度的,而这也是诱发线程安全的根本原因。我们虽然无法指定线程之间的调度顺序,但是可以通过JVM提供的一些API让某个线程阻塞,主动放弃CPU,给其他线程让路,从而间接调整线程间的调度顺序。

♫join和sleep

我们已经知道通过join可以让一个线程等待令另一个线程执行完毕/一定时间,sleep可以让线程阻塞指定时间:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                //让t1线程休眠100毫秒
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("t1线程执行完毕");
        });
        t1.start();
        //主线程等待t1执行完毕(超过500毫秒不再等待)
        t1.join(500);
        System.out.println("主线程执行完毕");
    }
}

运行结果:

 但使用joinsleep无法精确控制线程执行到哪行代码再进入阻塞状态,使用waitnotify/notify就可以更精确地控制线程进入阻塞状态的时间。

wait

wait可以让一个线程暂停执行,等待另一个线程调用notify或notifyAll唤醒它。wait的执行顺序是先释放锁→阻塞等待→收到通知后重写尝试获取锁调,如果没有锁就使用wait运行时会报错:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object o1 = new Object();
        o1.wait();
    }
}

运行结果:

非法的监视器(synchronized)状态异常,故wait得先获取锁后使用:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object o1 = new Object();
        synchronized (o1) {
            System.out.println("wait前");
            o1.wait();
            System.out.println("wait后");
        }
    }
}

运行结果:

此时,wait就进入阻塞状态,等待notify或notifyAll唤醒它。

注:wait释放锁,进入阻塞状态后,其它线程是可以获取到o1对象的锁的

使用wait时还可以设定等待通知的时间,避免死等:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object o1 = new Object();
        synchronized (o1) {
            System.out.println("wait前");
            //500毫秒后仍没有通知则重新尝试获取锁
            o1.wait(500);
            System.out.println("wait后");
        }
    }
}

运行结果:

此时,主线程只会等待通知500毫秒,500毫秒后不需要notify或notifyAll就可以重新尝试获取锁。

注:虽然wait(500)与sleep(500)很像,但wait(500)通过notify或notifyAll唤醒不会抛出异常,属于正常的代码,而sleep(500)虽然能通过interrupt唤醒,但是却是以异常的形式唤醒的,属于出问题的代码。

♫notify和notifyAll

notify用来唤醒wait等待的线程,使用notify同样得先获取锁,且使用notify的对象需要和使用wait的对象一致:

public class Test {
    public static void main(String[] args) {
        Object o1 = new Object();
        Thread t1 = new Thread(()->{
            synchronized (o1) {
                System.out.println("wait前");
                try {
                    o1.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait后");
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (o1) {
                o1.notify();
            }
        });

        t1.start();
        t2.start();
    }
}

运行结果:

线程间的调度顺序_第1张图片

此处,先执行t1线程,t1掉用wait方法阻塞后执行t2,t2调用notify方法后唤醒t1线程。但由于线程的随机调度,并不能保证t1线程比t2线程先执行,如果是t2线程先执行的话,notify先执行,那么notify相当于是无效通知,等到t1线程调用wait方法后就没有对应的notify唤醒了。

注:在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

若有多个线程在等待o1对象,o1.notify则是随机唤醒一个等待的线程:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object o1 = new Object();
        Thread t1 = new Thread(()->{
            synchronized (o1) {
                try {
                    System.out.println("t1 wait前");
                    o1.wait();
                    System.out.println("t1 wait后");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (o1) {
                try {
                    System.out.println("t2 wait前");
                    o1.wait();
                    System.out.println("t2 wait后");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        t2.start();
        //由于线程进入wait的等待状态会释放锁,故t1和t2都会一起进入wait的等待状态
        //让t1和t2都进入wait的等待状态
        Thread.sleep(100);
        synchronized (o1) {
            o1.notify();
        }
    }
}

运行结果:

使用o1.notifyAll则是唤醒所有等待的线程:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object o1 = new Object();
        Thread t1 = new Thread(()->{
            synchronized (o1) {
                try {
                    System.out.println("t1 wait前");
                    o1.wait();
                    System.out.println("t1 wait后");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (o1) {
                try {
                    System.out.println("t2 wait前");
                    o1.wait();
                    System.out.println("t2 wait后");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        t2.start();
        //由于线程进入wait的等待状态会释放锁,故t1和t2都会一起进入wait的等待状态
        //让t1和t2都进入wait的等待状态
        Thread.sleep(100);
        synchronized (o1) {
            o1.notifyAll();
        }
    }
}

运行结果:

线程间的调度顺序_第2张图片

调用interrupt方法也可以导致 wait 抛出 InterruptedException 异常而终止等待:

public class Test {
    public static void main(String[] args) {
        Object o1 = new Object();
        Thread t1 = new Thread(()->{
            synchronized (o1) {
                try {
                    System.out.println("wait前");
                    o1.wait();
                    System.out.println("wait后");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t1.interrupt();
    }
}

运行结果:

线程间的调度顺序_第3张图片

♫例子 

 下面我们通过使用wait和notify实现三个线程顺序打印ABC(每个线程打印一个字母):

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Object o1 = new Object();
        Object o2 = new Object();
        Thread t1 = new Thread(()->{
            System.out.println("A");
            synchronized (o1) {
                o1.notify();
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (o1) {
                try {
                    o1.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("B");
            synchronized (o2) {
                o2.notify();
            }
        });
        Thread t3 = new Thread(()->{
            synchronized (o2) {
                try {
                    o2.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("C");
        });
        t2.start();
        t3.start();
        Thread.sleep(100);
        t1.start();
    }
}

运行结果:

线程间的调度顺序_第4张图片

o1对象确保t1线程打印A在T1线程打印B之前,o2对象确保t2线程打印B在t3线程打印C之前,sleep(100)是为了尽可能让线程先wait后notify,以避免无效通知最终导致程序僵住了。

你可能感兴趣的:(java,开发语言)