2024年Java最全带你快速看完9(6),springboot常见面试题

最后

即使是面试跳槽,那也是一个学习的过程。只有全面的复习,才能让我们更好的充实自己,武装自己,为自己的面试之路不再坎坷!今天就给大家分享一个Github上全面的Java面试题大全,就是这份面试大全助我拿下大厂Offer,月薪提至30K!

我也是第一时间分享出来给大家,希望可以帮助大家都能去往自己心仪的大厂!为金三银四做准备!
一共有20个知识点专题,分别是:

Dubbo面试专题

JVM面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Java并发面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Kafka面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

MongDB面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

MyBatis面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

MySQL面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Netty面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

RabbitMQ面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Redis面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

Spring Cloud面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

SpringBoot面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

zookeeper面试专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

常见面试算法题汇总专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

计算机网络基础专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

设计模式专题

这个GItHub上的Java项目开源了,2020最全的Java架构面试复习指南

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

if (!stopRequested)

while (true)

i++;

编译器的优化初衷是好的,但这里却帮了倒忙!

修正这个问题的一种方式是同步访问stopRequested字段。这个程序会如预期般在大约一秒之内终止:

public class StopThread {

private static Boolean stopRequested;

private static synchronized void requestStop() {

stopRequested = true;

}

private static synchronized Boolean stopRequested() {

return stopRequested;

}

public static void main(String[] args)

throws InterruptedException {

Thread backgroundThread = new Thread(() -> {

int i = 0;

while (!stopRequested())

i++;

});

backgroundThread.start();

TimeUnit.SECONDS.sleep(1);

requestStop();

}

}

注意读和写操作都要被同步,否则无法保证同步能起作用。

还是有其他更正确的替代方法,它更加简洁,性能也可能更好。虽然volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该字段的时候都将看到最近刚刚被写入的值:

public class StopThread {

private static volatile Boolean stopRequested;

public static void main(String[] args) throws InterruptedException {

Thread backgroundThread = new Thread(() -> {

int i = 0;

while (!stopRequested)

i++;

});

backgroundThread.start();

TimeUnit.SECONDS.sleep(1);

stopRequested = true;

}

}

在使用volatile的时候务必要小心。以下面的方法为例,假设它要产生序列号:

private static volatile int nextSerialNumber = 0;

public static int generateSerialNumber() {

return nextSerialNumber++;

}

这段代码是有问题的,原因在于:增量操作符(++)不是原子的。它在nextSerialNumber字段中执行两项操作:

  1. 读取nextSerialNumber的值

  2. 写回一个新值,相当于nextSerialNumber + 1

如果第二个线程在第一个线程第一步和第二步之间读取nextSerialNumber,第二个线程就会与第一个线程一起看到同一个值,并返回相同的nextSerialNumber

修正 generateSerialNumber 方法:

private static final Atomiclong nextSerialNum = new Atomiclong();

public static long generateSerialNumber() {

return nextSerialNum.getAndIncrement();

}

避免数据不一致的问题的最佳办法是不共享可变的数据或者共享不可变的数据。如果采用这一策略,对它建立文档就很重要,以便它可以随着程序的发展而得到维护。

如果一个线程修改了一个数据对象,然后其他线程也可以读取该对象,只要它没有再被修改。—— 这种对象叫做高效不可变(effectively immutable ) 。

将上面对象从一个线程传递到其他的线程被称作安全发布(safe publication) 。

安全发布对象引用有许多种方法:

  1. 将它保存在静态字段中,作为类初始化的一部分

  2. 将它保存在volatile字段、final字段或者通过正常锁定访问的字段中

  3. 将它放到支持并发的集合中

79 避免过度同步


为了避免死锁和数据破坏,千万不要从同步区字段内部调用外来方法。

在一个被同步的方法或者代码块中,永远不要放弃对客户端的控制。换句话说:在一个被同步的区域内部,不要调用设计成要被覆盖的方法,或者是由客户端以函数对象的形式提供的方法。这样的方法是外来的。

以下面的类为例,它实现了一个可以观察到的集合包装(set wrapper)。该类允许客户端在将元素添加到集合中时预订通知。

观察者(Observer)模式

package com.wjw.effectivejava2;

import java.util.ArrayList;

import java.util.Collection;

import java.util.List;

import java.util.Set;

public class ObservableSet extends ForwardingSet {

public ObservableSet(Set set) {

super(set);

}

private final List<SetObserver> observers = new ArrayList<>();

public void addObserver(SetObserver observer) {

synchronized (observers) {

observers.add(observer);

}

}

public Boolean removeObserver(SetObserver observer) {

synchronized (observers) {

return observers.remove(observer);

}

}

private void notifyElementAdded(E element) {

synchronized (observers) {

for (SetObserver observer : observers)

observer.added(this, element);

}

}

@Override

public boolean add(E element) {

Boolean added = super.add(element);

if (added)

notifyElementAdded(element);

return added;

}

@Override

public boolean addAll(Collection<? extends E> c) {

Boolean result = false;

for (E element : c)

result |= add(element);

// Calls notifyElementAdded

return result;

}

}

