java併發面試題

多線程

  1. java中有幾種方法可以實現一個線程?   
            
    答:在Java中實現一個線程有兩種方法,第一是實現Runnable接口實現它的run()方法,第二種是繼承Thread類,覆蓋它的run()方法。這兩種方法的區別是,如果你的類已經繼承了其它的類,那麼你只能選擇實現Runnable接口了,因爲Java只允許單繼承的。
  2. 如何停止一個正在運行的線程?
            
    答:當不阻塞時候設置一個標誌位,讓代碼塊正常運行結束並停止線程。如果發生了阻塞,用interupt()方法,Thread.interrupt()方法不會中斷一個正在運行的線程。這一方法實際上完成的是,在線程受到阻塞時拋出一箇中斷信號,這樣線程就得以退出阻塞的狀態。
  3. notify()和notifyAll()有什麼區別?
            答:1)notify()和notifyAll()都是Object對象用於通知處在等待該對象的線程的方法。
                   2)void notify(): 喚醒一個正在等待該對象的線程。
                   3)void notifyAll(): 喚醒所有正在等待該對象的線程。
            兩者的最大區別在於:
                   notifyAll使所有原來在該對象上等待被notify的線程統統退出wait的狀態,變成等待該對象上的鎖,一旦該對象被解鎖,他們就會去競爭。
             notify他只是選擇一個wait狀態線程進行通知,並使它獲得該對象上的鎖,但不驚動其他同樣在等待被該對象notify的線程們,當第一個線程運行完畢以後釋放對象上的鎖,此時如果該對象沒有再次使用notify語句,即便該對象已經空閒,其他wait狀態等待的線程由於沒有得到該對象的通知,繼續處在wait狀態,直到這個對象發出一個notify或notifyAll,它們等待的是被notify或notifyAll,而不是鎖。

  4. sleep()和 wait()有什麼區別?
           
    答:sleep是線程類(Thread)的方法,導致此線程暫停執行指定時間,給執行機會給其他線程,但是監控狀態依然保持,到時後會自動恢復。調用sleep不會釋放對象鎖。
    wait是Object類的方法,對此對象調用wait方法導致本線程放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象發出notify方法(或notifyAll)後本線程才進入對象鎖定池準備獲得對象鎖進入運行狀態。
  5. 什麼是Daemon線程?它有什麼意義?
           
    答:所謂後臺(daemon)線程,是指在程序運行的時候在後臺提供一種通用服務的線程,並且這個線程並不屬於程序中不可或缺的部分。因此,當所有的非後臺線程介紹時,程序也就終止了,同時會殺死進程中的所有後臺線程。反過來說,只要有任何非後臺線程還在運行,程序就不會終止。必須在線程啓動之前調用setDaemon()方法,才能把它設置爲後臺線程。注意:後臺進程在不執行finally子句的情況下就會終止其run()方法。
  6. java如何實現多線程之間的通訊和協作?
          答: Java提供了3個非常重要的方法來巧妙地解決線程間的通信問題。這3個方法分別是:wait()、notify()和notifyAll()。它們都是Object類的最終方法,因此每一個類都默認擁有它們。雖然所有的類都默認擁有這3個方法,但是只有在synchronized關鍵字作用的範圍內,並且是同一個同步問題中搭配使用這3個方法時纔有實際的意義。這些方法在Object類中聲明的語法格式如下所示:
         
    1. final void wait() throws InterruptedException  
    2. final void notify()  
    3. final void notifyAll()  

  1. 什麼是可重入鎖(ReentrantLock)? 

          答:  java.util.concurrent.lock 中的 Lock 框架是鎖定的一個抽象,它允許把鎖定的實現作爲 Java 類,而不是作爲語言的特性來實現。這就爲 Lock 的多種實現留下了空間,各種實現可能有不同的調度算法、性能特性或者鎖定語義。 ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的併發性和內存語義,但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的性能。(換句話說,當許多線程都想訪問共享資源時,JVM 可以花更少的時候來調度線程,把更多時間用在執行線程上。)
              reentrant 鎖意味着什麼呢?簡單來說,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放。這模仿了 synchronized 的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者後續)synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個 synchronized 塊時,才釋放鎖。

  2. 當一個線程進入某個對象的一個synchronized的實例方法後,其它線程是否可進入此對象的其它方法?
             答:A、一個線程在訪問一個對象的同步方法時,另一個線程可以同時訪問這個對象的非同步方法
                    B、 一個線程在訪問一個對象的同步方法時,另一個線程不能同時訪問這個同步方法。
  3. synchronized和java.util.concurrent.locks.Lock的異同?
             答:Lock 和 synchronized 有一點明顯的區別 —— lock 必須在 finally 塊中釋放。否則,如果受保護的代碼將拋出異常,鎖就有可能永遠得不到釋放!這一點區別看起來可能沒什麼,但是實際上,它極爲重要。忘記在 finally 塊中釋放鎖,可能會在程序中留下一個定時炸彈,當有一天炸彈爆炸時,您要花費很大力氣纔有找到源頭在哪。而使用同步,JVM 將確保鎖會獲得自動釋放。
  4. 樂觀鎖和悲觀鎖的理解及如何實現,有哪些實現方式?

