天天看點

國内Java面試總是問StringBuffer,StringBuilder差別是啥?檔次為什麼這麼低?

GitHub 6.6k Star 的Java工程師成神之路 ,不來了解一下嗎? GitHub 6.6k Star 的Java工程師成神之路 ,真的不來了解一下嗎? GitHub 6.6k Star 的Java工程師成神之路 ,真的确定不來了解一下嗎?

這是一個知乎上面很火的問題(

https://www.zhihu.com/question/50211894

),下面是我關于這個問題的回答,截止今天,這個答案收獲了500+贊和70+評論。

原答案

這個問題隻是開個場,熱個身而已啊。

StringBuffer,StringBuilder差別是啥?

什麼是線程安全?

如何保證線程安全?

什麼是鎖?死鎖?

synchronized的實作原理是什麼?

有了synchronized,還要volatile幹什麼?

synchronized的鎖優化是怎麼回事?(鎖粗化?鎖消除?自旋鎖?偏向鎖?輕量級鎖?)

知道JMM嗎?(原子性?可見性?有序性?)

Java并發包了解嗎?

那什麼是fail-fast?什麼是fail-safe?

什麼是CopyOnWrite?

那AQS呢?那CAS呢?

CAS都知道,那樂觀鎖一定知道了?

樂觀鎖悲觀鎖差別是什麼?

資料庫如何實作悲觀鎖和樂觀鎖?

資料庫鎖有了解麼?行級鎖?表級鎖?共享鎖?排他鎖?gap鎖?next-key lock?

資料庫鎖和隔離級别有什麼關系?

資料庫鎖和索引有什麼關系?

什麼是聚簇索引?非聚簇索引?最左字首是什麼?B+樹索引?聯合索引?回表?

分布式鎖有了解嗎?

Redis怎麼實作分布式鎖?

為什麼要用Redis?

Redis和memcache差別是什麼?

Zookeeper怎麼實作分布式鎖?

什麼是Zookeeper?

什麼是CAP?

什麼是BASE?和CAP什麼差別?

CAP怎麼推導?如何取舍?

分布式系統怎麼保證資料一緻性?

啥是分布式事務?分布式事務方案?

那麼,最後了,來手寫一個線程安全的單例吧?

不用synchronized和lock能實作線程安全的單例嗎?

這你都能答上?那好吧,你給我解釋下什麼是Paxos算法吧?

卒~!

部分答案

針對以上的問題,我給一些答案,希望大家都能有所收獲。這些答案也都是從我的部落格中曆史文章總結出來的。

部分問題簡單幾句可以說的清楚的,我就直接貼答案了,比較複雜的,我貼個傳送門。

但是,以下答案可能并不完整,比如一些比較類的,我可能隻說重點或者和這個知識體系有關的關鍵點,是以希望讀者可以根據下面的問題,完善每一個問題的答案。

StringBuffer是線程安全的,而StringBuilder是非線程安全的。

線程安全是程式設計中的術語,指某個函數、函數庫在并發環境中被調用時,能夠正确地處理多個線程之間的共享變量,使程式功能正确完成。即在多線程場景下,不發生有序性、原子性以及可見性問題。

Java中主要通過加鎖來實作線程安全。通常使用synchronized和Lock

死鎖是指兩個或兩個以上的程序在執行過程中,由于競争資源或者由于彼此通信而造成的一種阻塞的現象,若無外力作用,它們都将無法推進下去。此時稱系統處于死鎖狀态或系統産生了死鎖,這些永遠在互相等待的程序稱為死鎖程序。

死鎖的發生必須具備以下四個必要條件:互斥條件、請求和保持條件、不剝奪條件、環路等待條件

死鎖的解決辦法就是破壞以上四種必備條件中的一個或者多個。

深入了解多線程(一)——Synchronized的實作原理 深入了解多線程(四)—— Moniter的實作原理 再有人問你synchronized是什麼,就把這篇文章發給他

volatile通常被比喻成”輕量級的synchronized“,volatile可以保證可見性和有序性,實作原理是通過記憶體屏障實作的。

