• Java多线程 wait, notify 和 notifyAll


    Java的Object类

     public class Object {
            public final native void notify();
    
            public final native void notifyAll();
    
            public final native void wait(long timeout) throws InterruptedException;
        }

    调用这些方法的当前线程必须拥有此对象监视器,否则将会报java.lang.IllegalMonitorStateException exception

    wait;

    Object的wait方法有三个重载方法,其中一个方法wait() 是无限期(一直)等待,直到其它线程调用notify或notifyAll方法唤醒当前的线程;另外两个方法wait(long timeout) 和wait(long timeout, int nanos)允许传入 当前线程在被唤醒之前需要等待的时间,timeout为毫秒数,nanos为纳秒数。

    wait():释放占有的对象锁,线程进入等待池,释放cpu,而其他正在等待的线程即可抢占此锁,获得锁的线程即可运行程序。

    而sleep()不同的是,线程调用此方法后,会休眠一段时间,休眠期间,会暂时释放cpu,但并不释放对象锁。也就是说,在休眠期间,其他线程依然无法进入此代码内部。休眠结束,线程重新获得cpu,执行代码。

    wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会!

    notify:

    notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。

    调用notify()后,并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM则会在等待的线程中调度一个线程去获得对象锁,执行代码。

    需要注意的是,wait()和notify()必须在synchronized代码块中调用

    所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。

    notifyAll:

    notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。

    这些方法可以使用于“生产者-消费者”问题,消费者是在队列中等待对象的线程,生产者是在队列中释放对象并通知其他线程的线程。

    demo:

    public class Main {
        public static void main(String[] args) {
            Message msg = new Message("process it");
            Waiter waiter = new Waiter(msg);
            new Thread(waiter,"waiterThread").start();
    
            Waiter waiter1 = new Waiter(msg);
            new Thread(waiter1, "waiter1Thread").start();
    
            Notifier notifier = new Notifier(msg);
            new Thread(notifier, "notifierThread").start();
            System.out.println("All the threads are started");
        }
    }
    class Message {
        private String msg;
        public Message(String str){
            this.msg=str;
        }
        public String getMsg() {
            return msg;
        }
        public void setMsg(String str) {
            this.msg=str;
        }
    }
    
    class Waiter implements Runnable{
        private Message msg;
        public Waiter(Message m){
            this.msg=m;
        }
    
        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            synchronized (msg) {
                try{
                    System.out.println(name+" waiting to get notified at time:"+System.currentTimeMillis());
                    msg.wait();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(name+" waiter thread got notified at time:"+System.currentTimeMillis());
                //process the message now
                System.out.println(name+" processed: "+msg.getMsg());
            }
        }
    }
    
    class Notifier implements Runnable {
        private Message msg;
    
        public Notifier(Message msg) {
            this.msg = msg;
        }
    
        @Override
        public void run() {
            String name = Thread.currentThread().getName();
            System.out.println(name+" started");
            try {
                Thread.sleep(1000);
                synchronized (msg) {
                    msg.setMsg(name+" Notifier work done");
                    msg.notify();
                    // msg.notifyAll();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    All the threads are started
    waiter1Thread waiting to get notified at time:1480592377675
    waiterThread waiting to get notified at time:1480592377677
    notifierThread started
    waiter1Thread waiter thread got notified at time:1480592378685
    waiter1Thread processed: notifierThread Notifier work done
    View Code

    Notifier中的run方法改成msg.notifyAll();

    Result:

    All the threads are started
    waiter1Thread waiting to get notified at time:1480592533780
    waiterThread waiting to get notified at time:1480592533780
    notifierThread started
    waiterThread waiter thread got notified at time:1480592534782
    waiterThread processed: notifierThread Notifier work done
    waiter1Thread waiter thread got notified at time:1480592534782
    waiter1Thread processed: notifierThread Notifier work done
    View Code

    一个通知线程,3个等待线程:

    public class Main {
        private String flag[] = { "true" };
        public static void main(String[] args) {
            System.out.println("Main Thread Run!");
            Main test = new Main();
            System.nanoTime();
            NotifyThread notifyThread =test.new NotifyThread("notify01");
            WaitThread waitThread01 = test.new WaitThread("waiter01");
            WaitThread waitThread02 = test.new WaitThread("waiter02");
            WaitThread waitThread03 = test.new WaitThread("waiter03");
            notifyThread.start();
            waitThread01.start();
            waitThread02.start();
            waitThread03.start();
        }
    
        class NotifyThread extends Thread{
            public NotifyThread(String name) {
                super(name);
            }
            public void run() {
                try {
                    sleep(3000);//推迟3秒钟通知
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (flag) {
                    flag[0] = "false";
                    flag.notifyAll();
                }
            }
        }
    
        class WaitThread extends Thread {
            public WaitThread(String name) {
                super(name);
            }
    
            public void run() {
                synchronized (flag) {
                    while (flag[0] != "false") {
                        System.out.println(getName() + " begin waiting!");
                        long waitTime = System.currentTimeMillis();
                        try {
                            flag.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        waitTime = System.currentTimeMillis() - waitTime;
                        System.out.println("wait time :" + waitTime);
                    }
                    System.out.println(getName() + " end waiting!");
                }
            }
        }
    }
    Main Thread Run!
    waiter01 begin waiting!
    waiter02 begin waiting!
    waiter03 begin waiting!
    wait time :2999
    waiter03 end waiting!
    wait time :2999
    waiter02 end waiting!
    wait time :2999
    waiter01 end waiting!
    View Code

    =====================================================================================

    wait和sleep的demo:

    public class Main {
        public static void main(String[] args) {
            new Thread(new Thread1()).start();
            try {
                Thread.sleep(5000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            new Thread(new Thread2()).start();
        }
    
        private static class Thread1 implements Runnable{
            @Override
            public void run(){
                synchronized (Main.class) {
                    System.out.println("enter thread1...");
                    System.out.println("thread1 is waiting...");
                    try {
                        //调用wait()方法,线程会放弃对象锁,进入等待此对象的等待锁定池
                        Main.class.wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("thread1 is going on ....");
                    System.out.println("thread1 is over!!!");
                }
            }
        }
        private static class Thread2 implements Runnable{
            @Override
            public void run(){
                synchronized (Main.class) {
                    System.out.println("enter thread2....");
                    System.out.println("thread2 is sleep....");
                    //只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
                    Main.class.notify();
                    //==================
                    //区别
                    //如果我们把代码:Main.class.notify();给注释掉,即Main.class调用了wait()方法,但是没有调用notify()
                    //方法,则线程永远处于挂起状态。
                    try {
                        //sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,
                        //但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
                        //在调用sleep()方法的过程中,线程不会释放对象锁。
                        Thread.sleep(5000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("thread2 is going on....");
                    System.out.println("thread2 is over!!!");
                }
            }
        }
    }
    enter thread1...
    thread1 is waiting...
    enter thread2....
    thread2 is sleep....
    thread2 is going on....
    thread2 is over!!!
    thread1 is going on ....
    thread1 is over!!!
    View Code

    wait,notify的使用,线程同步唤醒

    package com.qhong;
    
    /**
     * Created by Administrator on 2017/7/4 0004.
     */
    public class MyThreadPrinter2 implements Runnable {
    
        private String name;
        private Object prev;
        private Object self;
    
        private MyThreadPrinter2(String name, Object prev, Object self) {
            this.name = name;
            this.prev = prev;
            this.self = self;
        }
    
        @Override
        public void run() {
            int count = 10;
            while (count > 0) {
                synchronized (prev) {
                    synchronized (self) {
                        System.out.print(name);
                        count--;
    
                         self.notify();
                    }
                    try {
                        prev.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
            }
        }
    
        public static void main(String[] args) throws Exception {
            Object a = new Object();
            Object b = new Object();
            Object c = new Object();
            MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
            MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
            MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
    
    
            new Thread(pa).start();
            Thread.sleep(1000);  //确保按顺序A、B、C执行
            new Thread(pb).start();
            Thread.sleep(1000);
            new Thread(pc).start();
            Thread.sleep(1000);
        }
    }

    Output:

    ABCABCABCABCABCABCABCABCABCABC
    View Code

    Thread.sleep(100)是保证ABC的执行顺序

    在这个例子中,可以深刻的体现出wait不仅是阻塞线程,更体现wait释放锁对象。

    比如是4轮,第一轮的abc是有Thread.sleep来控制的,这个时候只有锁对象c是被释放的,锁对象a阻塞线程B,锁对象b阻塞线程C

                       第二轮,a的prev是c,所以是可以运行的,并且在这一步释放锁对象a,锁对象c注释线程A

                       ................

    就这样一轮一轮的控制,虽然想通了,但是让我写还是想不出来的,挺繁琐!

    有是一个demo

    package com.qhong.thread;
    
    
    public class MyWaitNotify{
    
        MonitorObject myMonitorObject = new MonitorObject();
        boolean wasSignalled = false;
    
        public void doWait(){
            synchronized(myMonitorObject){
                while(!wasSignalled){
                    try{
                        myMonitorObject.wait();
                    } catch(InterruptedException e){...}
                }
                //clear signal and continue running.
                wasSignalled = false;
            }
        }
    
        public void doNotify(){
            synchronized(myMonitorObject){
                wasSignalled = true;
                myMonitorObject.notify();
            }
        }
    }
    
    class MonitorObject{}

    代码比较简单,但是要注意下面的说明,有好几个概念以前没注意:

    1、不管是等待线程还是唤醒线程都在同步块里调用wait()和notify()。这是强制性的!一个线程如果没有持有对象锁,将不能调用wait(),notify()或者notifyAll()。否则,会抛出IllegalMonitorStateException异常。

    2、一旦线程调用了wait()方法,它就释放了所持有的监视器对象上的锁。这将允许其他线程也可以调用wait()或者notify()。

    3、为了避免丢失信号,必须把它们保存在信号类里。如上面的wasSignalled变量。

    4、假唤醒:由于莫名其妙的原因,线程有可能在没有调用过notify()和notifyAll()的情况下醒来。这就是所谓的假唤醒(spurious wakeups)。为了防止假唤醒,保存信号的成员变量将在一个while循环里接受检查,而不是在if表达式里。这样的一个while循环叫做自旋锁。

    5、不要在字符串常量或全局对象中调用wait()。即上面MonitorObject不能是字符串常量或是全局对象。每一个MyWaitNotify的实例都拥有一个属于自己的监视器对象,而不是在空字符串上调用wait()/notify()。


    http://www.cnphp6.com/archives/62258

    http://blog.csdn.net/luoweifu/article/details/46664809

    http://developer.51cto.com/art/201508/487488.htm

    http://blog.csdn.net/a352193394/article/details/39381271

    http://blog.csdn.net/evankaka/article/details/44153709

  • 相关阅读:
    DataTable 只保留想要的几列
    如何用多个字符串来切分字符串
    用.net 发送邮件
    sqlserver 行列转换
    sql面试题一 学生成绩
    将DataReader转换为DataTable
    C# 如何用多个字符串来切分字符串并去除空格
    SqlServer按时间自动生成生成单据编号
    实验一 Java开发环境的熟悉
    Java学习笔记心得——初识Java
  • 原文地址:https://www.cnblogs.com/hongdada/p/6071027.html
Copyright © 2020-2023  润新知