併發框架

  1. SynchronizedMap和ConcurrentHashMap有什麼區別?
            答:java5中新增了ConcurrentMap接口和它的一個實現類ConcurrentHashMap。ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的鎖機制。比起synchronizedMap來,它提供了好得多的併發性。多個讀操作幾乎總可以併發地執行,同時進行的讀和寫操作通常也能併發地執行,而同時進行的寫操作仍然可以不時地併發進行(相關的類也提供了類似的多個讀線程的併發性,但是,只允許有一個活動的寫線程)。Hashtable中採用的鎖機制是一次鎖住整個hash表,從而同一時刻只能由一個線程對其進行操作;而ConcurrentHashMap中則是一次鎖住一個桶。ConcurrentHashMap默認將hash表分爲16個桶,諸如get,put,remove等常用操作只鎖當前需要用到的桶。這樣,原來只能一個線程進入,現在卻能同時有16個寫線程執行,併發性能的提升是顯而易見的。前面說到的16個線程指的是寫線程,而讀操作大部分時候都不需要用到鎖。只有在size等操作時才需要鎖住整個hash表。
           在迭代方面,ConcurrentHashMap使用了一種不同的迭代方式。在這種迭代方式中,當iterator被創建後集合再發生改變就不再是拋出ConcurrentModificationException,取而代之的是在改變時new新的數據從而不影響原有的數據 ,iterator完成後再將頭指針替換爲新的數據 ,這樣iterator線程可以使用原來老的數據,而寫線程也可以併發的完成改變。
  2. CopyOnWriteArrayList可以用於什麼應用場景?
            答:CopyOnWriteArrayList(免鎖容器)的好處之一是當多個迭代器同時遍歷和修改這個列表時,不會拋出ConcurrentModificationException。在CopyOnWriteArrayList中,寫入將導致創建整個底層數組的副本,而源數組將保留在原地,使得複製的數組在被修改時,讀取操作可以安全地執行。

線程安全

  1. 什麼叫線程安全?servlet是線程安全嗎?
           答:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。 或者說:一個類或者程序所提供的接口對於線程來說是原子操作或者多個線程之間的切換不會導致該接口的執行結果存在二義性,也就是說我們不用考慮同步的問題。
                servlet不是線程安全的,每個servlet都只被實例化一次,每個調用都是servlet的同一個實例,並且對類變量沒有線程安全,數據量大的時候容易照成異常。
  2. 同步有幾種實現方法?
         
    答:同步的實現方面有兩種,分別是synchronized,wait與notify
  3. volatile有什麼用?能否用一句話說明下volatile的應用場景?
       答:Volatile 變量具有 synchronized 的可見性特性,但是不具備原子特性。可以被看作是一種 “程度較輕的 synchronized”;與 synchronized 塊相比,volatile 變量所需的編碼較少,並且運行時開銷也較少,但是它所能實現的功能也僅是 synchronized 的一部分。
              您只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:
                              A. 對變量的寫操作不依賴於當前值。
                              B. 該變量沒有包含在具有其他變量的不變式中。
  4. 請說明下java的內存模型及其工作流程。
       答:Java把內存劃分成兩種:一種是棧內存,一種是堆內存。
              棧內存:存放對象:函數中基本類型的變量和對象的引用變量、靜態類方法 ;特點:棧有一個很重要的特殊性,就是存在棧中的數據可以共享。
             堆內存:存放對象:用來存放由new創建的對象和數組;特點:在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。
              java 內存模型 ( java memory model ):根據Java Language Specification中的說明, jvm系統中存在一個主內存(Main Memory或Java Heap Memory),Java中所有對象成員變量都儲存在主存中,對於所有線程都是共享的。每條線程都有自己的工作內存(Working Memory),工作內存中保存的是主存中某些對象成員變量的拷貝,線程對所有對象成員變量的操作都是在工作內存中進行,線程之間無法相互直接訪問,變量傳遞均需要通過主存完成。
                    (1) 獲取對象監視器的鎖(lock)
                    (2) 清空工作內存數據, 從主存複製對象成員變量到當前工作內存, 即同步數據 (read and load)
                    (3) 執行代碼,改變共享變量值 (use and assign)
                    (4) 將工作內存數據刷回主存 (store and write)
                    (5) 釋放對象監視器的鎖 (unlock)

  5. 爲什麼代碼會重排序?

