美文网首页
Java线程系列——Object类中线程相关方法

Java线程系列——Object类中线程相关方法

作者: 禺沫 | 来源:发表于2020-02-25 19:44 被阅读0次

    一、图解方法

    Thread和Object方法概览.png

    二、wait,notify,notifyAll方法详解

    1. 基本用法

    有时,我们想让一个线程或多个线程先去休息一下,等到我们后续需要,或者它的条件成熟的时候,再去唤醒它。这个就是wait, notify, notifyAll的作用了。一旦进入到了休息阶段,就进入了阻塞状态。线程执行wait方法,必须拥有这个对象的monitor锁。调用wait后,线程会进入阻塞,直到以下四种情况之一发生时,才会被唤醒:

    • 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程
    • 另一个线程调用这个对象的notifyAll()方法
    • 过了wait(long timeout)规定的超时时间,如果传入0就是永久等待
    • 线程自身调用了interrupt()

    notify只会唤醒一个线程,至于是哪个线程,取决于jvm的选择,java并没有做过多控制,notify和wait都需要在synchronized关键字里执行,在synchronized外执行,会抛出异常。一旦被唤醒,线程就不是等待的状态,会被重新调度。notifyAll会把所有等待的线程一次唤醒。至于哪一个线程会获得到锁,就看操作系统的调度了。

    下面的代码展示wait和notify的基本用法:证明运行wait方法会释放锁。

    public class Wait {
        public static Object object = new Object();
    
        static class Thread1 extends Thread {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "开始执行了");
                    try {
                        object.wait(); //会释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //被唤醒后会重新获得锁
                    System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");
                }
            }
        }
    
        static class Thread2 extends Thread {
            @Override
            public void run() {
              synchronized (object){
                  object.notify();
                  System.out.println("线程"+ Thread.currentThread().getName()+"调用了notify");
              }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread1 thread1 = new Thread1();
            Thread2 thread2 = new Thread2();
            thread1.start();
            Thread.sleep(200);
            thread2.start();
        }
    }
    

    运行结果:

    Thread-0开始执行了
    线程Thread-1调用了notify
    线程Thread-0获取到了锁

    下面的代码展示wait和notifyAll的基本用法:

    public class WaitNotifyAll implements Runnable {
    
        private static final Object resourceA = new Object();
        @Override
        public void run() {
            synchronized (resourceA) {
                System.out.println(Thread.currentThread().getName() + " got resourceA lock.");
                try {
                    System.out.println(Thread.currentThread().getName() + " waits to start.");
                    resourceA.wait();
                    System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Runnable runnable = new WaitNotifyAll();
            Thread threadA = new Thread(runnable);
            Thread threadB = new Thread(runnable);
    
            Thread threadC = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (resourceA){
                        resourceA.notifyAll();
                        System.out.println("ThreadC notified");
                    }
                }
            });
            threadA.start();
            threadB.start();
            Thread.sleep(200);
            threadC.start();
        }
    }
    

    运行结果如下:

    Thread-0 got resourceA lock.
    Thread-0 waits to start.
    Thread-1 got resourceA lock.
    Thread-1 waits to start.
    ThreadC notified
    Thread-1's waiting to end.
    Thread-0's waiting to end.

    wait方法只会释放当前的锁:

    public class WaitNotifyReleaseOwnMonitor {
        private static volatile Object resourceA = new Object();
        private static volatile Object resourceB = new Object();
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (resourceA) {
                        System.out.println("ThreadA got resourceA lock.");
                        synchronized (resourceB) {
                            System.out.println("ThreadA got resourceB lock.");
                            try {
                                resourceA.wait();
                                System.out.println("ThreadA releases resourceA lock.");
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (resourceA){
                        System.out.println("ThreadB got resourceA lock.");
                        synchronized (resourceB){
                            System.out.println("ThreadB got resourceB lock.");
                        }
                    }
                }
            });
            thread1.start();
            thread2.start();
        }
    }
    

    运行结果:

    ThreadA got resourceA lock.
    ThreadA got resourceB lock.
    ThreadB got resourceA lock.

    2. 特点、性质
    • 必须先拥有monitor才是使用
    • 使用notify只能唤醒一个,但是唤醒哪一个需要操作系统决定
    • 都属于Object
    • 这些方法都是比较底层的实现,上层封装给我们的有类似的功能:Condition
    • 注意释放锁的顺序,避免发生死锁。

    三、手写生产者消费者设计模式

    1. 为什么需要生产者消费者模式?

    解决生产者和消费者相互等待,配合困难。把生产方和消费方解耦,降低配合难度。生产者看见队列满了,就不再生产,消费者看见队列空了,就不再先消费了。并且双方可以互相通知,生产者通知消费者,我已经生产完了。消费者通知生产者,我已经消费完了。

    生产者模式1.png

    生产者和消费者通常会用到一个容器,通常是一个阻塞队列来解决他们的耦合问题。生产者和消费者不直接通讯,生产者将内容放到队列里,消费者从缓冲区去取。这样就把他们的能力进行了平衡。不至于一方太多或太少。

    2. 具体代码演示
    public class ProducerConsumerModel {
        public static void main(String[] args) {
            EventStorage eventStorage = new EventStorage();
            Producer producer = new Producer(eventStorage);
            Consumer consumer = new Consumer(eventStorage);
            new Thread(producer).start();
            new Thread(consumer).start();
        }
    }
    
    class Consumer implements Runnable {
        private EventStorage storage;
        public Consumer(EventStorage storage) {
            this.storage = storage;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                storage.take();
            }
        }
    }
    
    class Producer implements Runnable {
        private EventStorage storage;
        public Producer(EventStorage storage) {
            this.storage = storage;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                storage.put();
            }
        }
    }
    
    class EventStorage {
        private int maxSize;
        private LinkedList<Date> storage;
    
        public EventStorage() {
            maxSize = 10;
            storage = new LinkedList<>();
        }
    
        public synchronized void put() {
            while (storage.size() == maxSize) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            storage.add(new Date());
            System.out.println("仓库里有了" + storage.size() + "个产品。");
            notify();
        }
    
        public synchronized void take() {
            while (storage.size() == 0) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("拿到了" + storage.poll() + "现在仓库还剩下:" + storage.size());
            notify();
        }
    }
    

    四、wait、notify的灵活使用

    1. 两个线程交替打印0~100的奇偶性
    public class WaitNotifyPrintOddEventWait {
        private static final Object lock = new Object();
        private static int count = 0;
        public static void main(String[] args) {
            new Thread(new TurningRunner()).start();
            new Thread(new TurningRunner()).start();
        }
        //1.一旦拿到锁就去打印
        //2.打印完,唤醒其他线程,自己就休眠
        static class TurningRunner implements Runnable {
            @Override
            public void run() {
                while (count <= 100) {
                    synchronized (lock) {
                        //拿到锁就打印
                      System.out.println(Thread.currentThread().getName() + ":" + count++);
                        lock.notify();
                        if(count <= 100){ //没有这句停不下来
                            try {
                                //如果任务还没结束,就让出当前的锁,并休眠
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    }
    

    五、常见问题:

    1. 为什么wait()需要在同步代码块内使用,而sleep()不需要?
      为了通信变得可靠,防止死锁或者永久等待的发生。比如要执行到wait(),线程切换到notify去执行,然后再切回到执行wait,逻辑就错了,容易发生永久等待,或死锁。所以把需要线程间互相配合的代码都放在同步代码块中了。而sleep是自己线程的,和其他线程没关系,所以不用放在synchronized中。

    2. 为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里?而sleep定义在Thread类里?
      锁是绑定到对象的,每个对象中都有几位是保存锁的状态。如果锁的概念放到Thread中,那么就无法实现,某一个线程持有多个锁的情况,没有目前灵活。

    3.wait方法是属于Object对象的,那调用Thread.wait会怎么样?
    线程退出的时候它会自动执行notify(),会使我们的流程受到干扰。Thread类不适合作为锁对象,其他的都适合,用个Object类就可以了。

    4.如何选择用notify还是notifyAll()?
    notifyAll之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办?
    和回到初始状态一样,进入等待状态,等线程调度器调度。

    5.用suspend()和resume()来阻塞线程可以吗?为什么?
    推荐wait(),notify()来代替。

    相关文章

      网友评论

          本文标题:Java线程系列——Object类中线程相关方法

          本文链接:https://www.haomeiwen.com/subject/hcifchtx.html