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

一、图解方法

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()来代替。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355