日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

《关于我横扫一线厂的那些面经》拼多多Java岗(附答案)

發(fā)布時間:2024/1/8 java 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《关于我横扫一线厂的那些面经》拼多多Java岗(附答案) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

去年年底面試了多多買菜,有圖為證,現(xiàn)整理面經(jīng),希望各位不要覺得太遲(這該死的拖延癥😂)。

周日晚上8點視頻面試的拼多多,結(jié)果人家全員加班中,辦公室中都是人,所以大家去多多前還是思考下把。🤣

?

問題1.arraylist線程是否安全,具體體現(xiàn)在哪行?

答:線程不安全,具體表現(xiàn)新增元素的賦值操作elementData[size++]=e。

?

我們先來看下什么是線程的安全性:

線程安全就是多線程訪問時對數(shù)據(jù)進行了加鎖機制(樂觀鎖或悲觀鎖),只有一個線程能夠正常訪問,其他線程不能進行訪問直到該線程讀取完。不會出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)污染

線程不安全就是多線程訪問時不提供數(shù)據(jù)訪問保護,有可能出現(xiàn)多個線程先后更改數(shù)據(jù),所以得到的數(shù)據(jù)是臟數(shù)據(jù)

?

如圖,AbstractList下面有兩個類,一個是線程不安全ArrayList,另外一個是線程安全vector。

?

從源碼的角度來看,因為Vector的方法前加了synchronized 關(guān)鍵字,也就是同步的意思,所以其是線程安全的。

?

所以我們看到這兩個的區(qū)別:

Vector:線程安全,但是性能低。

ArrayList:線程不安全,但是性能高,高效。

?

所以自古魚和熊掌不可兼得。

?

我們來看下具體的代碼,從下圖我們可以得出其添加元素時候的兩步走:

1. 在 Items[Size] 的位置存放此元素;

2. 增大 Size 的值。

?

我們來思考下:

單線程運行的情況下,如果 Size = 0,添加一個元素后,此元素在位置 0,而且 Size=1,一切都是正常的;

而如果是在多線程情況下,比如有兩個線程,線程 A 先將元素存放在位置 0,此時size并沒有加一,但是此時 CPU 調(diào)度線程A暫停,線程 B 得到運行的機會。

線程B也向此 ArrayList 添加元素,因為此時 Size 仍然等于 0 (注意哦,我們假設(shè)的是添加一個元素是要兩個步驟哦,而線程A僅僅完成了步驟1),

所以線程B也將元素存放在位置0,此時size也沒有加1。然后線程A和線程B都繼續(xù)運行,都增加 Size 的值,此時size等于2。

那好,現(xiàn)在我們發(fā)現(xiàn)問題了,元素實際上只有一個,存放在位置 0,而 Size 卻等于 2。這就是“線程不安全”了。

?

看下面的代碼,在多線程并發(fā)情況下,提示了報錯信息,程序報了并發(fā)修改異常ConcurrentModificationException,我們也可以通過看下ArrayList底層中add()方法,是沒有加鎖的操作,當(dāng)多個線程共享一份資源時,可能發(fā)生線程問題;arrayList的add()方法是沒有加鎖的。

public static void main(String[] args) throws InterruptedException {List<Integer> list = new ArrayList<>();ExecutorService threadPool = Executors.newFixedThreadPool(30);for (int i = 1; i <= 30; i++) {int finalI = i;threadPool.execute(() -> {list.add(finalI);System.out.println(list);});}threadPool.shutdown();}

?

如果我們想要不報錯,可以將list轉(zhuǎn)化為線程安全的集合,使用Collections工具類的synchronizedList方法,來將其轉(zhuǎn)化線程安全的。

