【面试必备】手撕代码,你怕不怕?

【面试必备】手撕代码,你怕不怕?

前言:不管是远程的视频面试,还是现场的面试,都有可能会有手撕代码的环节,这也是很多童鞋包括我(虽然还没遇到过..)都很头疼的东西,可能是因为 IDE 自动提示功能用惯了或是其他一些原因,总之让我手写代码就是感觉很奇怪..但是我想的话,这应该侧重考察的是一些细节或者是习惯方面的一些东西,所以还是防患于未然吧,把一些可能手撕的代码给准备准备,分享分享,希望可以得到各位的指正,然后能有一些讨论,由于我字太丑就不上传自己默写的代码了,但还是希望各位潦草写一遍加深一下印象吧,以上;

Part 1.生产者-消费者问题

这绝对是属于重点了,不管是考察对于该重要模型的理解还是考察代码能力,这都是一道很好的考题,所以很有必要的,我们先来回顾一下什么是生产者-消费者问题;

问题简单回顾

【面试必备】手撕代码,你怕不怕?

生产者消费者问题(英语:Producer-consumer problem),也称有限缓冲问题(英语:Bounded-buffer problem),是一个多线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。(摘自维基百科:生产者消费者问题)

注意: 生产者-消费者模式中的内存缓存区的主要功能是数据在多线程间的共享,此外,通过该缓冲区,可以缓解生产者和消费者的性能差;

几种实现方式

上面说到该问题的关键是:如何保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区空时消耗数据;解决思路可以简单概括为:

生产者持续生产,直到缓冲区满,满时阻塞;缓冲区不满后,继续生产;

消费者持续消费,直到缓冲区空,空时阻塞;缓冲区不空后,继续消费;

生产者和消费者都可以有多个;

那么在 Java 语言中,能达到上述要求的,自然而然的就会有如下的几种写法,但是问题的核心都是能够让消费者和生产者在各自满足条件需要阻塞时能够起到正确的作用:

wait()/notify()方式;

await()/signal()方式;

BlockingQueue阻塞队列方式;

PipedInputStream/PipedOutputStream方式;

手写代码,我们着重掌握上面对应的第一种和第三种写法就足够了;

wait()/notify()方式实现

在手写代码之前,我们需要现在 IDE 上实现一遍,理解其中的过程并且找到一些重点/细节,我们先来代码走一遍,然后我把我理解的重点给圈儿出来:

生产者代码 public class Producer implements Runnable { private volatile boolean isRunning = true; private final Vector sharedQueue; // 内存缓冲区 private final int SIZE; // 缓冲区大小 private static AtomicInteger count = new AtomicInteger(); // 总数,原子操作 private static final int SLEEPTIME = 1000; public Producer(Vector sharedQueue, int SIZE) { this.sharedQueue = sharedQueue; this.SIZE = SIZE; } @Override public void run() { int data; Random r = new Random(); System.out.println("start producer id = " + Thread.currentThread().getId()); try { while (isRunning) { // 模拟延迟 Thread.sleep(r.nextInt(SLEEPTIME)); // 当队列满时阻塞等待 while (sharedQueue.size() == SIZE) { synchronized (sharedQueue) { System.out.println("Queue is full, producer " + Thread.currentThread().getId() + " is waiting, size:" + sharedQueue.size()); sharedQueue.wait(); } } // 队列不满时持续创造新元素 synchronized (sharedQueue) { data = count.incrementAndGet(); // 构造任务数据 sharedQueue.add(data); System.out.println("producer create data:" + data + ", size:" + sharedQueue.size()); sharedQueue.notifyAll(); } } } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupted(); } } public void stop() { isRunning = false; } }

有了上面的提到的解决思路,应该很容易实现,但是这里主要提一下一些细节和重点:

创造数据:生产者-消费者解决的问题就是数据在多线程间的共享,所以我们首要关心的问题就应该是数据,我们这里采用的是使用一个AtomicInteger类来为我们创造数据,使用它的好处是该类是一个保证原子操作的类,我们使用其中的incrementAndGet()方法不仅能够保证线程安全,还可以达到一个计数的效果,所以是一个既简单又实用的选择,当然也可以使用其他的数据来代替,这里注意的是要保证该类在内存中只存在一份,使用static修饰

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpdpfy.html