观察者通过调用addObserver方法预订通知,通过调用removeObserver方法取消预订。在这两种情况下,这个回调(callback)接口的实例都会被传递给方法:

@FunctionalInterface

public interface SetObserver {

// Invoked when an element is added to the observable set

void added(ObservableSet set, E element);

}

如果只是粗略地检验一下,ObservableSet 会显得很正常。例如,下面的程序打印出0〜99的数字:

public static void main(String[] args) {

ObservableSet set = new ObservableSet<>(new HashSet<>());

set.addObserver((s, e) -> System.out.println(e));

for (int i = 0; i < 100; i++)

set.add(i);

}

尝试一个更复杂的例子,将上面代码里set.addObserver((s, e) -> System.out.println(e));替换为:

set.addObserver(new SetObserver<>() {

public void added(ObservableSet s, Integer e) {

System.out.println(e);

if (e == 23)

s.removeObserver(this);

}

});

注意:这个调用以一个匿名类SetObserver 实例代替了前一个调用中使用的lambda。这是因为函数对象需要将自身传给s.removeObserver ,而lambda则无法访问它们自己

80 executor、task和stream优先于线程


这一节主要是讲线程池的使用。

java.util.concurrent包中包含了一个Executor Framework它是一个很灵活的基于接口的任务执行工具。

ExecutorService exec = Executors.newSingleThreadExecutor();

为执行而提交一个runnable的方法:

exec.execute(runnable);

告诉executor如何优雅地终止

exec.shutdown();

你可以利用executor service完成更多的工作:

1. 等待一个任务集合中的任何任务完成

利用 invokeAnyinvokeAll

2. 等待executor service优雅地完成终止

awaitTermination

3. 在任务完成时逐个地获取这些任务的结果

ExecutorCompletionService

4. 调度在某个时间段定时运行或周期运行的任务

ScheduledThreadPoolExecutor

为特殊的应用程序选择executor service是很有技巧的:

  1. 编写小程序,或者轻量负载的服务器,使用Executors.newCachedThreadPool

  2. 大负载的产品服务器中,使用Executors.newFixedThreadPool

但是后面人们又发现,使用原生的Executors线程池可能会带来OOM问题,所以又对此作了专门的要求:

在这里插入图片描述

在Executor Framework中,工作单元和执行机制是分开的。工作单元称作任务(task)。任务又有两种:RunnableCallable(会返回值,并且能够抛出任意的异常)

在Java 7中,Executor框架被扩展为支持fork-join任务

这些任务是通过一种称作fork-join池的特殊executor服务运行的。

fork-join任务用ForkJoinTask实例表示,可以被分成更小的子任务,包含ForkJoinPool 的线程不仅要处理这些任务,还要从另一个线程中“偷”任务,以确保所有的线程保持忙碌,从而提高CPU使用率、提高吞吐量,并降低延迟。

并行流Parallel streams就是在fork join池上编写的

81 并发工具优于 wait 和notify


先把结论写出来:

  1. 直接使用 wait 方法和 notify 方法就像用“汇编语言”编程一样,而java.util.concurrent 则提供了更高级的语言。没有理由再使用 wait 方法和 notify

  2. 如果你在维护使用wait 方法和 notify方法的代码,务必确保始终是利用在 while 循环内部调用 wait 方法。

  3. 应该优先使用 notifyAll 方法,而不是使用 notify 方法。如果使用 notify 方法,一定要确保程序的活性。

并发集合

并发集合为标准的集合接口(如ListQueueMap )提供了高性能的并发实现。为了提供高并发性,这些实现在内部自己管理同步。因此,将并发集合锁定没有什么作用,只会使程序的速度变慢。

例如,下面这个方法模拟了 String.intern 的行为:

private static final ConcurrentMap<String, String> map = new ConcurrentHashMap<>();

public static String intern(String s) {

String previousValue = map.putIfAbsent(s, s);

return previousValue == null ? s : previousValue;

}

String.intern:若常量池中不存在相同的字符串,JVM就会在常量池中创建一个字符串,然后返回该字符串的引用

putIfAbsent:如果所指定的 key 已经在 HashMap 中存在,返回和这个 key 值对应的 value, 如果所指定的 key 不在 HashMap 中存在,则返回 null

ConcurrentHashMapget操作进行了优化。所以可以先用get操作进行条件判断:

public static String intern(String s) {

String result = map.get(s);

if (result == null) {

result = map.putIfAbsent(s, s);

if (result == null)

result = s;

}

return result;

}

并发集合导致同步的集合大多被废弃了。应该优先使用 ConcurrentHashMap ,而不是使用Collections.synchronizedMap

