java多线程之notify()的唤醒顺序

在网上看到的额都说notify()的唤醒顺序是随机的,可是自己做的一个实验显示并不是如此:package adad;

public class MyThreadFactory {

// 线程A是否处于等待状态的标志  
 private boolean isThreadAWaiting;  
    // 线程B是否处于等待状态的标志  
    private boolean isThreadBWaiting;  
    // 线程C是否处于等待状态的标志  
    private boolean isThreadCWaiting;  


    public MyThreadFactory() {  
        isThreadAWaiting = true;  
        isThreadBWaiting = true;  
        isThreadCWaiting = true;  
    }  

    /** 
     * 对象锁 
     */  
    private final Object object = new Object();  

    /** 
     * 该线程作为一个唤醒线程 
     */  
    public void startWakenThread() {  
        Thread t = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                synchronized (object) {  
                    System.out.println("唤醒线程开始执行...");  
                    // 首先释放线程A  
                    quitThreadA();  
                }  
            }  
        });  
        t.start();  
    }  

    /** 
     * 启动线程A 
     */  
    public void startThreadA() {  
        Thread t = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                synchronized (object) {  
                    System.out.println("线程A开始等待...");  
                    try {  
                        for (; ; ) {  
                            if (!isThreadAWaiting) break;  
                            object.wait();  
                        }  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    System.out.println("线程A结束...");  
                    // 线程A结束后,暂停2秒释放线程B  
                    try {  
                        Thread.sleep(2000);  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    quitThreadB();  
                }  
            }  
        });  
        t.start();  
    }  

    /** 
     * 启动线程B 
     */  
    public void startThreadB() {  
        Thread t = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                synchronized (object) {  
                    System.out.println("线程B开始等待...");  
                    try {  
                        for (; ; ) {  
                            if (!isThreadBWaiting) break;  
                            object.wait();  
                        }  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    System.out.println("线程B结束...");  
                    // 线程B结束后,暂停2秒释放线程C  
                    try {  
                        Thread.sleep(2000);  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    quitThreadC();  
                }  
            }  
        });  
        t.start();  
    }  

    /** 
     * 启动线程C 
     */  
    public void startThreadC() {  
        Thread t = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                synchronized (object) {  
                    System.out.println("线程C开始等待...");  
                    try {  
                        for (; ; ) {  
                            if (!isThreadCWaiting) break;  
                            object.wait();  
                        }  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    System.out.println("线程C结束...");  

                    try {  
                        Thread.sleep(1000);  
                    } catch (InterruptedException e) {  
                        e.printStackTrace();  
                    }  
                    System.out.println("所有线程执行完毕!");  
                }  
            }  
        });  
        t.start();  
    }  

    /** 
     * 线程A退出等待 
     */  
    private void quitThreadA() {  
        isThreadAWaiting = false;  
        object.notify();  
    }  

    /** 
     * 线程B退出等待 
     */  
    private void quitThreadB() {  
        isThreadBWaiting = false;  
        object.notify();  
    }  

    /** 
     * 线程C退出等待 
     */  
    private void quitThreadC() {  
        isThreadCWaiting = false;  
        object.notify();  
    }  
   public static void main(String[] args) {  
        MyThreadFactory factory = new MyThreadFactory();  
        factory.startThreadB();//这儿的启动顺序注意
        factory.startThreadA();  

        factory.startThreadC();  

        try {  
            Thread.sleep(3000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        factory.startWakenThread();  
    }  

}

在网上找的这个例子,刚开始启动顺序是ABC,我改成BAC后运行程序多次都唤醒不了,这不正是说明唤醒跟其启动(启动的的时候会有个进入等待池也就是先进先出的顺序吗)

这个程序,貌似不能用作 线程唤醒无序的例子,每个线程里有两个变量来控制是否能运行,一个是object对象锁,一个是标记变量。

你的唤醒函数的设置标记的顺序是 ABC,但是object的notify不能保证按照ABC唤醒。因此你这个程序实际运行起来应该是有问题的

换句话说,即使你用object.notify(); 唤醒了B。 但是因为标记状态没有被A设置,因此唤醒不了3个线程。

何况你虽然是顺序start三个线程,但不代表这三个线程就一定是按你调用的顺序启动的。 三个线程太少,看不出随机性。

我自己写了一个测试随机唤醒的程序。 结果发现虽然结果不是有规律,但是也不是完全没有规律。 粗看下一个规律是。前面几个休眠的线程会按照顺序唤醒,后面的线程则会倒序唤醒。 具体还需要研究下。

 import java.util.LinkedList;
import java.util.List;

public class ThreadRunSort {

    /** 
     * 对象锁 
     */  
    private final Object object = new Object();  
    private List<Integer> sleep = new LinkedList<>();
    private List<Integer> notify = new LinkedList<>();
    /** 
     * 该线程作为一个唤醒线程 
     */  
    public void startThread(int i) {  
        Thread t = new Thread(new Runnable() {  
            @Override  
            public void run() {  
                synchronized (object) {  
                    try {
                        System.out.println(Thread.currentThread().getName()+"进入休眠");
                        sleep.add(i);
                        object.wait();
                        System.out.println(Thread.currentThread().getName()+"线程已经唤醒");
                        notify.add(i);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }  
            }  
        });  
        t.setName("Thread"+i);
        t.start();  
    }  

    public static void main(String[] args) {  
        ThreadRunSort a = new ThreadRunSort();
        for(int i =1;i<22;i++){
            a.startThread(i);
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println();
        for(int i =1;i<22;i++){
            synchronized (a.object) {
                a.object.notify();
            }
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("休眠顺序"+a.sleep);
        System.out.println("唤醒顺序"+a.notify);


    }    
}

看了楼上的回复,发现了一点问题。将他的例子中notify部分改动了一下。

for(int i =1;i<50;i++){
            try {
                Thread.sleep(10);  // 在这里sleep确保notify会顺序执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (a.object) {
                a.object.notify();
            }
        }

最后执行结果:
休眠顺序[1, 4, 7, 6, 5, 2, 3, 10, 9, 13, 8, 12, 14, 11, 16, 15, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 25, 28, 30, 31, 29, 32, 34, 35, 36, 33, 38, 37, 39, 40, 41, 42, 43, 44, 45, 46, 48, 47, 49]
唤醒顺序[1, 4, 7, 6, 5, 2, 3, 10, 9, 13, 8, 12, 14, 11, 16, 15, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 25, 28, 30, 31, 29, 32, 34, 35, 36, 33, 38, 37, 39, 40, 41, 42, 43, 44, 45, 46, 48, 47, 49]

结果显示唤醒顺序与执行顺序是一样的(注意这里是强调唤醒顺序,而不是重新获得锁的顺序)。
wait后的线程确实是保存在一个FIFO的等待队列中。
为什么楼上的结果唤醒顺序与休眠顺序不一致?
因为他打印的不是唤醒顺序,而是唤醒后线程重新获得锁的顺序。
在循环中直接notify的话,这些等待的线程虽然是按顺序唤醒,但是间隔时间非常短,几乎会同时去竞争锁(这一点和在循环中start线程却不能保证线程执行的顺序是一个道理),因此在竞争下是无法保证获取锁的顺序的。

总结:wait后的线程会进入一个FIFO的队列中,notify是一个有序的出队列的过程。而短时间内多个线程竞争获得锁的顺序则是不确定的,这要靠cpu调度决定。

但是如果把代码改成
synchronized (a.object) {
a.object.notifyAll();
}
顺序又变成
休眠顺序[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
唤醒顺序[21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

难道notify是一个有序的出队列的过程,notifyAll是出栈?