jumprn
孩子不是海子
采纳率9.1%
2017-05-25 02:03 阅读 3.5k

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后运行程序多次都唤醒不了,这不正是说明唤醒跟其启动(启动的的时候会有个进入等待池也就是先进先出的顺序吗)

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享

3条回答 默认 最新

  • u011212394 KeepMoving++ 2018-09-21 14:32

    看了楼上的回复,发现了一点问题。将他的例子中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调度决定。

    点赞 5 评论 复制链接分享
  • zzt46245 双木有兮木有辛 2017-05-26 09:29

    这个程序,貌似不能用作 线程唤醒无序的例子,每个线程里有两个变量来控制是否能运行,一个是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);
    
    
        }    
    }
    
    点赞 3 评论 复制链接分享
  • gaoqida 一头大菜鸟 2019-10-08 17:17

    但是如果把代码改成
    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是出栈?

    点赞 评论 复制链接分享

相关推荐