java线程之间的通讯:结合阿里巴巴面试题解析

在牛客网上看秋招的面经,看到阿里的面试官出的一题,如下

编程题1:用两个线程分别打印0-100之间的奇偶数。

比如 有A,B两个线程;
A线程打印1,3,5,7,9 … 99
B线程打印0,2,4,6,8 … 100
其实这题还是比较简单的,如果你不会这题,那么,我们来一起看一下怎么实现线程之间的通讯。

一、初识wait和notity

wait和notity我觉得有点类似于在操作系统这门课程中利用信号量机制解决系统中的同步和互斥问题。 信号量是Dijkstra在1965年提出的一种方法,它使用一个整型变量来累计唤醒次数,供以后使用。在他的建议中引入了一个新的变量类型,称作信号量(semaphore)。一个信号量的取值可以为0(表示没有保存下来的唤醒操作)或者正值(表示有一个或多个唤醒操作)。
那么这里肯定要讲一下生产者和消费者模型,假设生产者去生产商品,消费者去购买商品,生产者生产出来的商品需要放到仓库存储,然后再从仓库取出卖给消费者。同时仓库存放商品需要生产者付一定的资金,存放的资金越多,需要付的资金越多。这里我们先讨论一个消费者一个生产者的情况。看下图

java线程之间的通讯:结合阿里巴巴面试题解析_第1张图片
生产者消费者.png

我们发现生产者和消费者都关注着一件事情 那就是仓库有没有商品,生产者自然希望仓库商品越少越好,因为可以减少租金,但是同时得满足消费者的需求。那么这时候可以把商品当做信号量,因为这是他们共同关注的,这时候如果生产一个商品,消费者去消费这个商品之后再生产,就解决的生产者的问题了,当然这是一个生产者和一个消费者的情况下。那么我先看一个程序。
package com.lntu.zlt;

public class ProducterAndConsumer {
private int i = 0;
final private Object lock = new Object();
private volatile boolean isProduce = false;

public void produce() {
    synchronized (lock) {
        if (isProduce) {
            try {
                lock.wait();// 仓库有商品,等待消费者消费
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } else {
            i++;
            System.out.println("p生产了->" + i);
            lock.notify();// 通知消费者商品有啦
            isProduce = true;
        }
    }
}

public void consume() {
    synchronized (lock) {
        if (isProduce) {
            System.out.println("c消费了->" + i);
            lock.notify();// 通知生产者商品已经消费完了
            isProduce = false;
        } else {
            try {
                lock.wait();// 没有商品,进入等待
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

public static void main(String[] args) {
    ProducterAndConsumer pc = new ProducterAndConsumer();
    new Thread() {
        @Override
        public void run() {
            while (true) {
                pc.produce();
            }
        }
    }.start();

    new Thread() {
        @Override
        public void run() {
            while (true) {
                pc.consume();
                ;
            }
        }
    }.start();
}
     }

运行结果:
p生产了->19650
c消费了->19650
p生产了->19651
c消费了->19651
p生产了->19652
c消费了->19652
...............
我截取了一段结果,我写的代码是不断地去消费和生产,我们可以发现是生产者生产一个,消费者消费一个,他们之间是有通信的,就是靠wait和notity进行通信。当然这里的代码只适用于一个消费者和一个生产者的情况,后面我会介绍多个消费者生产者的情况。当仓库有商品时就会利用wait函数进入等待,等待消费者的消费。如果没有商品呢,先生产商品,然后就用notity去通知消费者,只是因为在一个Monitor的情况下,同样消费者如果消费完了商品也要notity一下生产者,没有商品就进入等待队列。JDK官方文档是这么介绍wait和notity的

  • void notify()
    Wakes up a single thread that is waiting on this object’s monitor.
    译:唤醒在此对象监视器上等待的单个线程

  • void notifyAll()
    Wakes up all threads that are waiting on this object’s monitor.
    译:唤醒在此对象监视器上等待的所有线程

  • void wait( )
    Causes the current thread to wait until another thread invokes the notify() method or the notifyAll( ) method for this object.
    译:导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法

  • void wait(long timeout)
    Causes the current thread to wait until either another thread invokes the notify( ) method or the notifyAll( ) method for this object, or a specified amount of time has elapsed.
    译:导致当前的线程等待,直到其他线程调用此对象的notify() 方法或 notifyAll() 方法,或者指定的时间过完。

  • void wait(long timeout, int nanos)
    Causes the current thread to wait until another thread invokes the notify( ) method or the notifyAll( ) method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed.
    译:导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法,或者其他线程打断了当前线程,或者指定的时间过完。
    不明白对象监视器的可以去查查0.0

二、利用wait和notity打印奇偶数

思路:同理和上面生产者消费者一样,定义了一个i,在加锁的代码块里,每次进程1判断他是不是奇数,是奇数,打印出来,累加i,notify Thread2,这里我用到的是notityAll(),这个是notify一个Monitor里面的所有线程。如果不是奇数,则等待,这时候数据再交给Thread2处理,然后Thread2在加锁的代码块里面判断i是否为偶数,若为偶则notify其他线程,并累加数,不是则进入wait。

package com.lntu.zlt;
public class alibb {
private static Object lock = new Object();
private static int i = 0;
private static final int TOTAL = 100;

public static void main(String[] args) {

    Thread thread1 = new Thread() {
        public void run() {
            while (i <= TOTAL) {
                synchronized (lock) {
                    if (i % 2 == 1) {
                        System.out.println("thread1  " + i++);
                        lock.notifyAll();
                    } else {

                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }
        }
    };

    Thread thread2 = new Thread() {
        public void run() {
            while (i <= TOTAL) {
                synchronized (lock) {
                    if (i % 2 == 0) {
                        System.out.println("thread2  " + i++);
                        lock.notifyAll();
                    } else {

                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();

                        }
                    }
                }

            }
        }
    };
    thread1.start();
    thread2.start();
}
   }

三、wait和sleep的区别

这也是面试问的挺多的一个问题了,下面总结一下。

  • 1.sleep是Thread的方法,wait是Object的方法。
  • 2.sleep不会释放Object Monitor(Lock),但是Wait会释放Monitor,并且把Object Monitor 放到wait queue。
  • 3.sleep不会依赖Monitor,但是wait依赖。
  • 4.sleep方法不需要被唤醒,但是wait需要被唤醒,但是wait(long timeout)不一样,例如调用wait(30),要么notify去唤醒他,要么等待30ms,会终止等待。

ps:如果想加微信交流那么请扫描下方二维码


java线程之间的通讯:结合阿里巴巴面试题解析_第2张图片
微信号forlove930.jpg

如有异议,请下方留言,喜欢的点个关注,谢谢。

你可能感兴趣的:(java线程之间的通讯:结合阿里巴巴面试题解析)