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

一、图解方法

Java线程系列——Object类中线程相关方法_第1张图片
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. 为什么需要生产者消费者模式?

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

Java线程系列——Object类中线程相关方法_第2张图片
生产者模式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 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类中线程相关方法)