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

本文详细介绍了Java面试中常见的生产者消费者问题的解决方案,包括wait/notify()和BlockingQueue两种实现方式,并强调了在实现过程中需要注意的关键点和优化。此外,还探讨了排序算法的基础,如冒泡排序和快速排序的实现及优化,以及二叉树的递归和非递归遍历。内容覆盖了Java程序员在面试中可能遇到的常见算法和并发问题。
摘要由CSDN通过智能技术生成

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

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

问题简单回顾

 

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

生产者消费者问题(英语: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修饰

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java高级交流:854630135,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

  • 内存缓冲区:要保证在多线程环境下内存缓冲区的安全,所以我们考虑使用简单的Vector类来作为我们的内存缓冲区,并且使用final修饰保证内存缓冲区的唯一,然后的话我们需要判断队列是否满,需要手动添加一个标识缓冲区大小的变量SIZE,注意也是final修饰;
  • 模拟延迟:这里主要模拟的是一个网络延迟,我们首先定义了一个SLEEPTIME的延迟范围,注意使用的是static final修饰,然后使用Random()类的nextInt()方法来随机选取一个该范围内的值来模拟网络环境中的延迟;
  • 停止方法:首先需要知道在Thread类中有一个弃用的stop()方法,我们自己增加一个标志位isRunning来完成我们自己的stop()功能,需要注意的是使用volatile来修饰,保证该标志位的可见性;
  • 错误处理:当捕获到错误时,我们应该使用Thread类中的interrupted()方法来终止当前的进程;
  • 消息提示:我们主要是要在控制台输出该生产者的信息,包括当前队列的状态,大小,当前线程的生产者信息等,注意的是信息格式的统一(后面的消费者同样的)

消费者代码

 
public class Consumer implements Runnable { private final Vector sharedQueue; // 内存缓冲区 private final int SIZE; // 缓冲区大小 private static final int SLEEPTIME = 1000; public Consumer(Vector sharedQueue, int SIZE) { this.sharedQueue = sharedQueue; this.SIZE = SIZE; } @Override public void run() { Random r = new Random(); System.out.println("start consumer id = " + Thread.currentThread().getId()); try { while (true) { // 模拟延迟 Thread.sleep(r.nextInt(SLEEPTIME)); // 当队列空时阻塞等待 while (sharedQueue.isEmpty()) { synchronized (sharedQueue) { System.out.println("Queue is empty, consumer " + Thread.currentThread().getId() + " is waiting, size:" + sharedQueue.size()); sharedQueue.wait(); } } // 队列不空时持续消费元素 synchronized (sharedQueue) { System.out.println("consumer consume data:" + sharedQueue.remove(0) + ", size:" + sharedQueue.size()); sharedQueue.notifyAll(); } } } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } } 

跟生产者相同的,你需要注意内存缓冲区模拟延迟错误处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值