這篇文章主要介紹常見的java面試題有哪些,文中介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們一定要看完!
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對這個(gè)行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長期合作伙伴,公司提供的服務(wù)項(xiàng)目有:域名與空間、雅安服務(wù)器托管、營銷軟件、網(wǎng)站建設(shè)、晉江網(wǎng)站維護(hù)、網(wǎng)站推廣。
只有光頭才能變強(qiáng)
redis目前還在看,今天來分享一下我在秋招看過(遇到)的一些面試題(相對比較常見的)
0、final關(guān)鍵字
簡要說一下final關(guān)鍵字,final可以用來修飾什么?
這題我是在真實(shí)的面試中遇到的,當(dāng)時(shí)答得不太好,現(xiàn)在來整理一下吧。
final可以修飾類、方法、成員變量
當(dāng)final修飾類的時(shí)候,說明該類不能被繼承
當(dāng)final修飾方法的時(shí)候,說明該方法不能被重寫
在早期,可能使用final修飾的方法,編譯器針對這些方法的所有調(diào)用都轉(zhuǎn)成內(nèi)嵌調(diào)用,這樣提高效率(但到現(xiàn)在一般我們不會去管這事了,編譯器和JVM都越來越聰明了)
當(dāng)final修飾成員變量時(shí),有兩種情況:
如果修飾的是基本類型,說明這個(gè)變量的所代表數(shù)值永不能變(不能重新賦值)!
如果修飾的是引用類型,該變量所的引用不能變,但引用所代表的對象內(nèi)容是可變的!
值得一說的是:并不是被final修飾的成員變量就一定是編譯期常量了。比如說我們可以寫出這樣的代碼:private final int java3y = new Randon().nextInt(20);
你有沒有這樣的編程經(jīng)驗(yàn),在編譯器寫代碼時(shí),某個(gè)場景下一定要將變量聲明為final,否則會出現(xiàn)編譯不通過的情況。為什么要這樣設(shè)計(jì)?
在編寫匿名內(nèi)部類的時(shí)候就可能會出現(xiàn)這種情況,匿名內(nèi)部類可能會使用到的變量:
外部類實(shí)例變量
方法或作用域內(nèi)的局部變量
方法的參數(shù)
class Outer { // string:外部類的實(shí)例變量 String string = ""; //ch:方法的參數(shù) void outerTest(final char ch) { // integer:方法內(nèi)局部變量 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 { } }
其中我們可以看到:方法或作用域內(nèi)的局部變量和方法參數(shù)都要顯示使用final關(guān)鍵字來修飾(在jdk1.7下)!
如果切換到j(luò)dk1.8編譯環(huán)境下,可以通過編譯的~
下面我們首先來說一下顯示聲明為final的原因:為了保持內(nèi)部外部數(shù)據(jù)一致性
Java只是實(shí)現(xiàn)了capture-by-value形式的閉包,也就是匿名函數(shù)內(nèi)部會重新拷貝一份自由變量,然后函數(shù)外部和函數(shù)內(nèi)部就有兩份數(shù)據(jù)。
要想實(shí)現(xiàn)內(nèi)部外部數(shù)據(jù)一致性目的,只能要求兩處變量不變。JDK8之前要求使用final修飾,JDK8聰明些了,可以使用effectively final的方式
為什么僅僅針對方法中的參數(shù)限制final,而訪問外部類的屬性就可以隨意
內(nèi)部類中是保存著一個(gè)指向外部類實(shí)例的引用,內(nèi)部類訪問外部類的成員變量都是通過這個(gè)引用。
在內(nèi)部類修改了這個(gè)引用的數(shù)據(jù),外部類再獲取時(shí)拿到的數(shù)據(jù)是一致的!
那當(dāng)你在匿名內(nèi)部類里面嘗試改變外部基本類型的變量的值的時(shí)候,或者改變外部引用變量的指向的時(shí)候,表面上看起來好像都成功了,但實(shí)際上并不會影響到外部的變量。所以,Java為了不讓自己看起來那么奇怪,才加了這個(gè)final的限制。
參考資料:
java為什么匿名內(nèi)部類的參數(shù)引用時(shí)final?https://www.zhihu.com/question/21395848
一、char和varchar的區(qū)別
char是固定長度,varchar長度可變。varchar:如果原先存儲的位置無法滿足其存儲的需求,就需要一些額外的操作,根據(jù)存儲引擎的不同,有的會采用拆分機(jī)制,有的采用分頁機(jī)制。
char的存儲方式是:英文字符占1個(gè)字節(jié),漢字占用2個(gè)字節(jié);varchar的存儲方式是:英文和漢字都占用2個(gè)字節(jié),兩者的存儲數(shù)據(jù)都非unicode的字符數(shù)據(jù)。
char是固定長度,長度不夠的情況下,用空格代替。varchar表示的是實(shí)際長度的數(shù)據(jù)類型
選用考量:
如果字段長度較短和字符間長度相近甚至是相同的長度,會采用char字符類型
二、多個(gè)線程順序打印問題
三個(gè)線程分別打印A,B,C,要求這三個(gè)線程一起運(yùn)行,打印n次,輸出形如“ABCABCABC....”的字符串。
原博主給出了4種方式,我認(rèn)為信號量這種方式比較簡單和容易理解,我這里粘貼一下(具體的可到原博主下學(xué)習(xí))..
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); // 非靜態(tài)方法引用 x::toString 和() -> x.toString() 是等價(jià)的! 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(); } } }
作者:cheergoivan
鏈接:https://www.jianshu.com/p/40078ed436b4
來源:簡書
2018年9月14日18:15:36 yy筆試題就出了..
三、生產(chǎn)者和消費(fèi)者
在不少的面經(jīng)都能看到它的身影哈~~~基本都是要求能夠手寫代碼的。
其實(shí)邏輯并不難,概括起來就兩句話:
如果生產(chǎn)者的隊(duì)列滿了(while循環(huán)判斷是否滿),則等待。如果生產(chǎn)者的隊(duì)列沒滿,則生產(chǎn)數(shù)據(jù)并喚醒消費(fèi)者進(jìn)行消費(fèi)。
如果消費(fèi)者的隊(duì)列空了(while循環(huán)判斷是否空),則等待。如果消費(fèi)者的隊(duì)列沒空,則消費(fèi)數(shù)據(jù)并喚醒生產(chǎn)者進(jìn)行生產(chǎn)。
基于原作者的代碼,我修改了部分并給上我認(rèn)為合適的注釋(下面附上了原作者出處,感興趣的同學(xué)可到原文學(xué)習(xí))
生產(chǎn)者:
import java.util.Random; import java.util.Vector; import java.util.concurrent.atomic.AtomicInteger; public class Producer implements Runnable { // true--->生產(chǎn)者一直執(zhí)行,false--->停掉生產(chǎn)者 private volatile boolean isRunning = true; // 公共資源 private final Vector sharedQueue; // 公共資源的最大數(shù)量 private final int SIZE; // 生產(chǎn)數(shù)據(jù) 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)); // 當(dāng)隊(duì)列滿時(shí)阻塞等待 while (sharedQueue.size() == SIZE) { synchronized (sharedQueue) { System.out.println("Queue is full, producer " + Thread.currentThread().getId() + " is waiting, size:" + sharedQueue.size()); sharedQueue.wait(); } } // 隊(duì)列不滿時(shí)持續(xù)創(chuàng)造新元素 synchronized (sharedQueue) { // 生產(chǎn)數(shù)據(jù) 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; } }
消費(fèi)者:
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)); // 當(dāng)隊(duì)列空時(shí)阻塞等待 while (sharedQueue.isEmpty()) { synchronized (sharedQueue) { System.out.println("Queue is empty, consumer " + Thread.currentThread().getId() + " is waiting, size:" + sharedQueue.size()); sharedQueue.wait(); } } // 隊(duì)列不空時(shí)持續(xù)消費(fèi)元素 synchronized (sharedQueue) { System.out.println("consumer consume data:" + sharedQueue.remove(0) + ", size:" + sharedQueue.size()); sharedQueue.notifyAll(); } } } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); } } }
Main方法測試:
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.構(gòu)建內(nèi)存緩沖區(qū) 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.睡一會兒然后嘗試停止生產(chǎn)者(結(jié)束循環(huán)) Thread.sleep(10 * 1000); prodThread1.stop(); prodThread2.stop(); prodThread3.stop(); // 4.再睡一會兒關(guān)閉線程池 Thread.sleep(3000); // 5.shutdown()等待任務(wù)執(zhí)行完才中斷線程(因?yàn)橄M(fèi)者一直在運(yùn)行的,所以會發(fā)現(xiàn)程序無法結(jié)束) service.shutdown(); } }
作者:我沒有三顆心臟
鏈接:https://www.jianshu.com/p/3f0cd7af370d
來源:簡書
另外,上面原文中也說了可以使用阻塞隊(duì)列來實(shí)現(xiàn)消費(fèi)者和生產(chǎn)者。這就不用我們手動去寫wait/notify
的代碼了,會簡單一丟丟。可以參考:
使用阻塞隊(duì)列解決生產(chǎn)者-消費(fèi)者問題:https://www.cnblogs.com/chenpi/p/5553325.html
四、算法[1]
我現(xiàn)在需要實(shí)現(xiàn)一個(gè)棧,這個(gè)棧除了可以進(jìn)行普通的push、pop操作以外,還可以進(jìn)行g(shù)etMin的操作,getMin方法被調(diào)用后,會返回當(dāng)前棧的最小值,你會怎么做呢?你可以假設(shè)棧里面存的都是int整數(shù)
解決方案:
使用一個(gè)min變量來記住最小值,每次push的時(shí)候,看看是否需要更新min。
如果被pop出去的是min,第二次pop的時(shí)候,只能遍歷一下棧內(nèi)元素,重新找到最小值。
總結(jié):pop的時(shí)間復(fù)雜度是O(n),push是O(1),空間是O(1)
使用輔助棧來存儲最小值。如果當(dāng)前要push的值比輔助棧的min值要小,那在輔助棧push的值是最小值
總結(jié):push和pop的時(shí)間復(fù)雜度都是O(1),空間是O(n)。典型以空間換時(shí)間的例子。
import java.util.ArrayList; import java.util.List; public class MinStack { private Listdata = new ArrayList (); private List mins = new ArrayList (); public void push(int num) { data.add(num); if (mins.size() == 0) { // 初始化mins mins.add(num); } else { // 輔助棧mins每次push當(dāng)時(shí)最小值 int min = getMin(); if (num >= min) { mins.add(min); } else { mins.add(num); } } } public int pop() { // ???,異常,返回-1 if (data.size() == 0) { return -1; } // pop時(shí)兩棧同步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); } }
繼續(xù)優(yōu)化:
棧為空的時(shí)候,返回-1很可能會帶來歧義(萬一人家push進(jìn)去的值就有-1呢?),這邊我們可以使用Java Exception來進(jìn)行優(yōu)化
算法的空間優(yōu)化:上面的代碼我們可以發(fā)現(xiàn):data棧和mins棧的元素個(gè)數(shù)總是相等的,mins棧中存儲幾乎都是最小的值(此部分是重復(fù)的!)
所以我們可以這樣做:當(dāng)push的時(shí)候,如果比min棧的值要小的,才放進(jìn)mins棧。同理,當(dāng)pop的時(shí)候,如果pop的值是mins的最小值,mins才出棧,否則mins不出棧!
上述做法可以一定避免mins輔助棧有相同的元素!
但是,如果一直push的值是最小值,那我們的mins輔助棧還是會有大量的重復(fù)元素,此時(shí)我們可以使用索引(mins輔助棧存儲的是最小值索引,非具體的值)!
最終代碼:
import java.util.ArrayList; import java.util.List; public class MinStack { private Listdata = new ArrayList (); private List mins = new ArrayList (); 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時(shí)先獲取索引 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); } }
參考資料:
【面試現(xiàn)場】如何實(shí)現(xiàn)可以獲取最小值的棧?
作者:channingbreeze 出處: 互聯(lián)網(wǎng)偵察
五、多線程下的HashMap
眾所周知,HashMap不是一個(gè)線程安全的類。但有可能在面試的時(shí)候會被問到:如果在多線程環(huán)境下使用HashMap會有什么現(xiàn)象發(fā)生呢??
結(jié)論:
put()
的時(shí)候?qū)е碌亩嗑€程數(shù)據(jù)不一致(丟失數(shù)據(jù))
resize()
操作會導(dǎo)致環(huán)形鏈表
jdk1.8已解決環(huán)鏈的問題(聲明兩對指針,維護(hù)兩個(gè)連鏈表)
fail-fast機(jī)制,對當(dāng)前HashMap同時(shí)進(jìn)行刪除/修改會拋出ConcurrentModificationException異常
參考資料:
談?wù)凥ashMap線程不安全的體現(xiàn):http://www.importnew.com/22011.html
jdk1.8 hashmap多線程put不會造成死循環(huán):https://blog.csdn.net/qq_27007251/article/details/71403647
六、Spring和Springboot區(qū)別
一、SpringBoot是能夠創(chuàng)建出獨(dú)立的Spring應(yīng)用程序的
二、簡化Spring配置
Spring由于其繁瑣的配置,一度被人成為“配置地獄”,各種XML、Annotation配置,讓人眼花繚亂,而且如果出錯(cuò)了也很難找出原因。
Spring Boot項(xiàng)目就是為了解決配置繁瑣的問題,最大化的實(shí)現(xiàn)convention over configuration(約定大于配置)。
提供一系列的依賴包來把其它一些工作做成開箱即用其內(nèi)置一個(gè)’Starter POM’,對項(xiàng)目構(gòu)建進(jìn)行了高度封裝,最大化簡化項(xiàng)目構(gòu)建的配置。
三、嵌入式Tomcat,Jetty容器,無需部署WAR包
七、G1和CMS
G1收集器的設(shè)計(jì)目標(biāo)是取代CMS收集器,它同CMS相比,在以下方面表現(xiàn)的更出色:
G1是一個(gè)有整理內(nèi)存過程的垃圾收集器,不會產(chǎn)生很多內(nèi)存碎片。
CMS采用的是標(biāo)記清除垃圾回收算法,可能會產(chǎn)生不少的內(nèi)存碎片
G1的Stop The World(STW)更可控,G1在停頓時(shí)間上添加了預(yù)測機(jī)制,用戶可以指定期望停頓時(shí)間。
拓展閱讀:
G1 垃圾收集器介紹:https://javadoop.com/post/g1
八、海量數(shù)據(jù)解決方案
海量數(shù)據(jù)的處理也是一個(gè)經(jīng)??嫉闹R點(diǎn),無論在面試還是在筆試中都是比較常見的。有幸讀了下面的文章,摘錄了一些解決海量數(shù)據(jù)的思路:
Bloom filter布隆過濾器
適用范圍:可以用來實(shí)現(xiàn)數(shù)據(jù)字典,進(jìn)行數(shù)據(jù)的判重,或者集合求交集
Hashing
適用范圍:快速查找,刪除的基本數(shù)據(jù)結(jié)構(gòu),通常需要總數(shù)據(jù)量可以放入內(nèi)存
bit-map
適用范圍:可進(jìn)行數(shù)據(jù)的快速查找,判重,刪除,一般來說數(shù)據(jù)范圍是int的10倍以下
堆
適用范圍:海量數(shù)據(jù)前n大,并且n比較小,堆可以放入內(nèi)存
雙層桶劃分----其實(shí)本質(zhì)上就是【分而治之】的思想,重在“分”的技巧上!
適用范圍:第k大,中位數(shù),不重復(fù)或重復(fù)的數(shù)字
數(shù)據(jù)庫索引
適用范圍:大數(shù)據(jù)量的增刪改查
倒排索引(Inverted index)
適用范圍:搜索引擎,關(guān)鍵字查詢
外排序
適用范圍:大數(shù)據(jù)的排序,去重
trie樹
適用范圍:數(shù)據(jù)量大,重復(fù)多,但是數(shù)據(jù)種類小可以放入內(nèi)存
分布式處理 mapreduce
適用范圍:數(shù)據(jù)量大,但是數(shù)據(jù)種類小可以放入內(nèi)存
詳細(xì)可參考原文:
十道海量數(shù)據(jù)處理面試題與十個(gè)方法大總結(jié):https://blog.csdn.net/v_JULY_v/article/details/6279498
九、冪等性
9.1HTTP冪等性
昨天去做了一套筆試題,經(jīng)典的HTTP中get/post
的區(qū)別。今天回來搜了一下,發(fā)現(xiàn)跟之前的理解有點(diǎn)出入。
如果一個(gè)人一開始就做Web開發(fā),很可能把HTML對HTTP協(xié)議的使用方式,當(dāng)成HTTP協(xié)議的唯一的合理使用方式。從而犯了以偏概全的錯(cuò)誤
單純以HTTP協(xié)議規(guī)范來說,可能我們之前總結(jié)出的GET/POST
區(qū)別就沒用了。(但通讀完整篇文章,我個(gè)人認(rèn)為:如果面試中有GET/POST
區(qū)別,還是默認(rèn)以Web開發(fā)場景下來回答較好,這也許是面試官想要的答案)
參考資料:
GET和POST有什么區(qū)別?及為什么網(wǎng)上的多數(shù)答案都是錯(cuò)的。http://www.cnblogs.com/nankezhishi/archive/2012/06/09/getandpost.html
其中也學(xué)習(xí)到了冪等性這么一個(gè)概念,于是也做做筆記吧~~~
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方法的冪等性是指一次和多次請求某一個(gè)資源應(yīng)該具有同樣的副作用。
這里簡單說一下“副作用”的意思:指當(dāng)你發(fā)送完一個(gè)請求以后,網(wǎng)站上的資源狀態(tài)沒有發(fā)生修改,即認(rèn)為這個(gè)請求是無副作用的
HTTP的GET/POST/DELETE/PUT
方法冪等的情況:
GET
是冪等的,無副作用
比如我想要獲得訂單ID為2的訂單:http://localhost/order/2
,使用GET
多次獲取,這個(gè)ID為2的訂單(資源)是不會發(fā)生變化的!
DELETE/PUT
是冪等的,有副作用
比如我想要刪除或者更新ID為2的訂單:http://localhost/order/2
,使用PUT/DELETE
多次請求,這個(gè)ID為2的訂單(資源)只會發(fā)生一次變化(是有副作用的)!但繼續(xù)多次刷新請求,訂單ID為2的最終狀態(tài)都是一致的
POST
是非冪等的,有副作用的
比如我想要創(chuàng)建一個(gè)名稱叫3y的訂單:http://localhost/order
,使用POST
多次請求,此時(shí)可能就會創(chuàng)建多個(gè)名稱為3y的訂單,這個(gè)訂單(資源)是會多次變化的,每次請求的資源狀態(tài)都會變化!
題外話:
HTTP協(xié)議本身是一種面向資源的應(yīng)用層協(xié)議,但對HTTP協(xié)議的使用實(shí)際上存在著兩種不同的方式:一種是RESTful的,它把HTTP當(dāng)成應(yīng)用層協(xié)議,比較忠實(shí)地遵守了HTTP協(xié)議的各種規(guī)定(充分利用了HTTP的方法);另一種是SOA的,它并沒有完全把HTTP當(dāng)成應(yīng)用層協(xié)議,而是把HTTP協(xié)議作為了傳輸層協(xié)議,然后在HTTP之上建立了自己的應(yīng)用層協(xié)議
參考資料:
理解HTTP冪等性http://www.cnblogs.com/weidagang2046/archive/2011/06/04/2063696.html#!comments
如何理解RESTful的冪等性http://blog.720ui.com/2016/restful_idempotent/
淺談HTTP中Get與Post的區(qū)別http://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html
HTTP 請求中 POST 和 GET 請求的區(qū)別?https://www.zhihu.com/question/27622127/answer/37676304
9.2接口冪等性
在查閱資料的時(shí)候,可以發(fā)現(xiàn)很多博客都講了接口的冪等性。從上面我們也可以看出,POST
方法是非冪等的。但我們可以通過一些手段來令POST
方法的接口變成是冪等的。
說了那么多,那接口設(shè)計(jì)成冪等的好處是什么????
舉個(gè)例子說一下非冪等的壞處:
3y大一的時(shí)候是要搶體育課的,但學(xué)校的搶課系統(tǒng)做得賊爛(延遲很高)。我想要搶到課,就開了10多個(gè)Chrome標(biāo)簽頁去搶(即使某個(gè)Chrome標(biāo)簽頁崩了,我還有另外的Chrome標(biāo)簽頁是可用的)。我想搶到乒乓球或者羽毛球。
搶課時(shí)間一到,我就輪著點(diǎn)擊我要想搶的乒乓球或者羽毛球。如果系統(tǒng)設(shè)計(jì)得不好,這個(gè)請求是非冪等的(或者說事務(wù)控制得不好),我手速足夠快&&網(wǎng)絡(luò)足夠好,那我很可能搶到了多次乒乓球或者羽毛球的課程了。(這是不合理的,一個(gè)人只能選一門課,而我搶到了多門或者多次重復(fù)的課)
涉及到商城的應(yīng)用場景可能就是:用戶下了多個(gè)重復(fù)的訂單了
如果我的搶課接口是冪等的話,那就不會出現(xiàn)這個(gè)問題了。因?yàn)閮绲仁嵌啻握埱竽骋粋€(gè)資源應(yīng)該具有同樣的副作用。
在數(shù)據(jù)庫后臺最多只會有一條記錄,不存在搶到多門課的現(xiàn)象了。
說白了,設(shè)計(jì)冪等性接口就是為了防止重復(fù)提交的(數(shù)據(jù)庫出現(xiàn)多條重復(fù)的數(shù)據(jù))!
網(wǎng)上有博主也分享了幾條常見解決重復(fù)提交的方案:
同步鎖(單線程,在集群可能會失效)
分布式鎖如redis(實(shí)現(xiàn)復(fù)雜)
業(yè)務(wù)字段加唯一約束(簡單)
令牌表+唯一約束(簡單推薦)---->實(shí)現(xiàn)冪等接口的一種手段
MySQL的insert ignore或者on duplicate key update(簡單)
共享鎖+普通索引(簡單)
利用MQ或者Redis擴(kuò)展(排隊(duì))
其他方案如多版本控制MVCC 樂觀鎖 悲觀鎖 狀態(tài)機(jī)等。。
以上是常見的java面試題有哪些的所有內(nèi)容,感謝各位的閱讀!希望分享的內(nèi)容對大家有幫助,更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!