public static void main(String[] args) throws InterruptedException {List<Integer> list = Collections.synchronizedList(new ArrayList<>());//此處只是案例demo,真實使用線程池不建議用這種方式創(chuàng)建ExecutorService threadPool = Executors.newFixedThreadPool(30);for (int i = 1; i <= 30; i++) {int finalI = i;threadPool.execute(() -> {list.add(finalI);System.out.println(list);});}threadPool.shutdown();}

?

問題2.hashmap為什么用紅黑樹,不要AVL樹,或B+樹?

答:因為AVL樹比紅黑樹保持更加嚴格的平衡,是以更多旋轉(zhuǎn)操作導(dǎo)致更慢的插入和刪除為代價的樹,B+樹所有的節(jié)點擠在一起,當(dāng)數(shù)據(jù)量不多的時候會退化成鏈表。

?

AVL樹

平衡二叉搜索樹(Self-balancing binary search tree)又被稱為AVL樹,且具有以下性質(zhì):它是一 棵空樹或它的左右兩個子樹的高度差的絕對值不超過1,并且左右兩個子樹都是一棵平衡二叉樹。

某在AVL樹中查找通常更快,但這是以更多旋轉(zhuǎn)操作導(dǎo)致更慢的插入和刪除為代價的。因此,如果希望查找次數(shù)主導(dǎo)樹的更新次數(shù),請使用AVL樹。

下面兩張圖都是平衡二叉搜索樹。

? ? ? ? ? ? ? ? ? ? ? ? ? ?

?

那我們來看下不是平衡二叉搜索樹長什么樣子?從下圖可以看到C節(jié)點的左子樹有F,L,M,即為兩層,但是C節(jié)點的右子樹并沒有,他們相差了2,所以并不是平衡二叉搜索樹。

左子樹的左子樹插入結(jié)點 (左左)

?

右子樹的右子樹插入節(jié)點 (右右)

?

左子樹的右子樹插入節(jié)點 (左右)

?

右子樹的左子樹插入節(jié)點 (右左)


B+樹

B+樹的非葉子結(jié)點不存儲數(shù)據(jù),所以每個結(jié)點能存儲的關(guān)鍵字更多。所以B+樹更能應(yīng)對大量數(shù)據(jù)的情況。jdk1.7中的HashMap本來是數(shù)組+鏈表的形式,鏈表由于其查找慢的特點,所以需要被查找效率更高的樹結(jié)構(gòu)來替換。
如果用B+樹的話,在數(shù)據(jù)量不是很多的情況下,數(shù)據(jù)都會“擠在”一個結(jié)點里面。這個時候遍歷效率就退化成了鏈表

?

一個m階的B樹具有如下幾個特征:

1.根結(jié)點至少有兩個子女。

2.每個中間節(jié)點都至少包含ceil(m / 2)個孩子,最多有m個孩子。

3.每一個葉子節(jié)點都包含k-1個元素,其中 m/2 <= k <= m。

4.所有的葉子結(jié)點都位于同一層。

5.每個節(jié)點中的元素從小到大排列,節(jié)點當(dāng)中k-1個元素正好是k個孩子包含的元素的值域分劃。

?

?

問題3.ConcurrentHashMap為什么線程安全?哪些點保證了?

答:數(shù)組初始化的時候自旋來保證一定可以初始化成功,然后通過 CAS 設(shè)置 SIZECTL 變量的值,來保證同一時刻只能有一個線程對數(shù)組進行初始化,CAS 成功之后,還會再次判斷當(dāng)前數(shù)組是否已經(jīng)初始化完成,如果已經(jīng)初始化完成,就不會再次初始化;

新增槽點時通過自旋保證一定新增成功,然后通過CAS來新增,如果遇到槽點有值,通過鎖住當(dāng)前槽點或紅黑樹的根節(jié)點

擴容時通過鎖住原數(shù)組的槽點,設(shè)置轉(zhuǎn)移節(jié)點,以及自旋等操作來保證線程安全。

?

ConcurrentHashMap 在 put 方法上的整體思路:
1. 如果數(shù)組為空,初始化,初始化完成之后,走 2;
2. 計算當(dāng)前槽點有沒有值,沒有值的話,cas 創(chuàng)建,失敗繼續(xù)自旋(for 死循環(huán)),直到成功,槽點有值的話,走 3;
3. 如果槽點是轉(zhuǎn)移節(jié)點(正在擴容),就會一直自旋等待擴容完成之后再新增,不是轉(zhuǎn)移節(jié)點走4;
4. 槽點有值的,先鎖定當(dāng)前槽點,保證其余線程不能操作,如果是鏈表,新增值到鏈表的尾部,如果是紅黑樹,使用紅黑樹新增的方法新增;
5. 新增完成之后 check 需不需要擴容,需要的話去擴容。


具體源碼如下:

final V putVal (K key, V value,boolean onlyIfAbsent){if (key == null || value == null) throw new NullPointerException();//通過hashcode計算 hashint hash = spread(key.hashCode());int binCount = 0;for (Node<K, V>[] tab = table; ; ) {Node<K, V> f;int n, i, fh;//table為空,進行初始化工作,調(diào)用initTableif (tab == null || (n = tab.length) == 0)tab = initTable();//如果當(dāng)前索引位置沒有值,直接創(chuàng)建else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {//cas 在 i 位置創(chuàng)建新的元素,當(dāng) i 位置是空時,即能創(chuàng)建成功,結(jié)束 for 自循,否則繼續(xù)自旋if (casTabAt(tab, i, null,new Node<K, V>(hash, key, value, null)))break; // no lock when adding to empty bin}//如果當(dāng)前槽點是轉(zhuǎn)移節(jié)點,表示該槽點正在擴容,就會一直等待擴容完成//轉(zhuǎn)移節(jié)點的 hash 值是固定的,都是 MOVEDelse if ((fh = f.hash) == MOVED)tab = helpTransfer(tab, f);//槽點上有值的else {V oldVal = null;//鎖定當(dāng)前槽點,其余線程不能操作,保證了安全synchronized (f) {//這里再次判斷 i 索引位置的數(shù)據(jù)沒有被修改//binCount 被賦值的話,說明走到了修改表的過程里面if (tabAt(tab, i) == f) {//鏈表if (fh >= 0) {binCount = 1;for (Node<K, V> e = f; ; ++binCount) {K ek;//值有的話,直接返回if (e.hash == hash &&((ek = e.key) == key ||(ek != null && key.equals(ek)))) {oldVal = e.val;if (!onlyIfAbsent)e.val = value;break;}Node<K, V> pred = e;//把新增的元素賦值到鏈表的最后,退出自旋if ((e = e.next) == null) {pred.next = new Node<K, V>(hash, key,value, null);break;}}}//紅黑樹,這里沒有使用 TreeNode,使用的是 TreeBin,TreeNode 只是紅黑樹的一個節(jié)點//TreeBin 持有紅黑樹的引用,并且會對其加鎖,保證其操作的線程安全else if (f instanceof TreeBin) {Node<K, V> p;binCount = 2;//滿足 if 的話,把老的值給 oldVal//在 putTreeVal 方法里面,在給紅黑樹重新著色旋轉(zhuǎn)的時候//會鎖住紅黑樹的根節(jié)點if ((p = ((TreeBin<K, V>) f).putTreeVal(hash, key,value)) != null) {oldVal = p.val;if (!onlyIfAbsent)p.val = value;}}}}//binCount 不為空,并且 oldVal 有值的情況,說明已經(jīng)新增成功了if (binCount != 0) {// 鏈表是否需要轉(zhuǎn)化成紅黑樹if (binCount >= TREEIFY_THRESHOLD)treeifyBin(tab, i);if (oldVal != null)return oldVal;//這一步幾乎走不到。槽點已經(jīng)上鎖,只有在紅黑樹或者鏈表新增失敗的時候//才會走到這里,這兩者新增都是自旋的,幾乎不會失敗break;}}}//check 容器是否需要擴容,如果需要去擴容,調(diào)用 transfer 方法去擴容//如果已經(jīng)在擴容中了,check 有無完成addCount(1L, binCount);return null;}


數(shù)組初始化時的線程安全


數(shù)組初始化時,首先通過自旋來保證一定可以初始化成功,然后通過 CAS 設(shè)置 SIZECTL 變量的值,來保證同一時刻只能有一個線程對數(shù)組進行初始化,CAS 成功之后,還會再次判斷當(dāng)前數(shù)組是否已經(jīng)初始化完成,如果已經(jīng)初始化完成,就不會再次初始化,通過自旋 + CAS + 雙重 check等手段保證了數(shù)組初始化時的線程安全,源碼如下:
? ? ? ? ? ? ? ? ? ?

//初始化 table,通過對 sizeCtl 的變量賦值來保證數(shù)組只能被初始化一次private final Node<K, V>[] initTable () {Node<K, V>[] tab;int sc;//通過自旋保證初始化成功while ((tab = table) == null || tab.length == 0) {// 小于 0 代表有線程正在初始化,釋放當(dāng)前 CPU 的調(diào)度權(quán),重新發(fā)起鎖的競爭if ((sc = sizeCtl) < 0)Thread.yield(); // lost initialization race; just spin// CAS 賦值保證當(dāng)前只有一個線程在初始化,-1 代表當(dāng)前只有一個線程能初始化// 保證了數(shù)組的初始化的安全性else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {try {// 很有可能執(zhí)行到這里的時候,table 已經(jīng)不為空了,這里是雙重 checkif ((tab = table) == null || tab.length == 0) {// 進行初始化int n = (sc > 0) ? sc : DEFAULT_CAPACITY;@SuppressWarnings("unchecked")Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n];table = tab = nt;sc = n - (n >>> 2);}} finally {sizeCtl = sc;}break;}}return tab;}}


?新增槽點值時的線程安全


?此時為了保證線程安全,做了四處優(yōu)化:

  • ?通過自旋死循環(huán)保證一定可以新增成功。
  • ?當(dāng)前槽點為空時,通過 CAS 新增。
  • ?當(dāng)前槽點有值,鎖住當(dāng)前槽點。
  • ?紅黑樹旋轉(zhuǎn)時,鎖住紅黑樹的根節(jié)點,保證同一時刻,當(dāng)前紅黑樹只能被一個線程旋轉(zhuǎn)


擴容中時的線程安全

  • 拷貝槽點時,會把原數(shù)組的槽點鎖住;
  • 拷貝成功之后,會把原數(shù)組的槽點設(shè)置成轉(zhuǎn)移節(jié)點,這樣如果有數(shù)據(jù)需要 put 到該節(jié)點時,發(fā)現(xiàn)該槽點是轉(zhuǎn)移節(jié)點,會一直等待,直到擴容成功之后,才能繼續(xù) put,可以參考 put 方 法中的 helpTransfer 方法;
  • 從尾到頭進行拷貝,拷貝成功就把原數(shù)組的槽點設(shè)置成轉(zhuǎn)移節(jié)點。
  • 等擴容拷貝都完成之后,直接把新數(shù)組的值賦值給數(shù)組容器,之前等待 put 的數(shù)據(jù)才能繼續(xù)?put。

? ? ? ? ? ? ? ? ? ??

// 擴容主要分 2 步,第一新建新的空數(shù)組,第二移動拷貝每個元素到新數(shù)組中去// tab:原數(shù)組,nextTab:新數(shù)組private final void transfer (Node < K, V >[]tab, Node < K, V >[]nextTab){// 老數(shù)組的長度int n = tab.length, stride;if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)stride = MIN_TRANSFER_STRIDE; // subdivide range// 如果新數(shù)組為空,初始化,大小為原數(shù)組的兩倍,n << 1if (nextTab == null) { // initiatingtry {@SuppressWarnings("unchecked")Node<K, V>[] nt = (Node<K, V>[]) new Node<?, ?>[n << 1];nextTab = nt;} catch (Throwable ex) { // try to cope with OOMEsizeCtl = Integer.MAX_VALUE;return;}nextTable = nextTab;transferIndex = n;}// 新數(shù)組的長度int nextn = nextTab.length;// 代表轉(zhuǎn)移節(jié)點,如果原數(shù)組上是轉(zhuǎn)移節(jié)點,說明該節(jié)點正在被擴容ForwardingNode<K, V> fwd = new ForwardingNode<K, V>(nextTab);boolean advance = true;boolean finishing = false; // to ensure sweep before committing nextTab// 無限自旋,i 的值會從原數(shù)組的最大值開始,慢慢遞減到 0for (int i = 0, bound = 0; ; ) {Node<K, V> f;int fh;while (advance) {int nextIndex, nextBound;// 結(jié)束循環(huán)的標志if (--i >= bound || finishing)advance = false;// 已經(jīng)拷貝完成else if ((nextIndex = transferIndex) <= 0) {i = -1;advance = false;}// 每次減少 i 的值else if (U.compareAndSwapInt(this, TRANSFERINDEX, nextIndex,nextBound = (nextIndex > stride ?nextIndex - stride : 0))) {bound = nextBound;i = nextIndex - 1;advance = false;}}// if 任意條件滿足說明拷貝結(jié)束了if (i < 0 || i >= n || i + n >= nextn) {int sc;// 拷貝結(jié)束,直接賦值,因為每次拷貝完一個節(jié)點,都在原數(shù)組上放轉(zhuǎn)移節(jié)點,所以拷貝完成的節(jié)點的數(shù)據(jù)一定不會再發(fā)生變化。// 原數(shù)組發(fā)現(xiàn)是轉(zhuǎn)移節(jié)點,是不會操作的,會一直等待轉(zhuǎn)移節(jié)點消失之后在進行操作。// 也就是說數(shù)組節(jié)點一旦被標記為轉(zhuǎn)移節(jié)點,是不會再發(fā)生任何變動的,所以不會有任何線程安全的問題// 所以此處直接賦值,沒有任何問題。if (finishing) {nextTable = null;table = nextTab;sizeCtl = (n << 1) - (n >>> 1);return;}if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)return;finishing = advance = true;i = n; // recheck before commit}} else if ((f = tabAt(tab, i)) == null)advance = casTabAt(tab, i, null, fwd);else if ((fh = f.hash) == MOVED)advance = true; // already processedelse {synchronized (f) {// 進行節(jié)點的拷貝if (tabAt(tab, i) == f) {Node<K, V> ln, hn;if (fh >= 0) {int runBit = fh & n;Node<K, V> lastRun = f;for (Node<K, V> p = f.next; p != null; p = p.next) {int b = p.hash & n;if (b != runBit) {runBit = b;lastRun = p;}}if (runBit == 0) {ln = lastRun;hn = null;} else {hn = lastRun;ln = null;}// 如果節(jié)點只有單個數(shù)據(jù),直接拷貝,如果是鏈表,循環(huán)多次組成鏈表拷貝for (Node<K, V> p = f; p != lastRun; p = p.next) {int ph = p.hash;K pk = p.key;V pv = p.val;if ((ph & n) == 0)ln = new Node<K, V>(ph, pk, pv, ln);elsehn = new Node<K, V>(ph, pk, pv, hn);}// 在新數(shù)組位置上放置拷貝的值setTabAt(nextTab, i, ln);setTabAt(nextTab, i + n, hn);// 在老數(shù)組位置上放上 ForwardingNode 節(jié)點// put 時,發(fā)現(xiàn)是 ForwardingNode 節(jié)點,就不會再動這個節(jié)點的數(shù)據(jù)了setTabAt(tab, i, fwd);advance = true;}// 紅黑樹的拷貝else if (f instanceof TreeBin) {// 紅黑樹的拷貝工作,同 HashMap 的內(nèi)容,代碼忽略// 在老數(shù)組位置上放上 ForwardingNode 節(jié)點setTabAt(tab, i, fwd);advance = true;}}}}}}

?

問題4.剛才說到分布式鎖,談?wù)勗O(shè)計思路和方案

答:主要根據(jù)具體的業(yè)務(wù)場景展開描述(這邊各個項目不一樣,就不展開說了),主要是引入redis實現(xiàn)的分布式鎖,應(yīng)該保證互斥性(在任何時候只有一個客戶端持有鎖,使用setnx)不能死鎖(設(shè)置過期時間)保證上鎖和解鎖是同一個客戶端(設(shè)置不同的value值),業(yè)務(wù)時間太長,導(dǎo)致鎖過期(設(shè)置看門狗,自動續(xù)鎖),鎖的重入性(使用redis的hset)。

?

如果在一個分布式系統(tǒng)中,我們從數(shù)據(jù)庫中讀取一個數(shù)據(jù),然后修改保存,這種情況很容易遇到并發(fā)問題。因為讀取和更新保存不是一個原子操作,在并發(fā)時就會導(dǎo)致數(shù)據(jù)的不正確。如果是單機應(yīng)用,直接使用本地鎖就可以避免。如果是分布式應(yīng)用,應(yīng)用部署在多個JVM中,沒有辦法控制,所以引入分布式鎖來解決。

?

互斥性

127.0.0.1:6379> setnx lock value1 #在鍵lock不存在的情況下,將鍵key的值設(shè)置為value1 (integer) 1 127.0.0.1:6379> setnx lock value2 #試圖覆蓋lock的值,返回0表示失敗 (integer) 0 127.0.0.1:6379> get lock #獲取lock的值,驗證沒有被覆蓋 "value1" 127.0.0.1:6379> del lock #刪除lock的值,刪除成功 (integer) 1 127.0.0.1:6379> setnx lock value2 #再使用setnx命令設(shè)置,返回0表示成功 (integer) 1 127.0.0.1:6379> get lock #獲取lock的值,驗證設(shè)置成功

加鎖:使用setnx key value命令,如果key不存在,設(shè)置value(加鎖成功)。如果已經(jīng)存在lock(也就是有客戶端持有鎖了),則設(shè)置失敗(加鎖失敗)。

解鎖:使用del命令,通過刪除鍵值釋放鎖。

?

不能死鎖

設(shè)置過期時間,到點數(shù)據(jù)刪除,避免導(dǎo)致如果一個客戶端持有鎖的期間突然崩潰了,就會導(dǎo)致無法解鎖,則其他人將無法拿到該鎖,鎖會一直存在,最終導(dǎo)致出現(xiàn)死鎖的現(xiàn)象。

?

鎖過期

有效時間設(shè)置多長,假如我的業(yè)務(wù)操作比有效時間長,我的業(yè)務(wù)代碼還沒執(zhí)行完就自動給我解鎖了,不就完蛋了嗎。

這個問題就有點棘手了,在網(wǎng)上也有很多討論,第一種解決方法就是靠程序員自己去把握,預(yù)估一下業(yè)務(wù)代碼需要執(zhí)行的時間,然后設(shè)置有效期時間比執(zhí)行時間長一些,保證不會因為自動解鎖影響到客戶端業(yè)務(wù)代碼的執(zhí)行。但是這并不是萬全之策,比如網(wǎng)絡(luò)抖動這種情況是無法預(yù)測的,也有可能導(dǎo)致業(yè)務(wù)代碼執(zhí)行的時間變長,所以并不安全。有一種方法比較靠譜一點,

就是給鎖續(xù)期。在Redisson框架實現(xiàn)分布式鎖的思路,就使用watchDog機制實現(xiàn)鎖的續(xù)期。當(dāng)加鎖成功后,同時開啟守護線程,默認有效期是30秒,每隔10秒就會給鎖續(xù)期到30秒,只要持有鎖的客戶端沒有宕機,就能保證一直持有鎖,直到業(yè)務(wù)代碼執(zhí)行完畢由客戶端自己解鎖,如果宕機了自然就在有效期失效后自動解鎖。

?

保證上鎖和解鎖都是同一個客戶端

key的值可以根據(jù)業(yè)務(wù)設(shè)置,比如是用戶中心使用的,可以命令為USER_REDIS_LOCK,value可以使用uuid保證唯一,用于標識加鎖的客戶端,保證加鎖和解鎖都是同一個客戶端。

每次解鎖可以先判斷鎖的value是不是當(dāng)前用戶,如果是,說明可以解鎖,如果不是,則不能解鎖,會導(dǎo)致誤解了其他人的鎖。

?

鎖的重入性

可重入鎖意思是在外層使用鎖之后,內(nèi)層仍然可以使用,那么可重入鎖的實現(xiàn)思路又是怎么樣的呢?在Redisson實現(xiàn)可重入鎖的思路,使用Redis的哈希表存儲可重入次數(shù),當(dāng)加鎖成功后,使用hset命令,value(重入次數(shù))則是1。

解鎖時,先判斷可重復(fù)次數(shù)是否大于0,大于0則減一,否則刪除鍵值,釋放鎖資源。

?

問題5.算法題

看到這算法題,我笑了,這不是力扣的第一題嗎,哈哈哈,幸好刷過。簡單方法大家都會寫,暴力操作,但是性能有影響,但是評論區(qū)有為大神寫的很巧妙,我就直接搬過來了。

題目:

給定一個整數(shù)數(shù)組 nums?和一個整數(shù)目標值 target,請你在該數(shù)組中找出 和為目標值 的那?兩個?整數(shù),并返回它們的數(shù)組下標。

你可以假設(shè)每種輸入只會對應(yīng)一個答案。但是,數(shù)組中同一個元素在答案里不能重復(fù)出現(xiàn)。

你可以按任意順序返回答案。

解法:

這個解法并不是從題目計算答案,而是從答案出發(fā),看需要什么數(shù)字。

public int[] twoSum(int[] nums, int target) {int[] indexs = new int[2];// 建立k-v ,一一對應(yīng)的哈希表HashMap<Integer,Integer> hash = new HashMap<Integer,Integer>();for(int i = 0; i < nums.length; i++){if(hash.containsKey(nums[i])){indexs[0] = i;indexs[1] = hash.get(nums[i]);return indexs;}// 將數(shù)據(jù)存入 key為補數(shù) ,value為下標hash.put(target-nums[i],i);}// // 雙重循環(huán) 循環(huán)極限為(n^2-n)/2 // for(int i = 0; i < nums.length; i++){// for(int j = nums.length - 1; j > i; j --){// if(nums[i]+nums[j] == target){// indexs[0] = i;// indexs[1] = j; // return indexs;// }// }// }return indexs;}

?

總結(jié)

以上是生活随笔為你收集整理的《关于我横扫一线厂的那些面经》拼多多Java岗(附答案)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。