併發容器和框架

  1. 如何讓一段程序併發的執行,並最終彙總結果?
     答:使用CyclicBarrier 和CountDownLatch都可以,使用CyclicBarrier 在多個關口處將多個線程執行結果彙總,CountDownLatch 在各線程執行完畢後向總線程彙報結果。
  2. 如何合理的配置java線程池?如CPU密集型的任務,基本線程池應該配置多大?IO密集型的任務,基本線程池應該配置多大?用有界隊列好還是無界隊列好?任務非常多的時候,使用什麼阻塞隊列能獲取最好的吞吐量?
     答:1)配置線程池時CPU密集型任務可以少配置線程數,大概和機器的cpu核數相當,可以使得每個線程都在執行任務
            2)IO密集型時,大部分線程都阻塞,故需要多配置線程數,2*cpu核數
            3)有界隊列和無界隊列的配置需區分業務場景,一般情況下配置有界隊列,在一些可能會有爆發性增長的情況下使用無界隊列。
            4)任務非常多時,使用非阻塞隊列使用CAS操作替代鎖可以獲得好的吞吐量。
  3. 如何使用阻塞隊列實現一個生產者和消費者模型?
  4. 多讀少寫的場景應該使用哪個併發容器,爲什麼使用它?比如你做了一個搜索引擎,搜索引擎每次搜索前需要判斷搜索關鍵詞是否在黑名單裏,黑名單每天更新一次。
     答:1)CopyOnWriteArrayList這個容器適用於多讀少寫…
            2)讀寫並不是在同一個對象上。在寫時會大面積複製數組,所以寫的性能差,在寫完成後將讀的引用改爲執行寫的對象

Java中的鎖

  1. 如何實現樂觀鎖(CAS)?如何避免ABA問題?
     答:1)讀取內存值的方式實現了樂觀鎖(比如:SVN系統),方法:第一,比較內存值和期望值;第二,替換內存值爲要替換值。
            2)帶參數版本來避免aba問題,在讀取和替換的時候進行判定版本是否一致
                       
  2. 讀寫鎖可以用於什麼應用場景?
     答: 讀寫鎖可以用於 “多讀少寫” 的場景,讀寫鎖支持多個讀操作併發執行,寫操作只能由一個線程來操作
            ReadWriteLock對向數據結構相對不頻繁地寫入,但是有多個任務要經常讀取這個數據結構的這類情況進行了優化。ReadWriteLock使得你可以同事有多個讀取者,只要它們都不試圖寫入即可。如果寫鎖已經被其他任務持有,那麼任何讀取者都不能訪問,直至這個寫鎖被釋放爲止。
            ReadWriteLock 對程序心性能的提高受制於如下幾個因素也還有其他等等的因素。
              1)數據被讀取的頻率與被修改的頻率相比較的結果。
              2)讀取和寫入的時間
              3)有多少線程競爭
              4)是否在多處理機器上運行
  3. 什麼時候應該使用可重入鎖?
     答:重入鎖指的是在某一個線程中可以多次獲得同一把鎖,在線程中多次操作有鎖的方法。
  4. 什麼場景下可以使用volatile替換synchronized?
     答: 只需要保證共享資源的可見性的時候可以使用volatile替代,synchronized保證可操作的原子性一致性和可見性。volatile適用於新值不依賴於就值的情形。
          volatile是java提供的一種同步手段,只不過它是輕量級的同步,爲什麼這麼說,因爲volatile只能保證多線程的內存可見性,不能保證多線 程的執行有序性。而最徹底的同步要保證有序性和可見性,例如synchronized。任何被volatile修飾的變量,都不拷貝副本到工作內存,任何 修改都及時寫在主存。因此對於Valatile修飾的變量的修改,所有線程馬上就能看到,但是volatile不能保證對變量的修改是有序的。volatile存在的意義是,任何線程對某個變量的修改,都會馬上被其他線程讀取到,因爲直接操作主存, 沒有線程對工作內存和主存的同步。所以,volatile的使用場景是有限的,在有限的一些情形下可以使用 volatile 變量替代鎖(
    synchronized)。
           要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:

                   1)對 變量的寫操作不依賴於當前值。
                   2)該變量沒有包含在具有其他變量的不變式中
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章