有些集合接口已经通过阻塞操作(blocking operation)进行了扩展,它们会一直等待到可以成功执行为止。这样就允许将阻塞队列用于生产者一消费者队列(producer-consumer queue)。

大多数 ExecutorService 实现(包括ThreadPoolExecutor)都使用了一个BlockingQueue

同步器

同步器(Synchronizer)是使线程能够等待另一个线程的对象,允许它们协调动作。最常用的同步器是CountDownLatchSemaphore 。较不常用的是 CyclicBarrierExchanger。功能最强大的同步器是Phaser

倒计数锁存器(Countdown Latch)是一次性的障碍,允许一个或者多个线程等待一个或者多个其他线程来做某些事情。

要在这个简单的基本类型之上构建一些有用的东西,做起来相当容易。例如,假设想要构建一个简单的框架,用来给一个动作的并发执行定时。

直接在waitnotify之上实现这个逻辑会很棍乱,而在CountDownLatch之上实现则相当简单:

public static long time(Executor executor, int concurrency, Runnable action) throws InterruptedException {

CountDownLatch ready = new CountDownLatch(concurrency);

CountDownLatch start = new CountDownLatch(1);

CountDownLatch done = new CountDownLatch(concurrency);

for (int i = 0; i < concurrency; i++) {

executor.execute(() -> {

ready.countDown();

// Tell timer we’re ready

try {

start.await();

// Wait till peers are ready

action.run();

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

} finally {

done.countDown();

// Tell timer we’re done

}

});

}

ready.await();

// Wait for all workers to be ready

long startNanos = System.nanoTime();

start.countDown();

// And they’re off!

done.await();

// Wait for all workers to finish

return System.nanoTime() - startNanos;

}

代码解释:

这个方法使用了三个倒计数锁存器。第一个是ready ,工作线程用它来告诉 timer 线程它们已经准备好了。然后工作线程在第二个锁存器 start 上等待。

当最后一个工作线程调用ready.countDown时,timer 线程记录下起始时间,并调用start.countDown允许所有的工作线程继续进行。

然后 timer 线程在第三个锁存器 done 上等待,直到最后一个工作线程运行完该动作,并调done.countDown 。此时,timer 线程就会苏醒过来,并记录下结束的时间。

对于间歇式的定时,始终应该优先使用System.nanoTime ,而不是使用System.currentTimeMillis。因为 System.nanoTime 更准确,也更精确,它不受系统时钟调整的影响。

前一个例子中的那三个倒计数锁存器其实可以用一个 CyclicBarrier 或者 Phaser 实例代替。这样得到的代码更加简洁,但是理解起来比较困难。


wait 方法被用来使线程等待某个条件。它必须在同步区域内部被调用,这个同步区域将对象锁定在了调用 wait 方法的对象上。

synchronized (obj) {

while ()

obj.wait(); // (Releases lock, and reacquires on wakeup)

… // Perform action appropriate to condition

}

应该使用循环模式来调用wait方法;永远不要在循环之外调用wait方法。循环会在等待之前和之后对条件进行测试:

  • 在等待之前测试条件,当条件已经成立时就跳过等待

  • 在等待之前测试条件,如果条件不成立的话继续等待

一旦出现下面的这几种情况,线程就可能会醒过来:

  • 另一个线程可能意外地或恶意地调用了 notify 方法

公有可访问对象的同步方法中包含的wait 方法都会出现这样的问题

  • 即使只有某些等待线程的条件已经被满足,但是通知线程仍然调用 notifyAll 方法

  • 在没有通知的情况下,等待线程也可能会苏醒过来。这被称为“伪唤醒”(spurious wakeup)

82 文档应包含线程安全性


先把结论写出来:

  1. 每个类都应该严谨的描述其线程安全属性

最后总结

搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析

最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化

image

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

; // (Releases lock, and reacquires on wakeup)

… // Perform action appropriate to condition

}

应该使用循环模式来调用wait方法;永远不要在循环之外调用wait方法。循环会在等待之前和之后对条件进行测试:

  • 在等待之前测试条件,当条件已经成立时就跳过等待

  • 在等待之前测试条件,如果条件不成立的话继续等待

一旦出现下面的这几种情况,线程就可能会醒过来:

  • 另一个线程可能意外地或恶意地调用了 notify 方法

公有可访问对象的同步方法中包含的wait 方法都会出现这样的问题

  • 即使只有某些等待线程的条件已经被满足,但是通知线程仍然调用 notifyAll 方法

  • 在没有通知的情况下,等待线程也可能会苏醒过来。这被称为“伪唤醒”(spurious wakeup)

82 文档应包含线程安全性


先把结论写出来:

  1. 每个类都应该严谨的描述其线程安全属性

最后总结

搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析

最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化

[外链图片转存中…(img-EBXwqtBM-1714887956791)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 24
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值