volatile有一個重要的作用,是synchronized不具備的,那就是禁止指令重排序。這一特點在雙重校驗鎖實作單例的時候有用到,雖然使用了synchronized關鍵字,但是如果不用volatile修飾單例對象,就會存在問題。

深入了解Java中的volatile關鍵字 再有人問你synchronized是什麼,就把這篇文章發給他。 深入了解多線程(五)—— Java虛拟機的鎖優化技術

Java記憶體模型(Java Memory Model ,JMM)是一種符合記憶體模型規範的,屏蔽了各種硬體和作業系統的通路差異的,保證了Java程式在各種平台下對記憶體的通路都能保證效果一緻的機制及規範。

再有人問你Java記憶體模型是什麼,就把這篇文章發給他。

java.util.concurrent包(J.U.C)中包含的是java并發程式設計中有用的一些工具類,包括幾個部分:

1、locks部分:包含在java.util.concurrent.locks包中,提供顯式鎖(互斥鎖和速寫鎖)相關功能;

2、atomic部分:包含在java.util.concurrent.atomic包中,提供原子變量類相關的功能,是建構非阻塞算法的基礎;

3、executor部分:散落在java.util.concurrent包中,提供線程池相關的功能;

4、collections部分:散落在java.util.concurrent包中,提供并發容器相關功能;

5、tools部分:散落在java.util.concurrent包中,提供同步工具類,如信号量、閉鎖、栅欄等功能;

我們通常說的Java中的fail-fast機制,預設指的是Java集合的一種錯誤檢測機制。當多個線程對部分集合進行結構上的改變的操作時,有可能會産生fail-fast機制,這個時候就會抛出ConcurrentModificationException。

ConcurrentModificationException,當方法檢測到對象的并發修改,但不允許這種修改時就抛出該異常。

為了避免觸發fail-fast機制,導緻異常,我們可以使用Java中提供的一些采用了fail-safe機制的集合類。

這樣的集合容器在周遊時不是直接在集合内容上通路的,而是先複制原有集合内容,在拷貝的集合上進行周遊。

java.util.concurrent包下的容器都是fail-safe的,可以在多線程下并發使用,并發修改。同時也可以在foreach中進行add/remove 。

一不小心就踩坑的fail-fast是個什麼鬼?

Copy-On-Write簡稱COW,是一種用于程式設計中的優化政策。其基本思路是,從一開始大家都在共享同一個内容,當某個人想要修改這個内容的時候,才會真正把内容Copy出去形成一個新的内容然後再改,這是一種延時懶惰政策。

CopyOnWrite容器即寫時複制的容器。通俗的了解是當我們往一個容器添加元素的時候,不直接往目前容器添加,而是先将目前容器進行Copy,複制出一個新的容器,然後新的容器裡添加元素,添加完元素之後,再将原容器的引用指向新的容器。

