只有光头才能变强
Redis目前还在看,今天来分享一下我在秋招看过(遇到)的一些面试题(相对比较常见的)
简要说一下final关键字,final可以用来修饰什么?
这题我是在真实的面试中遇到的,当时答得不太好,现在来整理一下吧。
final可以修饰类、方法、成员变量
值得一说的是: 并不是被final修饰的成员变量就一定是编译期常量了 。比如说我们可以写出这样的代码: private final int java3y = new Randon().nextInt(20);
你有没有这样的编程经验,在编译器写代码时,某个场景下一定要将变量声明为final,否则会出现编译不通过的情况。为什么要这样设计?
在编写匿名内部类的时候就可能会出现这种情况,匿名内部类可能会使用到的变量:
class Outer { // string:外部类的实例变量 String string = ""; //ch:方法的参数 void outerTest(final char ch) { // integer:方法内局部变量 final Integer integer = 1; new Inner() { void innerTest() { System.out.println(string); System.out.println(ch); System.out.println(integer); } }; } public static void main(String[] args) { new Outer().outerTest(' '); } class Inner { } } 复制代码
其中我们可以看到:方法或作用域内的局部变量和方法参数都要 显示使用final关键字来修饰 (在jdk1.7下)!
如果切换到jdk1.8编译环境下,可以通过编译的~
下面我们首先来说一下显示声明为final的原因: 为了保持内部外部数据一致性
为什么仅仅针对方法中的参数限制final,而访问外部类的属性就可以随意
内部类中是保存着一个 指向外部类实例的引用 ,内部类访问外部类的成员变量都是通过这个引用。
那当你在匿名内部类里面尝试改变外部基本类型的变量的值的时候,或者改变外部引用变量的指向的时候, 表面上 看起来好像都成功了,但 实际上并不会影响到外部的变量 。所以,Java为了不让自己看起来那么奇怪,才加了这个final的限制。
参考资料:
选用考量:
三个线程分别打印A,B,C,要求这三个线程一起运行,打印n次,输出形如“ABCABCABC....”的字符串。
原博主给出了4种方式,我认为信号量这种方式比较简单和容易理解,我这里 粘贴 一下(具体的可到原博主下学习)..
public class PrintABCUsingSemaphore { private int times; private Semaphore semaphoreA = new Semaphore(1); private Semaphore semaphoreB = new Semaphore(0); private Semaphore semaphoreC = new Semaphore(0); public PrintABCUsingSemaphore(int times) { this.times = times; } public static void main(String[] args) { PrintABCUsingSemaphore printABC = new PrintABCUsingSemaphore(10); // 非静态方法引用 x::toString 和() -> x.toString() 是等价的! new Thread(printABC::printA).start(); new Thread(printABC::printB).start(); new Thread(printABC::printC).start(); /*new Thread(() -> printABC.printA()).start(); new Thread(() -> printABC.printB()).start(); new Thread(() -> printABC.printC()).start(); */ } public void printA() { try { print("A", semaphoreA, semaphoreB); } catch (InterruptedException e) { e.printStackTrace(); } } public void printB() { try { print("B", semaphoreB, semaphoreC); } catch (InterruptedException e) { e.printStackTrace(); } } public void printC() { try { print("C", semaphoreC, semaphoreA); } catch (InterruptedException e) { e.printStackTrace(); } } private void print(String name, Semaphore current, Semaphore next) throws InterruptedException { for (int i = 0; i < times; i++) { current.acquire(); System.out.print(name); next.release(); } } } 复制代码
2018年9月14日18:15:36 yy笔试题就出了..
在不少的面经都能看到它的身影哈~~~基本都是要求能够手写代码的。
其实逻辑并不难,概括起来就两句话:
基于原作者的代码,我修改了部分并给上我认为合适的注释(下面附上了原作者出处,感兴趣的同学可到原文学习)
import java.util.Random; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; public class Producer implements Runnable { // true--->生产者一直执行,false--->停掉生产者 private volatile boolean isRunning = true; // 公共资源 private final Vector sharedQueue; // 公共资源的最大数量 private final int SIZE; // 生产数据 private static AtomicInteger count = new AtomicInteger(); 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(1000)); // 当队列满时阻塞等待 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; } } 复制代码
import java.util.Random; import java.util.Vector; public class Consumer implements Runnable { // 公共资源 private final Vector sharedQueue; public Consumer(Vector sharedQueue) { this.sharedQueue = sharedQueue; } @Override public void run() { Random r = new Random(); System.out.println("start consumer id = " + Thread.currentThread().getId()); try { while (true) { // 模拟延迟 Thread.sleep(r.nextInt(1000)); // 当队列空时阻塞等待 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(); } } } 复制代码
import java.util.Vector; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Test2 { public static void main(String[] args) throws InterruptedException { // 1.构建内存缓冲区 Vector sharedQueue = new Vector(); int size = 4; // 2.建立线程池和线程 ExecutorService service = Executors.newCachedThreadPool(); Producer prodThread1 = new Producer(sharedQueue, size); Producer prodThread2 = new Producer(sharedQueue, size); Producer prodThread3 = new Producer(sharedQueue, size); Consumer consThread1 = new Consumer(sharedQueue); Consumer consThread2 = new Consumer(sharedQueue); Consumer consThread3 = new Consumer(sharedQueue); service.execute(prodThread1); service.execute(prodThread2); service.execute(prodThread3); service.execute(consThread1); service.execute(consThread2); service.execute(consThread3); // 3.睡一会儿然后尝试停止生产者(结束循环) Thread.sleep(10 * 1000); prodThread1.stop(); prodThread2.stop(); prodThread3.stop(); // 4.再睡一会儿关闭线程池 Thread.sleep(3000); // 5.shutdown()等待任务执行完才中断线程(因为消费者一直在运行的,所以会发现程序无法结束) service.shutdown(); } } 复制代码
另外,上面原文中也说了可以使用阻塞队列来实现消费者和生产者。这就不用我们手动去写 wait/notify
的代码了,会简单一丢丢。可以参考:
我现在需要实现一个栈,这个栈除了可以进行普通的push、pop操作以外,还可以进行getMin的操作,getMin方法被调用后,会返回当前栈的最小值,你会怎么做呢?你可以假设栈里面存的都是int整数
解决方案:
import java.util.ArrayList; import java.util.List; public class MinStack { private List<Integer> data = new ArrayList<Integer>(); private List<Integer> mins = new ArrayList<Integer>(); public void push(int num) { data.add(num); if (mins.size() == 0) { // 初始化mins mins.add(num); } else { // 辅助栈mins每次push当时最小值 int min = getMin(); if (num >= min) { mins.add(min); } else { mins.add(num); } } } public int pop() { // 栈空,异常,返回-1 if (data.size() == 0) { return -1; } // pop时两栈同步pop mins.remove(mins.size() - 1); return data.remove(data.size() - 1); } public int getMin() { // 栈空,异常,返回-1 if (mins.size() == 0) { return -1; } // 返回mins栈顶元素 return mins.get(mins.size() - 1); } } 复制代码
继续优化:
但是,如果一直push的值是最小值,那我们的mins辅助栈还是会有大量的重复元素,此时我们可以使用 索引 (mins辅助栈存储的是最小值索引,非具体的值)!
最终代码:
import java.util.ArrayList; import java.util.List; public class MinStack { private List<Integer> data = new ArrayList<Integer>(); private List<Integer> mins = new ArrayList<Integer>(); public void push(int num) throws Exception { data.add(num); if(mins.size() == 0) { // 初始化mins mins.add(0); } else { // 辅助栈mins push最小值的索引 int min = getMin(); if (num < min) { mins.add(data.size() - 1); } } } public int pop() throws Exception { // 栈空,抛出异常 if(data.size() == 0) { throw new Exception("栈为空"); } // pop时先获取索引 int popIndex = data.size() - 1; // 获取mins栈顶元素,它是最小值索引 int minIndex = mins.get(mins.size() - 1); // 如果pop出去的索引就是最小值索引,mins才出栈 if(popIndex == minIndex) { mins.remove(mins.size() - 1); } return data.remove(data.size() - 1); } public int getMin() throws Exception { // 栈空,抛出异常 if(data.size() == 0) { throw new Exception("栈为空"); } // 获取mins栈顶元素,它是最小值索引 int minIndex = mins.get(mins.size() - 1); return data.get(minIndex); } } 复制代码
参考资料:
众所周知,HashMap不是一个线程安全的类。但有可能在面试的时候会被问到:如果在多线程环境下使用HashMap会有什么现象发生呢??
结论:
put()
的时候导致的多线程数据不一致(丢失数据) resize()
操作会导致环形链表
参考资料:
一、SpringBoot是能够创建出独立的Spring应用程序的
二、简化Spring配置
三、嵌入式Tomcat,Jetty容器,无需部署WAR包
G1收集器的设计目标是取代CMS收集器,它同CMS相比,在以下方面表现的更出色:
拓展阅读:
海量数据的处理也是一个经常考的知识点,无论在面试还是在笔试中都是比较常见的。有幸读了下面的文章,摘录了一些解决海量数据的思路:
详细可参考原文:
昨天去做了一套笔试题,经典的HTTP中 get/post
的区别。今天回来搜了一下,发现跟之前的理解 有点出入 。
如果一个人一开始就做Web开发,很可能 把HTML对HTTP协议的使用方式,当成HTTP协议的唯一的合理使用方式。从而犯了以偏概全的错误
单纯以HTTP协议规范来说,可能我们之前总结出的 GET/POST
区别就没用了。(但通读完整篇文章, 我个人认为 :如果面试中有 GET/POST
区别,还是默认以Web开发场景下来回答较好,这也许是面试官想要的答案)
参考资料:
其中也学习到了幂等性这么一个概念,于是也做做笔记吧~~~
Methods can also have the property of “idempotence” in that (aside from error or expiration issues) the side-effects of N > 0 identical requests is the same as for a single request.
从定义上看,HTTP方法的幂等性是 指一次和多次请求某一个资源应该具有同样的副作用 。
HTTP的 GET/POST/DELETE/PUT
方法幂等的情况:
GET
是幂等的,无副作用
http://localhost/order/2
,使用 GET
多次获取 ,这个ID为2的订单(资源)是 不会发生变化 的! DELETE/PUT
是幂等的,有副作用
http://localhost/order/2
,使用 PUT/DELETE
多次请求 ,这个ID为2的订单(资源) 只会发生一次变化 (是有副作用的)!但继续多次刷新请求, 订单ID为2的最终状态都是一致的 POST
是非幂等的,有副作用的
http://localhost/order
,使用 POST
多次请求,此时可能就会 创建多个名称为3y的订单 ,这个订单(资源)是会多次变化的, 每次请求的资源状态都会变化 ! 题外话:
HTTP协议本身是一种 面向资源的应用层协议 ,但对HTTP协议的使用实际上存在着两种不同的方式:一种是 RESTful 的,它把HTTP当成应用层协议,比较忠实地遵守了HTTP协议的各种规定( 充分利用了HTTP的方法 );另一种 是SOA的 ,它并没有完全把HTTP当成应用层协议,而是把HTTP协议作为了传输层协议,然后在HTTP之上建立了自己的应用层协议
参考资料:
在查阅资料的时候,可以发现很多博客都讲了 接口的幂等性 。从上面我们也可以看出, POST
方法是非幂等的。但我们可以通过一些手段来令 POST
方法的接口变成是幂等的。
说了那么多,那接口设计成幂等的好处是什么????
举个例子说一下非幂等的坏处:
如果我的抢课接口是幂等的话,那就不会出现这个问题了。因为幂等是多次请求某一个资源应该具有 同样的 副作用。
说白了,设计幂等性接口就是为了 防止重复提交的 (数据库出现多条重复的数据)!
网上有博主也分享了几条常见解决重复提交的方案:
参考资料:
如果以上有理解错的地方,或者说有更好的理解方式,希望大家不吝在评论区下留言。共同进步!
如果想看更多的 原创 技术文章,欢迎大家关注我的 微信公众号:Java3y 。公众号还有 海量的视频资源 哦,关注即可免费领取。
可能感兴趣的链接: