我和面試官的博弈:Java 並發編程篇

2021-03-02 Java後端
前言: 本文為《今天你面試了嗎》系列文章,採用情景對話的方式還原面試場景,幫助大家梳理常用 Java 技術棧的知識點,上一篇文章發布後反響良好,此系列還會繼續更新。日後我會把此系列整理成 PDF 版本,發布給大家。還請大家置頂(標星)本公眾號:Java後端,第一時間接收優質博面試中問的頻率很高的一個是分布式,一個就是並發。而JUC(java.util.concurrent)裡的東西是並發編程的基石。上次的面試已經過去一段時間,在一邊努力工作的同時,我也一邊抽出時間準備 Java 並發編程的部分。今天懷著輕鬆愉快的心情,再次踏上我的大廠面試之旅。

面試官:你先說下你對synchronized的了解。

我:synchronized可以保證方法或者代碼在運行時,同一時刻只有一個方法可以進入到臨界區,同時還可以保證共享變量的內存可見性。

我:Java中每個對象都可以作為鎖,這是synchronized實現同步的基礎:

面試官:當線程訪問同步代碼塊時,它首先要得到鎖才能執行代碼,退出或者拋異常要釋放鎖,這是怎麼實現的呢?

我:同步代碼塊是使用monitorenter和monitorexit指令實現的,同步方法依靠的是方法修飾符上的ACCSYNCHRONIZED實現的。 

1、同步代碼塊:monitorenter指令插入到同步代碼快的開始位置,monitorexit指令插入到同步代碼塊的結束位置,jVM保證每一個monitorexist都有一個monitorenter與之相對應。任何對應都有一個monitor與之相關聯,當且一個monitor被持有之後,他將處於鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象對應的monitor所有權,即嘗試獲取對象的鎖。 

2、同步方法:synchronized方法是在Class文件的方法表中將該方法的accessflags欄位中的synchronized標誌位置為1,表示該方法是同步方法並使用調用該方法的對象或該方法所屬的Class在JVM的內部對象表示Klass作為鎖對象。面試官:你剛提到了每個對象都有一個monitor與之對應,那什麼是Monitor呢?我:我們可以把它理解為一個同步工具,也可以描述為一種同步機制,它通常被描述為一個對象。與一切皆對象一樣,所有的java對象是天生的Monitor,

每一個java對象都有成為Monitor的潛質,因為在Java的設計中,每一個java對象自打娘胎出來就帶了一把看不見的鎖,它被叫做內部鎖或者Monitor鎖。

我:(接著說)Monitor是線程私有的數據結構,每一個線程都有一個可用monitor record列表,同時還有一個全局的可用列表。每一個被鎖住的對象都會和一個monitor關聯(對象頭的MarkWord中的LockWord指向monitor的起始地址),同時monitor中由一個Owner欄位存放擁有該鎖的線程的唯一標識,表示該鎖被這個線程佔用。面試官:很好。我們知道synchronized是悲觀鎖,一直以來被當做重量級鎖。但是jdk1.6對鎖進行了優化,比如**自旋鎖、適應性自旋鎖、鎖消除、偏向鎖以及輕量級鎖**等技術來減少鎖操作的開銷,這些你都了解嗎?

我:知道一些。鎖主要存在四種狀態:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態。他們會隨著競爭的激烈而逐漸升級。注意鎖可以升級不可降級,

這種策略是為了提高獲得鎖和釋放鎖的效率。

面試官:那你先來說下自旋鎖

我:線程的阻塞和喚醒需要CPU從用戶態轉為核心態,頻繁的阻塞和喚醒對CPU來說是一個負擔很重的工作,同時影響系統的並發能力,同時我們發現很多應用上對象鎖的鎖狀態只會持續很短的一段時間,為了這一段很短的時間頻繁的阻塞和喚醒線程是不值得的,所以引入自旋鎖。

何謂自旋鎖呢-就是讓線程等待一段時間,不會被立即掛起,看持有鎖的線程是否會很快釋放鎖。那麼問題來了,等多長時間呢?時間短了等不到持有鎖的線程釋放鎖,時間長了佔用了處理器的時間,典型的「佔著茅坑不拉屎」,反而帶來性能上的浪費。所以,自旋等待的時間(自旋)的次數必須有一個限度,如果自旋超過了定義的時間仍沒有獲得鎖則要被掛起。

面試官:我記得有個適應性自旋鎖,更加智能。你能說下麼?

我:所謂自適應就意味著自旋的次數不再是固定的,它是由上一次在同一個鎖上的自旋時間以及鎖的擁有者的狀態來決定。線程如果自旋成功了,那麼下次自旋的次數會更加多,因為虛擬機認為既然上次成功了,那麼此次自旋也可能成功。反之,如果對於某個鎖,很少有自旋能成功的,那麼以後等待這個鎖的時候自選的次數會減少甚至不自旋。有了自適應自旋鎖,虛擬機對程序鎖的狀況預測越來越準確,虛擬機會越來越聰明。

面試官:給你看下面一段代碼,你說下會存在加鎖的操作嗎?

public static void main(String [] args) {
       Vector<String> vector = new Vector<>();
       for (int i=0; i<10; i++) {
           vector.add(i+"");
       }
       System.out.println(vector);
    

我:不會。這種情況下,JVM檢測到不可能存在共享數據競爭,這時JVM會對這些同步鎖進行鎖消除。鎖消除的基礎是逃逸分析的數據支持。

面試官:再看一段代碼,分析一下是在什麼地方加鎖的?

public static void test() {
       List<String> list = new ArrayList<>();
       for (int i=0; i<10; i++) {
           synchronized (Demo.class) {
                list.add(i + "");
           }
       }
       System.out.println(list);
    }

我:雖然synchronized是在循環裡面,但實際上加鎖的範圍會擴大到循環外,這是鎖粗化。鎖粗化就是將多個連續的加鎖、解鎖操作連接在一起,擴展成一個範圍更大的鎖。

面試官:你能說下輕量級鎖嗎?

我:輕量級鎖提升程序同步性能的依據是:對於絕大部分的鎖,在整個同步周期內是不存在競爭的(區別於偏向鎖),這是一個經驗數據。如果沒有競爭,輕量級鎖使用CAS操作避免了使用互斥量的開銷,但如果存在競爭,除了互斥量的開銷,還額外發生了CAS操作,因此在有競爭的情況下,輕量級鎖比傳統的重量級鎖更慢。

1、在代碼進入同步塊的時候,如果同步對象鎖狀態為無鎖狀態(鎖標誌位為「01」,是否為偏向鎖為「0」),虛擬機首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於存儲對象目前的Mark Word的拷貝,官方稱之為Displaced Mark Word,這時候線程堆棧與對象頭的狀態如圖: 

2、拷貝對象頭中的Mark Word複製到鎖記錄(Lock Record)中。 3、拷貝成功後,虛擬機將使用CAS操作嘗試將鎖對象的Mark Word更新為指向Lock Record的指針,並將線程棧幀中的Lock Record裡的owner指針指向Object的Mark Word。如果這個更新動作成功了,那麼這個線程就擁有了該對象的鎖,並且對象Mark Word的鎖標誌位設置為「00」,表示此對象處於輕量級鎖定狀態。4、如果這個更新操作失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行。否則說明多個線程競爭鎖,輕量級鎖就要膨脹為重量級鎖,鎖標誌位的狀態值變為「10」,Mark Word中存儲的就是指向重量級鎖(互斥量)的指針,後面等待鎖的線程也要進入阻塞狀態。我:偏向鎖的目的是消除數據在無競爭情況下的同步原語,進一步提高程序的運行性能。偏向鎖會偏向於第一個獲得它的線程,如果在接下來的執行過程中,該鎖沒有被其他線程獲取,那持有偏向鎖的線程將永遠不需要同步。

我頓了下,接著說:當鎖第一次被線程獲取的時候,線程使用CAS操作把這個線程的ID記錄在對象Mark Word中,同時置偏向標誌位1.以後該線程在進入和退出代碼塊時不需要進行CAS操作來加鎖和解鎖,只需要簡單測試一下對象頭的Mark Word裡是否存儲著指向當前線程的ID。如果測試成功,表示線程已經獲得了鎖。當有另外一個線程去嘗試獲取這個鎖時,偏向模式就宣告結束。

根據鎖對象目前是否處於被鎖定的狀態,撤銷偏向後恢復到未鎖定或輕量級鎖定狀態。

面試官:那偏向鎖、輕量級鎖和重量級鎖有什麼區別呢?

我:偏向鎖、輕量級鎖都是樂觀鎖,重量級鎖是悲觀鎖。一個對象剛開始實例化的時候,沒有任何線程來訪問它時,它是可偏向的,意味著它認為只可能有一個線程來訪問它,所以當第一個線程訪問它的時候,它會偏向這個線程,此時,對象持有偏向鎖。偏向第一個線程,這個線程在修改對象頭成為偏向鎖的時候使用CAS操作,並將對象頭中的ThreadID改成自己的Id,之後再訪問這個對象只需要對比ID。

一旦有第二個線程訪問這個對象,因為偏向鎖不會釋放,所以第二個線程看到對象是偏向狀態,表明在這個對象上存在競爭了,檢查原來持有該對象的線程是否依然存活,如果掛了,則可以將對象變為無鎖狀態,然後重新偏向新的線程。如果原來的線程依然存活,則馬上執行那個線程的操作棧,檢查該對象的使用情況,如果仍然需要持有偏向鎖,則偏向鎖升級為輕量級鎖(偏向鎖就是此時升級為輕量級鎖)。如果不存在使用了,則可以將對象恢復成無鎖狀態,然後重新偏向。

我:(接著說)輕量級鎖認為競爭存在,但是競爭的程度很輕,一般兩個線程對於同一個鎖的操作都會錯開,或者說自旋一下,另一個線程就會釋放鎖。但是當自旋超過一定次數,或者一個線程持有鎖,一個線程在自旋,又有第三個來訪,輕量級鎖膨脹為重量級鎖,重量級鎖使除了擁有鎖的線程以外的線程都阻塞,防止CPU空轉。簡單的說就是:有競爭,偏向鎖升級為輕量級鎖,競爭逐漸激烈,輕量級鎖升級為重量級鎖。

面試官:你了解java的內存模型嗎?能說下對JMM的理解嗎?

我:在JSR113標準中有有一段對JMM的簡單介紹:Java虛擬機支持多線程執行。在Java中Thread類代表線程,創建一個線程的唯一方法就是創建一個Thread類的實例對象,當調用了對象的start方法後,相應的線程將會執行。線程的行為有時會與我們的直覺相左,特別是在線程沒有正確同步的情況下。本規範描述了JMM平臺上多線程程序的語義,具體包含一個線程對共享變量的寫入何時能被其他線程看到。這是官方的接單介紹。

我:Java內存模型是內存模型在JVM中的體現。這個模型的主要目標是定義程序中各個共享變量的訪問規則,也就是在虛擬機中將變量存儲到內存以及從內存中取出變量這類的底層細節。通過這些規則來規範對內存的讀寫操作,保證了並發場景下的可見性、原子性和有序性。JMM規定了多有的變量都存儲在主內存中,每條線程都有自己的工作內存,線程的工作內存保存了該線程中用到的主內存副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不是直接讀寫主內存。不同線程之間也無法直接訪問對方工作內存中的變量,線程間變量的傳遞均需要自己的工作內存和主存之間

進行數據同步。而JMM就作用於工作內存和主存之間數據同步過程。他規定了如何做數據同步以及什麼時候做數據同步。也就是說Java線程之間的通信由Java內存模型控制,JMM決定一個線程對共享變量的寫入何時對另一個線程可見。

我:簡單的說:Java的多線程之間是通過共享內存進行通信的,而由於採用共享內存進行通信,在通信過程中會存在一系列如原子性、可見性和有序性的問題。JMM就是為了解決這些問題出現的,這個模型建立了一些規範,可以保證在多核CPU多線程編程的環境下,對共享變量的讀寫的原子性、可見性和有序性。

面試官:那你說下Java內存模型的happens-before規則?

我:在JMM中,如果一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須存在happens-before關係。happens-before原則是JMM中非常重要的原則,它是判斷數據是否存在競爭、線程是否安全的主要依據,保證了多線程環境下的可見性。下面我說下happens-before的內容:

happens-before的原則定義如下: 

1、如果一個操作happens-before另一個操作,那麼第一個操作的執行結果將對第二個操作可見,而且第一個操作的執行順序排在第二個操作之前。 2、兩個操作之間存在happens-before關係,並不一定意味著一定要按照happens-before原則制定的順序來執行。如果重排序之後的執行結果與按照happens-before關係來執行的結果一致,那麼這種重排序並不非法。   

下面是happens-before的原則規則: 

1、程序次序規則:一個線程內,按照代碼書寫順序,書寫在前面的操作先行發生於書寫在後面的操作。 

2、鎖定規則:一個unLock操作先行發生於後面對同一個鎖的lock操作。   

3、volatile變量規則:對一個變量的寫操作先行發生於後面對這個變量的讀操作。   

4、傳遞規則:如果操作A先行發生於操作B,而操作B又先行發生於操作C,則可以得出操作A先行發生於操作C。   

5、線程啟動規則:Thread對象的start()方法先行發生於此線程的每個動作。   

6、線程中斷規則:對線程interrupt()方法的調用先行發生於被中斷線程的代碼檢測到中斷事件的發生。   

7、線程終結規則:線程中所有的操作都先行發生於線程的終止檢測。   

8、對象終結規則:一個對象的初始化完成先行發生於它的finalize()方法的開始。   

面試官:你剛才提到了JVM會對我們的程序進行重排序,那是隨便重排序嗎?

我:不是的,它需要滿足以下兩個條件: 

2、存在數據依賴關係的不允許重排序。   

其實這兩點可以歸結為一點:無法通過happens-before原則推導出來的,JMM允許任意的排序。

 我:這裡有必要提到as-if-serial語義:所有的操作都可以為了優化而被重排序,但是你必須保證重排序後執行的結果不能被改變,編譯器、runtime和處理器都必須遵守as-if-serial語義。注意as-if-serial只保證單線程環境,多線程環境下無效。舉個慄子:

int a=1; //A
int b=2; //B
int c=a+b; //C

A,B,C三個操作存在如下關係:A和B不存在數據依賴,A和C,B和C存在數據依賴,因此在重排序的時候:A和B可以隨意排序,但是必須位於C的前面,但無論何種順序,最終結果C都是3.

我接著說:下面舉個重排序對多線程影響的慄子:

public class RecordExample2 {
   int a = 0;
   boolean flag = false;
 
   /**
    * A線程執行
    */
   public void writer(){
       a = 1; // 1
       flag = true; // 2
    }
 
   /**
    * B線程執行
    */
   public void read(){
       if(flag){ // 3
          int i = a + a; // 4
       }
   }}

假如操作1和操作2之間重排序,可能會變成下面這種執行順序: 

按照這種執行順序線程B肯定讀不到線程A設置的a值,在這裡多線程的語義就已經被重排序破壞了。操作3和操作4之間也可以重排序,這裡就不闡述了。但是他們之間存在一個控制依賴的關係,因為只有操作3成立操作4才會執行。當代碼中存在控制依賴性時,會影響指令序列的執行的並行度,所以編譯器和處理器會採用猜測執行來克服控制依賴對並行度的影響。假如操作3和操作4重排序了,操作4先執行,則先會把計算結果臨時保存到重排序緩衝中,當操作3為真時才會將計算結果寫入變量i中。 

面試官:你能給我講下對volatile的理解嗎?

我:講volatile之前,先補充說明下Java內存模型中的三個概念:原子性、可見性和有序性

1、可見性:可見性是指線程之間的可見性,一個線程修改的狀態對另一個線程是可見的。也就是一個線程的修改的結果,另一個線程能夠馬上看到。比如:用volatile修飾的變量,就會具有可見性,volatile修飾的變量不允許線程內部緩存和重排序,即直接修改內存,所以對其他線程是可見的。但這裡要注意一個問題,volatile只能讓被他修飾的內容具有可見性,不能保證它具有原子性。比如volatile int a=0; ++a;這個變量a具有可見性,但是a++是一個非原子操作,也就是這個操作同樣存在線程安全問題。在Java中,volatile/synchronized/final實現了可見性。

2、原子性:即一個操作或者多個操作要麼全部執行並且執行的過程不會被任何因素打斷,要麼都不執行。原子就像資料庫裡的事務一樣,他們是一個團隊,同生共死。看下面一個簡單的慄子:

i=0; //1
j=i; //2
i++; //3
i=j+1; //4


上面的四個操作,只有1是原子操作,其他都不是原子操作。比如2包含了兩個操作:讀取i,將i值賦給j。在Java中synchronized/lock操作中保證原子性。
3、有序性:程序執行的順序按照代碼的先後順序執行。前面JMM中提到了重排序,在java內存模型中,為了效率是允許編譯器和處理器對指令進行重排序,而且重排序不會影響單線程的運行結果,但是對多線程有影響。Java中提供了volatile和synchronized保證有序性。

我:volatile的原理是volatile可以保證線程可見性且提供了一定的有序性,但是無法保證原子性,在JVM底層volatile是採用「內存屏障」來實現的。總結起來就是:

volatile的內存語義是:

1、當寫一個volatile變量時,JMM會把該線程對應的本地內存中的共享變量值立即刷新到主內存中。 2、當讀一個volatile變量時,JMM會把線程的本地內存置為無效,直接從主內存中讀取共享變量。所以volatile的寫內存語義是直接刷新到主內存中,讀內存語義是直接從主內存中讀取---所以才能實現線程可見性。那麼volatile的內存語義是如何實現的呢?對於一般的變量會被重排序,而對於volatile則不能,這樣會影響其內存語義,所以為了實現volatile的內存語義JMM會限制重排序。 1、如果第一個操作為volatile讀,則不管第二個操作是啥,都不能重排序。這個操作確保volatile讀之後的操作不會被編譯器重排序到volatile讀之前。 2、當第二個操作為volatile寫,則不管第一個操作是啥,都不能重排序。這個操作確保了volatile寫之前的操作不會被編譯器重排序到volatile寫之後。 3、當第一個操作為volatile寫,第二個操作為volatile讀,不能重排序。

volatile的底層實現是通過插入內存屏障,但是對於編譯器來說,發現一個最優布置來最小化插入內存屏障的總數幾乎是不可能的,所以JMM採用了保守策略。如下: 

1、在每一個volatile寫操作前插入一個StoreStore屏障。 2、在每一個volatile寫操作後插入一個StoreLoad屏障。 3、在每一個volatile讀操作後插入一個LoadLoad屏障。 4、在每一個volatile讀操作後插入一個LoadStore屏障。 總結:StoreStore屏障->寫操作->StoreLoad屏障->讀操作->LoadLoad屏障->LoadStore屏障。

下面通過一個例子簡單分析下:

面試官:很好,看來你對volatile理解的挺深入的了。我們換個話題,你知道**CAS**嗎,能跟我講講嗎?

我:CAS(Compare And       Swap),比較並交換。整個AQS同步組件,Atomic原子類操作等等都是基於CAS實現的,甚至ConcurrentHashMap在JDK1.8版本中,也調整為CAS+synchronized。

可以說,CAS是整個JUC的基石。如下圖:

我:CAS的實現方式其實不難。在CAS中有三個參數:內存值V、舊的預期值A、要更新的值B,若且唯若內存值V的值等於舊的預期值A時,才會將內存值V的值修改為B,否則什麼也不幹,是一種樂觀鎖。其偽代碼如下:

if (this.value == A) {
   this.value = B
   return true;
} else {
   return false;
}

我:接著我舉了個AtomicInteger的例子,來給面試官闡述CAS的實現。

private static final Unsafe unsafe =Unsafe.getUnsafe();
private static final long valueOffset;
 
static {
   try {
       valueOffset = unsafe.objectFieldOffset
           (AtomicInteger.class.getDeclaredField("value"));
    }catch (Exception ex) { throw new Error(ex); }
}
 
private volatile int value;

1、Unsafe是CAS的核心類,Java無法直接訪問底層作業系統,而是通過本地native方法訪問。不過儘管如此,JVM還是開了個後門:Unsafe,它提供了2、valueOffset:為變量值在內存中的偏移地址,Unsafe就是通過偏移地址來得到數據的原值的。 

3、value:當前值,使用volatile修飾,保證多線程環境下看見的是同一個。 

// AtomicInteger.java
public final int addAndGet(int delta) {
   return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
 
// Unsafe.java
public final int getAndAddInt(Object var1,long var2, int var4) {
   int var5;
   do {
       var5 = this.getIntVolatile(var1, var2);
    }while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
 
    return var5;
}

在方法compareAndSwapInt(var1, var2, var5, var5 + var4)中,有四個參數,分別代表:對象,對象的地址,預期值,修改值。

我:CAS可以保證一次的讀-改-寫操作是原子操作,在單處理器上該操作容易實現,但是在多處理器上實現就有點複雜。CPU提供了兩種方法來實現多處理器的原子操作:總線加鎖或者緩存加鎖。 

1、總線加鎖:總線加鎖就是使用處理器提供的一個LOCK#信號,當一個處理器在總線上輸出此信號時,其他處理器的請求將被阻塞住,那麼該處理器可以獨佔使用共享內存。但是這種處理方式顯然

有點霸道。 

2、緩存加鎖:其實針對上面的情況,我們只需要保證在同一時刻,對某個內存地址的操作是原子性的即可。緩存加鎖,就是緩存在內存區域的數據如果在加鎖期間,當它執行鎖操作寫回內存時,處理器不再輸出#LOCK信號,而是修改內部的內存地址,利用緩存一致性協議來保證原子性。緩存一致性機制可以保證同一個內存區域的數據僅能被一個處理器修改,也就是說當CPU1修改緩存行中的i時使用緩存鎖定,那麼CPU2就不能同時緩存了i的緩存行。 

面試官:那CAS有什麼缺陷嗎?

我:CAS雖然高效的解決了原子問題,但是還是存在一些缺陷的,主要體現在三個方面: 1、循環時間太長:如果自旋CAS長時間不成功,則會給CPU帶來非常大的開銷,在JUC中,有些地方就會限制CAS自旋的次數。 2、只能保證一個共享變量原子操作:看了CAS的實現就知道這只能針對一個共享變量,如果是多個共享變量就只能使用鎖了。或者把多個變量整成一個變量也可以用CAS。 3、ABA問題:CAS需要檢查操作值有沒有發生改變,如果沒有發生改變則更新,但是存在這樣一種情況:如果一個值原來是A,變成了B,然後又變成了A,那麼在CAS檢查的時候會發現沒有改變,但是實質上它已經發生了改變,這就是所謂的ABA問題。對於ABA問題的解決方案是加上版本號,即在每個變量都加上一個版本號,每次改變時加1,即A->B->A,變成1A->2B->3A。例如原子類中AtomicInteger會發生ABA問題,使用AtomicStampedReference可以解決ABA問題。

有段時間沒更《今天面試了嗎》系列了。在面試裡,多線程,並發這塊問的還是非常頻繁的,不過JUC這塊的內容實在太多,一篇文章很難理清楚。今天是第一章節,未完待續...

本文作者:堅持就是勝利,歡迎點擊閱讀原文訪問作者主頁,或者移步下方連結:

https://juejin.im/user/5bee7feee51d4536c03fc698

相關焦點

  • Java並發編程學習前期知識上篇
    Java並發編程-前期準備知識-上我們先來看看幾個大廠真實的面試題:從上面幾個真實的面試問題來看,我們可以看到大廠的面試都會問到並發相關的問題。所以Java並發,這個無論是面試還是在工作中,並發都是會遇到的。
  • 原創】Java並發編程系列01|開篇獲獎感言
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫為什麼要學並發編程我曾聽一個從事15年開發工作的技術人員說過這個不寫並發程序的原則行得通的背景是那個時候基本都是單核處理器,系統並發量很低,藉助資料庫和類似Tomcat這種中間件就可以解決並發問題。如今硬體的驅動和網際網路行業的飛速發展,64核的處理器已經是很常見了,大型互聯廠商的系統並發量輕鬆過百萬,傳統的中間件和資料庫肯定是不能幫我們遮風避雨了,我們只能通過並發編程來解決這些問題。
  • Java並發編程之支持並發的list集合你知道嗎
    Java並發編程之-list集合的並發.我們都知道Java集合類中的arrayList是線程不安全的。那麼怎麼證明是線程不安全的呢?怎麼解決在並發環境下使用安全的list集合類呢?本篇是《凱哥(凱哥Java:kagejava)並發編程學習》系列之《並發集合系列》教程的第一篇:本文主要內容:怎麼證明arrayList不是線程安全的?怎麼解決這個問題?以及遇到問題解決的四個步驟及從源碼來分析作者思路。一:怎麼證明arrayList在並發情況下是線程不安全的呢?
  • JAVA並發編程:線程並發工具類Callable、Future 和FutureTask的使用
    FutureTask 類實現了RunnableFuture 接口,RunnableFuture 繼承了Runnable接口和Future 接口,而FutureTask 實現了RunnableFuture 接口。所以它既可以作為Runnable被線程執行,又可以作為Future 得到Callable 的返回值。FutureTask,可取消的異步計算。
  • Java多線程並發編程中並發容器第二篇之List的並發類講解
    Java多線程並發編程中並發容器第二篇之List的並發類講解概述本文我們將詳細講解list對應的並發容器以及用代碼來測試ArrayList、vector以及CopyOnWriteArrayList在100個線程向list中添加1000個數據後的比較
  • 阿里面試官問我Java線程和作業系統線程什麼關係
    這個問題是安琪拉之前面試被問到的一個問題,正好順著上一篇文章介紹完線程調用時的用戶態和內核態的切換,後續把Java 並發的都一起講了。面試官:聽前一個面試官說你Java並發這塊掌握的不錯,我們深入的交流一下;我:  看了看面試官頭部稀疏的結締組織,已然覺得這場面試不簡單,不過好在事前把安琪拉的博客看了個遍,有所準備,我回答說:咳咳,掌握的還算可以。
  • Java 線程面試題 Top 50
    在典型的Java面試中, 面試官會從線程的基本概念問起, 如:為什麼你需要使用線程, 如何創建線程,用什麼方式創建線程比較好(比如:繼承thread類還是調用Runnable接口),然後逐漸問到並發問題像在Java並發編程的過程中遇到了什麼挑戰,Java內存模型,JDK1.5引入了哪些更高階的並發工具,並發編程常用的設計模式,經典多線程問題如生產者消費者,哲學家就餐,讀寫器或者簡單的有界緩衝區問題
  • 「原創」Java並發編程系列02|並發編程三大核心問題
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫寫在前面編寫並發程序是比較困難的,因為並發程序極易出現Bug,這些Bug有都是比較詭異的,很多都是沒辦法追蹤,而且難以復現。
  • 「原創」Java並發編程系列25|交換器Exchanger(3)
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫正文很尷尬,發了並發編程的26和27,漏了本篇25。這下子我是真的沒存貨了哈哈。那下面我們來補上25先,25比較短小……勿噴。Exchanger是一個用於線程間協作的工具類,用於兩個線程間交換。本篇只介紹Exchanger的使用,不講解源碼。(因Exchanger類的源碼很是複雜,而且對平時開發設計借鑑意義也不是很大,所以本人至今沒有徹底研究明白,也就不在這裡誤導大家了。)
  • 面試總結——Java高級工程師
    尚學堂AD試前面也總結了一和二, 這第三篇可能更偏向於是內心的獨白篇和面試技巧總結吧…..一、獨白之前也面試別人,現在輪到自己找工作,怎麼說呢,每個面試官的看法不一樣,面試的方式就不一樣,比如我面試別人我喜歡問項目中他用到了那些,然後針對用到的技術去問一些問題,或者說對於某些場景的一些技術實現方案是我特別喜歡問的,比如當你的接口服務數據被人截包了,你如何防止數據惡意提交?
  • 「原創」Java並發編程系列28|Copy-On-Write容器
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫正文前面兩篇講了並發編程中線程安全HashMap:ConcurrentHashMap
  • 「原創」Java並發編程系列09|基礎乾貨
    本文轉載自【微信公眾號:java進階架構師,ID:java_jiagoushi】經微信公眾號授權轉載,如需轉載與原文作者聯繫本文為何適原創並發編程系列第9篇。現在,我們進入正題:介紹並發編程的基礎性概念。
  • 工作五年,一年內我靠這系列java面試寶典從13K到大廠30K
    我認為對於面試以及進階最佳的學習方法莫過於刷題+博客+書籍+總結!前三者我將淋漓盡致地揮毫於這篇文章中,至於總結要靠個人。實際上越到後面你越會發現面試並不難,其次就是在刷題的過程中有沒有去思考,刷題只是次之,這又是一個層次了,這裡暫時不提後面再談。
  • Java並發編程之驗證volatile的可見性
    Java並發編程之驗證volatile的可見性通過系列文章的學習,凱哥已經介紹了volatile的三大特性。1:保證可見性 2:不保證原子性 3:保證順序。那麼怎麼來驗證可見性呢?本文凱哥將通過代碼演示來證明volatile的可見性。
  • 知名網際網路公司校招 Java 開發崗面試知識點解析
    區別:JDK 用於開發,JRE 用於運行 java 程序;JDK 和 JRE 中都包含 JVM;JVM 是 java 程式語言的核心並且具有平臺獨立性。Others:限於篇幅,面試中 Java 基礎知識點還有:反射、泛型、註解等。
  • JAVA面試題及答案一百道(SE篇上)——老面試官的經驗之談
    上篇請看這裡-->JAVA面試題及答案一百道(SE篇上)——老面試官的經驗之談本文的面試題裡有部分關於設計模式的題目,但是阿偉要在這裡提醒各位一句話,找工作時面試官的水平可以清晰地反映出這個公司的水平,如果你的面試官抓住設計模式死摳死問,想讓你一成不變的把網上的面試答案背下來
  • Java 面試寶典!並發編程 71 道題及答案全送上!
    作者 | 烏梟責編 | 伍杏玲金三銀四跳槽季即將到來,作為 Java 開發者你開始刷面試題了嗎?別急,我整理了71道並發相關的面試題,看這一文就夠了!1、在java中守護線程和本地線程區別?java中的線程分為兩種:守護線程(Daemon)和用戶線程(User)。任何線程都可以設置為守護線程和用戶線程,通過方法Thread.setDaemon(bool on);true則把該線程設置為守護線程,反之則為用戶線程。
  • 因為不知道Java的CopyOnWriteArrayList,面試官讓我回去等通知
    hello,同學們,大家好,我是沉默王二,在我為數不多的面試經歷中,有一位姓馬的面試官令我印象深刻,九年過去了,我還能記得他為數不多的發量。老馬:「兄弟,ArrayList 是線程安全的嗎?」王二:「不是啊。」老馬:「那有沒有線程安全的 List?」王二:「有啊,Vector。」老馬:「還有別的嗎?」
  • 為什麼用 Java —— 關於並發編程
    題圖:by 張駿峰,科大少年班師兄,小蟻首席架構師前幾天發完《聊聊 Ruby on Rails》那篇文章後,有朋友問到:後臺準備考慮從 Ruby 遷移,問有沒有什麼推薦的語言,尤其是主要需求是大規模高並發,便於維護升級。
  • 你應該要理解的java並發關鍵字volatile
    提高java的並發編程,就不得不提volatile關鍵字,不管是在面試還是實際開發中 volatile都是一個應該掌握的技能。他的重要性不言而喻。因此也有必要學好。一、為什麼要用到volatile關鍵字?