AQS(AbstractQueuedSynchronizer),即隊列同步器。它是建構鎖或者其他同步元件的基礎架構(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并發包的作者(Doug Lea)期望它能夠成為實作大部分同步需求的基礎。它是JUC并發包中的核心基礎元件。

CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,隻有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程并不會被挂起,而是被告知這次競争中失敗,并可以再次嘗試。

CAS 操作包含三個操作數 —— 記憶體位置(V)、預期原值(A)和新值(B)。如果記憶體位置的值與預期原值相比對,那麼處理器會自動将該位置值更新為新值。否則,處理器不做任何操作。無論哪種情況,它都會在 CAS 指令之前傳回該位置的值。(在 CAS 的一些特殊情況下将僅傳回 CAS 是否成功,而不提取目前值。)CAS 有效地說明了“我認為位置 V 應該包含值 A;如果包含該值,則将 B 放到這個位置;否則,不要更改該位置,隻告訴我這個位置現在的值即可。”這其實和樂觀鎖的沖突檢查+資料更新的原理是一樣的。

樂觀鎖的一種實作方式——CAS

樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖假設認為資料一般情況下不會造成沖突,是以在資料進行送出更新的時候,才會正式對資料的沖突與否進行檢測,如果發現沖突了,則讓傳回使用者錯誤的資訊,讓使用者決定如何去做。

相對于悲觀鎖,在對資料庫進行處理的時候,樂觀鎖并不會使用資料庫提供的鎖機制。一般的實作樂觀鎖的方式就是記錄資料版本。

實作資料版本有兩種方式,第一種是使用版本号,第二種是使用時間戳。

深入了解樂觀鎖與悲觀鎖

同上

MySQL中的行級鎖,表級鎖,頁級鎖 MySQL中的共享鎖與排他鎖

很多DBMS定義了多個不同的“事務隔離等級”來控制鎖的程度和并發能力。

ANSI/ISO SQL定義的标準隔離級别有四種,從高到底依次為:可序列化(Serializable)、可重複讀(Repeatable reads)、送出讀(Read committed)、未送出讀(Read uncommitted)。

深入分析事務的隔離級别

在MySQL中,行級鎖并不是直接鎖記錄,而是鎖索引。索引分為主鍵索引和非主鍵索引兩種,如果一條sql語句操作了主鍵索引,MySQL就會鎖定這條主鍵索引;如果一條語句操作了非主鍵索引,MySQL會先鎖定該非主鍵索引,再鎖定相關的主鍵索引。

主鍵索引的葉子節點存的是整行資料。在InnoDB中,主鍵索引也被稱為聚簇索引(clustered index)

非主鍵索引的葉子節點的内容是主鍵的值,在InnoDB中,非主鍵索引也被稱為非聚簇索引(secondary index)

當我們建立一個聯合索引的時候,如(key1,key2,key3),相當于建立了(key1)、(key1,key2)和(key1,key2,key3)三個索引,這就是最左比對原則。

在 InnoDB 裡,索引B+ Tree的葉子節點存儲了整行資料的是主鍵索引。而索引B+ Tree的葉子節點存儲了主鍵的值的是非主鍵索引。因為主鍵索引樹的葉子節點直接就是我們要查詢的整行資料了。而非主鍵索引的葉子節點是主鍵的值,查到主鍵的值以後,還需要再通過主鍵的值再進行一次查詢,這個過程叫做回表。

我以為我對Mysql索引很了解,直到我遇到了阿裡的面試官

目前比較常用的有以下幾種方案:

基于資料庫實作分布式鎖 基于緩存(redis,memcached,tair)實作分布式鎖 基于Zookeeper實作分布式鎖

分布式鎖的幾種實作方式~

多個程序執行以下Redis指令:

SETNX lock.foo

如果 SETNX 傳回1,說明該程序獲得鎖,SETNX将鍵 lock.foo 的值設定為鎖的逾時時間(目前時間 + 鎖的有效時間)。 如果 SETNX 傳回0,說明其他程序已經獲得了鎖,程序不能進入臨界區。程序可以在一個循環中不斷地嘗試 SETNX 操作,以獲得鎖。

分布式緩存,提升性能

1、存儲方式:Memcache把資料全部存在記憶體之中,斷電後會挂掉,資料不能超 過記憶體大小。Redis支援資料的持久化,可以将記憶體中的資料儲存在磁盤中,重新開機時可以再次加載進行使用。(RDB快照和AOF日志兩 種持久化方式)。         

2、Redis支援資料的備份,及master-slave模式的資料備份。         

3、資料支援類型:Redis在資料支援上要比Memcache多得多。         

4、使用底層模型不同:新版本的Redis直接自己建構了VM機制,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。         

基于zookeeper臨時有序節點可以實作的分布式鎖。

大緻思想即為:每個用戶端對某個方法加鎖時,在zookeeper上的與該方法對應的指定節點的目錄下,生成一個唯一的瞬時有序節點。 判斷是否擷取鎖的方式很簡單,隻需要判斷有序節點中序号最小的一個。 當釋放鎖的時候,隻需将這個瞬時節點删除即可。同時,其可以避免服務當機導緻的鎖無法釋放,而産生的死鎖問題。

Zookeeper是一個開放源碼的分布式服務協調元件,是Google Chubby的開源實作。是一個高性能的分布式資料一緻性解決方案。他将那些複雜的、容易出錯的分布式一緻性服務封裝起來,構成一個高效可靠的原語集,并提供一系列簡單易用的接口給使用者使用。

Zookeeper介紹(二)——Zookeeper概述

CAP理論:一個分布式系統最多隻能同時滿足一緻性(Consistency)、可用性(Availability)和分區容錯性(Partition tolerance)這三項中的兩項。

分布式系統的CAP理論

BASE理論是對CAP理論的延伸,核心思想是即使無法做到強一緻性(Strong Consistency,CAP的一緻性就是強一緻性),但應用可以采用适合的方式達到最終一緻性(Eventual Consitency)。

BASE是指基本可用(Basically Available)、軟狀态( Soft State)、最終一緻性( Eventual Consistency)。

分布式系統的BASE理論

對于涉及到錢财這樣不能有一絲讓步的場景,C必須保證。網絡發生故障甯可停止服務,這是保證CP,舍棄A。比如前幾年支付寶光纜被挖斷的事件,在網絡出現故障的時候,支付寶就在可用性和資料一緻性之間選擇了資料一緻性,使用者感受到的是支付寶系統長時間當機,但是其實背後是無數的工程師在恢複資料,保證數資料的一緻性。

對于其他場景,比較普遍的做法是選擇可用性和分區容錯性,舍棄強一緻性,退而求其次使用最終一緻性來保證資料的安全。

分布式事務

分布式事務是指會涉及到操作多個資料庫的事務。其實就是将對同一庫事務的概念擴大到了對多個庫的事務。目的是為了保證分布式系統中的資料一緻性。分布式事務處理的關鍵是必須有一種方法可以知道事務在任何地方所做的所有動作,送出或復原事務的決定必須産生統一的結果(全部送出或全部復原)

關于分布式事務、兩階段送出協定、三階送出協定 分布式事務解決方案——柔性事務與服務模式 單例模式的七種寫法 為什麼我牆裂建議大家使用枚舉來實作單例

借助CAS(AtomicReference)實作單例模式:

public class Singleton {
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>(); 

    private Singleton() {}

    public static Singleton getInstance() {
        for (;;) {
            Singleton singleton = INSTANCE.get();
            if (null != singleton) {
                return singleton;
            }

            singleton = new Singleton();
            if (INSTANCE.compareAndSet(null, singleton)) {
                return singleton;
            }
        }
    }
}

           

用CAS的好處在于不需要使用傳統的鎖機制來保證線程安全,CAS是一種基于忙等待的算法,依賴底層硬體的實作,相對于鎖它沒有線程切換和阻塞的額外消耗,可以支援較大的并行度。 CAS的一個重要缺點在于如果忙等待一直執行不成功(一直在死循環中),會對CPU造成較大的執行開銷。

不使用synchronized和lock,如何實作一個線程安全的單例? 不使用synchronized和lock,如何實作一個線程安全的單例?(二)

Paxos一種基于消息傳遞且具有高度容錯特性的一緻性算法。Paxos算法号稱是最難了解的算法!!!

總結

面試,其實是一個循序漸進的過程,面試官不可能上來就讓一個面試者手撸paxos算法,總要先抛出一個比較簡單的問題,然後根據面試者回答的情況,逐漸的展開和深入。

另外 ,以上問題的"推倒"過程,其實就是一個完整的知識體系,很多人在我的公衆号背景以及微信好友問我到底什麼是知識體系,如何建構自己的知識體系。

這個問題并沒有什麼标準答案,同樣一個知識點,不斷的展開,把多個知識點互相連接配接,這就是一個知識體系。每個人的知識體系都不相同。但是建構過程都是一樣的,那就是圖論中的"深度優先搜尋"和"廣度優先搜尋",就看哪種比較适合你了。