1. 程式人生 > >面試經歷---S&G(2016年02月27日上午面試)

面試經歷---S&G(2016年02月27日上午面試)

S&G是一家外企,在廣州琶州那邊,下面附上這家企業的筆試題。

2016年2月27日上午筆試題

一.      JAVA基礎題

1.   什麼是介面?

介面就是一些方法特徵的集合,是對物件的抽象。

2.   什麼時候使用抽象類來替代介面?

存在繼承關係的情況下,可以使用抽象類來替代介面。

3.   什麼是final關鍵字?什麼時候能用它?

(1)使用final修飾的類不可以被繼承

(2)使用final修飾的方法不可以被重寫

(3)使用final修飾的變數不可以被重新賦值

       對於一個final變數,如果是基本資料型別的變數,則其數值一旦在初始化之後便不能更改;如果是引用型別的變數,則在對其初始化之後便不能再讓其指向另一個物件,但被引用物件的內容是可以改變的。

4.   如果有一個final collection,比如final List,給它填充資料,那麼list裡面的值可以改變嗎?

List不可以指向其他物件,但list裡面的值是可以改變的。

5.   為什麼說宣告一個final class是一個好的做法?

防止類被繼承。

6.   什麼是不可變和可變的類?

(1)    可變類

當你獲得一個類的例項引用時,你可以改變這個例項的內容。

(2)    不可變類

當你獲得一個類的例項引用時,你不可以改變例項中的內容。

不可變類的例項一旦建立,其內在成員 變數的值就不能被修改。

建立一個不可變類:所以成員都是private,不提供對成員的改變方法,確保所有的方法不會被過載,比如類加上修飾符final(強不可變類),或者所有類方法加上final(弱不可變類)。

7.   將一個物件傳入一個方法,那些引用是傳參引用還是傳值引用?

傳值引用。

函式引數的傳遞分為兩種:“值傳遞”和“引用傳遞”.

“值傳遞”—傳遞原引數的拷貝, 基本資料型別引數,都是值傳遞;函式內部修改此引數,則原資料不變.

“引用傳遞”—傳遞原引數本身, 物件引數,則為引用傳遞;函式內部修改此引數,則原物件更改.

注意:JAVA中函式引數的傳遞為值傳遞。引數為物件時,傳遞的是原引數引用的拷貝,這個引用的拷貝同樣會指向原物件。因此,在函式中對物件引數的修改,能夠體現到原物件。

1.物件就是傳引用

2.原始型別就是傳值

3.String,Integer, Double等immutable型別因為沒有提供自身修改的函式,每次操作都是新生成一個物件,所以要特殊對待。可以認為是傳值。

Integer 和 String 一樣。儲存value的類變數是Final屬性,無法被修改,只能被重新賦值/生成新的物件。 當Integer 做為方法引數傳遞進方法內時,對其的賦值都會導致 原Integer 的引用被 指向了方法內的棧地址,失去了對原類變數地址的指向。對賦值後的Integer物件做得任何操作,都不會影響原來物件。

傳值

如果是基本資料型別,則傳遞的是值

如果是物件,則傳遞的是物件引用的副本,如果只是在方法中修改這個物件的值,則會影響外部的物件;如果修改了引用的指向,則不會影響外部的物件。

public class MyObject {
    private String name;
       public MyObject(Stringname){
       this.setName(name);
    }
 
    public String getName() {
       returnname;
    }
 
    public void setName(String name) {
       this.name =name;
    }
}
 
import java.util.ArrayList;
import java.util.List;
 
public class Test2 {
    public int i = 0;
       public void doSomething1(intn){
       n = 2;
    }
   
    public void doSomething2(String value){
       value = "name2";
    }
   
    public void doSomething3(List<String> list){
       if(list ==null){
           list = new ArrayList<String>();
       }
      
       list.add("value1");
    }
   
    public void doSomething4(MyObject myObject) {
       myObject.setName("name2");
    }
   
    public void doSomething5(MyObject myObject) {
       myObject = new MyObject("name3");
    }
   
    public static void main(String args[]) {
       Test2 test = new Test2();
       int n = 1;
       String value = "name";
       List<String> list = new ArrayList<String>();
       list.add("test");
      
       MyObject myObject = new MyObject("name1");
       MyObject myObject2 = new MyObject("name2");
      
       test.doSomething1(n);
       test.doSomething2(value);
       test.doSomething3(list);
       test.doSomething4(myObject);
       test.doSomething5(myObject2);
      
       System.out.println("n="+n);
       System.out.println("value="+value);
       System.out.println("list="+list.toString());
        System.out.println("myObject name="+myObject.getName());
        System.out.println("myObject2 name="+myObject2.getName());
       
    }

}


輸出的結果是

n=1

value=name

list=[test, value1]

myObject name=name2

myObject2 name=name2

 二.      JVM和記憶體

1.   一般垃圾回收器包含哪些空間?

所有的回收器型別都是基於分代技術。Java HotSpot虛擬機器包含三代,年輕代(Young Generation)、年老代(Old Generation)、永久代(Permanent Generation)。

永久代

儲存類、方法以及它們的描述資訊。可以通過-XX:PermSize=64m和-XX:MaxPermSize=128m兩個可選項指定初始大小和最大值。通常 我們不需要調節該引數,預設的永久代大小足夠了,不過如果載入的類非常多,不夠用了,調節最大值即可。

年老代

主要儲存年輕代中經過多個回收週期仍然存活從而升級的物件,當然對於一些大的記憶體分配,可能也直接分配到永久代(一個極端的例子是年輕代根本就存不下)。

年輕代

絕大多數的記憶體分配回收動作都發生在年輕代。如下圖所示, 年輕代被劃分為三個區域,原始區(Eden)和兩個小的存活區(Survivor),兩個存活區按功能分為From和To。絕大多數的物件都在原始區分配,超過一個垃圾回收操作仍然存活的物件放到存活區。

2.   一般垃圾回收器中堆空間和永久代空間的區別是什麼?

(1)    堆用來儲存例項和陣列,所有的執行緒共享這一區域

(2)    永久代空間用來儲存類、方法以及它們的描述資訊

JVM記憶體結構:方法區+堆+棧+本地方法棧

垃圾回收空間劃分:年輕代+年老代+永久代

年輕代+年老代=堆

永久代=方法區

3.   垃圾回收器中的永久代空間能被回收嗎?

永久代的垃圾回收和老年代的垃圾回收是繫結的,一旦其中一個區域被佔滿,這兩個區都要進行垃圾回收。但是有一個明顯的問題,由於我們可以通過‑XX:MaxPermSize 設定永久代的大小,一旦類的元資料超過了設定的大小,程式就會耗盡記憶體,並出現記憶體溢位錯誤(OOM)。

備註:在JDK7之前的HotSpot虛擬機器中,納入字串常量池的字串被儲存在永久代中,因此導致了一系列的效能問題和記憶體溢位錯誤。

著Java8的到來,我們再也見不到永久代了。但是這並不意味著類的元資料資訊也消失了。這些資料被移到了一個與堆不相連的本地記憶體區域,這個區域就是我們要提到的元空間。

4.   永久代是怎麼樣才會出現記憶體溢位?

永久代主要存放類、方法及其描述資訊,但這些成員的大小超過設定的MaxPermSize時,就會出現記憶體溢位。

也可能通過CGLIB動態生成類,這樣也可能會出現記憶體溢位。

5.   一個物件在什麼情況下才會被垃圾回收器回收?

垃圾回收器每隔一段時間或者達到一定的閥值,就會執行回收操作,如果物件沒被引用時,就會被垃圾回收器回收。

確定物件是垃圾

(1)    引用計數器演算法,JDK1.1後這個演算法淘汰

(2)    根搜尋演算法

根搜尋方法是通過一些GC Roots”物件作為起點,從這些節點開始往下搜尋,搜尋通過的路徑成為引用鏈(Reference Chain),當一個物件沒有被GC Roots的引用鏈連線的時候,說明這個物件是不可用的。

GC Roots物件包括:

a) 虛擬機器棧(棧幀中的本地變量表)中的引用的物件。

b) 方法區域中的類靜態屬性引用的物件。

c) 方法區域中常量引用的物件。

d) 本地方法棧中JNINative方法)的引用的物件。

垃圾回收演算法

(1)    標記-清除演算法

(2)    複製演算法

(3)    標記整理演算法

(4)    分代收集

新生代---標記清除演算法

老年代—整理演算法

6.   什麼是弱引用?

⑴強引用(StrongReference

強引用是使用最普遍的引用。如果一個物件具有強引用,那垃圾回收器絕不會回收它。當記憶體空間不足,Java虛擬機器寧願丟擲OutOfMemoryError錯誤,使程式異常終止,也不會靠隨意回收具有強引用的物件來解決記憶體不足的問題。

⑵軟引用(SoftReference

如果一個物件只具有軟引用,則記憶體空間足夠,垃圾回收器就不會回收它;如果記憶體空間不足了,就會回收這些物件的記憶體。只要垃圾回收器沒有回收它,該物件就可以被程式使用。軟引用可用來實現記憶體敏感的快取記憶體(下文給出示例)。

軟引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果軟引用所引用的物件被垃圾回收器回收,Java虛擬機器就會把這個軟引用加入到與之關聯的引用佇列中。

⑶弱引用(WeakReference

弱引用與軟引用的區別在於:只具有弱引用的物件擁有更短暫的生命週期。在垃圾回收器執行緒掃描它所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。不過,由於垃圾回收器是一個優先順序很低的執行緒,因此不一定會很快發現那些只具有弱引用的物件。

弱引用可以和一個引用佇列(ReferenceQueue)聯合使用,如果弱引用所引用的物件被垃圾回收,Java虛擬機器就會把這個弱引用加入到與之關聯的引用佇列中。

⑷虛引用(PhantomReference

“虛引用”顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定物件的生命週期。如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。

虛引用主要用來跟蹤物件被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用佇列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個物件時,如果發現它還有虛引用,就會在回收物件的記憶體之前,把這個虛引用加入到與之關聯的引用佇列中。

ReferenceQueue queue = new ReferenceQueue ();

PhantomReference pr = new PhantomReference (objectqueue);

程式可以通過判斷引用佇列中是否已經加入了虛引用,來了解被引用的物件是否將要被垃圾回收。如果程式發現某個虛引用已經被加入到引用佇列,那麼就可以在所引用的物件的記憶體被回收之前採取必要的行動。

三.      JAVA語言

1.   String、StringBuilder、SringBuffer的區別是什麼?

String是不可變物件

StringBuilder和StringBuffer都是可變物件,StingBuilder是執行緒不安全的,但效率較高;StringBuffer是執行緒安全的,但效率較低。

2.   new String(“abc”)和”abc”的區別是什麼?

“abc”只建立一個字串物件

new String(“abc”)是建立兩個字串物件

3.   使用doubles來描述價格的陷阱是什麼?

四.      JAVA集合

1.   ArrayList和LinkedList的區別是什麼?什麼條件下會選擇誰?

ArrayList的內部儲存是使用陣列,查詢的效率較高,修改和刪除的效率較低。

LinkedList的內部儲存是使用連結串列,查詢的效率較低,修改和刪除的效率較高。

2.   Hashcode和equals 方法的區別是什麼

每個物件都有一個hashcode,但並不唯一,不同物件的hashcode可能相同。

Equals方法通常是比較兩個物件是否相等,可以使用==號來比較兩個物件的記憶體地址是否相等,也可以通過比較兩個物件的hashcode是判斷是否相等。

3.        HashMap和ConcurrentHashMap的區別是什麼?

HashMap不是執行緒安全的

ConcurrentHashMap加了ReentrantLock鎖,是執行緒安全的。

4.        ConcurrentHashMap是如何工作的?

內部儲存使用陣列+連結串列,物件根據他們的hashcode對陣列的大小取餘數來獲得陣列的位置;如果不同的物件有相同的hashcode,則這些物件存放在一個連結串列中,查詢時通過hashcode獲取陣列的位置,再通過==判斷物件的記憶體地址是否相同來獲取物件。加了ReentrantLock鎖

5.        如果HashMap中出現了衝突怎麼辦?

衝突的鍵放在一個連結串列中。

如果是JDK1.8,連結串列長度大於8,則把連結串列轉換成紅黑樹。

6.        如果HashMap中存在大量的衝突,怎麼辦?

如果是JDK1.8,連結串列長度大於8,則把連結串列轉換成紅黑樹。

7.        Map、Set、List的區別是什麼?

Map是鍵值對

Set是不重複的集合

List是一個數組集合

Set和List都繼承自Collection

五.      JAVA執行緒

1.   有哪些常用方法來防止出現死鎖?

(1)有序的資源分配法,
(2)銀行家演算法。

2.   解釋一下synchronized關鍵字?

Synchronized是給一個物件、方法或程式碼塊加上鎖,如果資源已經被其他執行緒鎖住了,則會一直等待,直到其他執行緒釋放資源。

RetanrantLock是非阻塞鎖,Synchronized是阻塞鎖。

3.   什麼是volatile關鍵字?

JAVA記憶體模型中包括主記憶體(方法區)和工作記憶體(棧),變數的值通常放在主記憶體中,而執行緒在呼叫這一共享變數時,是拷貝一份到自己的工作記憶體中,執行緒對自己工作記憶體中的變數進行讀寫,寫完後再同步到主記憶體中。如果變數使用了volatile關鍵字,可以確保執行緒對工作記憶體中的變數進行修改後,會及時同步到主記憶體,並且同步到其他執行緒的工作記憶體中,儲存執行緒對變數修改的可見性。Volatile是通過禁止編譯的的指令重排序來實現這一功能的。但volatile這一關鍵字並不能保證多執行緒對同一變數同時修改的原子性。

4.   當然呼叫了一個synchronized static method時,我是鎖住了物件例項還是類物件?

鎖住了類物件。

5.   如何建立一個執行緒的?

繼承類Thread,或者實現介面Runnable.

6.   Thread.sleep()和Thread.wait()的區別?

Sleep在睡眠一定的時間後,執行緒會自己醒來,沉睡的過程中佔用CPU.

Wait需要需要其他執行緒來喚醒才會醒來,自己不會醒來,沉睡的過程中不佔用CPU.

六.      JAVA模式

1.   選擇一種模式並且描述他是如何工作的(觀察者模式、策略模式、外觀模式、裝飾模式、訪問者模式)

1.1        觀察者模式Observer

定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽一個主題物件。這個主題物件在狀態發生變化時,會通知所有的觀察者物件,使它們能夠自動更新自己。

應用場景:當一個物件的改變需要同時改變其他物件的時候,而且它不知道具體有多少物件有待改變時,應該考慮使用觀察者模式。

1.2        策略模式Strategy

定義了演算法家庭,分別封裝起來,讓它們之間可以相互替換,此模式讓演算法的變化,不會影響到使用演算法的客戶。

應用場景:經常需要改變計算方式的情況。

1.3        外觀模式Facade

為子系統中的一組介面提供一個一致的介面,此模式定義了一個高層的介面,這個介面使得這一子系統更加容易使用。

比如買股票和買基金,如果是買股票,則需要親力親為去買每一隻股票,如果是買基金,只要購買基金則可,基金中包含了哪些股票則不需要關心。

常用的MVC模式就是使用外觀模式,當一個請求需要同時訪問多個多個service時,可以定義一個Controller介面,在這個介面中呼叫多個service服務。

1.4        裝飾模式Decorator

動態地給一個物件新增一些額外的職責,就增加功能來說,裝飾模式比生成子類更為靈活。

1.5        訪問者模式

表示一個作用於某物件結構中的各元素的操作。它使你可以在不改變各元素的類的前提下定義作用於這些元素的新操作。

七.      其他問題

1.   什麼是IOC?

控制反轉是,關於一個物件如何獲取他所依賴的物件的引用,這個責任的反轉。一個物件所依賴的物件的引用,不是在物件中直接定義,而是通過容器來注入,降低物件之間的耦合度。

呼叫類只依賴介面,而不依賴具體的實現類,減少了耦合。控制權交給了容器,在執行的時候才由容器決定將具體的實現動態的“注入”到呼叫類的物件中。

2.   解釋一下依賴注入?

依賴注入(DependencyInjection)是控制反轉的一種實現方法。JamesShore給出了依賴注入的定義:依賴注入就是將例項變數傳入到一個物件中去。

AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。

八.      Spring

1.   Spring中下面的註解用來做啥的?

a.   Autowired

自動裝載屬性值,不需要在物件中設定屬性的值,而是由容器自動去裝載。

b.   Transactional

自動裝載事務

c.   Bean

將一個物件新增到Spring容器中。

d.   Component/Service/Repository

@Component泛指元件,當元件不好歸類的時候,我們可以使用這個註解進行標註。

@Service用於標註業務層元件

@Repository用於標註資料訪問元件,即DAO元件

e.   Controller

@Controller用於標註控制層元件

f.    RequestMapping

@ RequestMapping用於標識訪問地址

2.   列出Spring 3和 Spring 4的一些差異?

3.   給出一個簡單的Web應用,你會如何做架構設計?

九.      REST

1.   REST中有哪些HTTP 方法?描述一下它們的作用

2.   解釋一下HATEOAS(Hypermedia As The Engine Of Application State)

面試題

1.HashMap產生collision後,怎麼辦?

HashMap的儲存結構是陣列+連結串列,不同hashcode的KEY存放到陣列中的不同位置,同一hashcode的KEY存放到資料組中的同一位置,但用連結串列儲存。

查詢某一KEY時,先取得KEY的hashcode,再通過hashcode計算出陣列的位置,再取出該陣列位置中的連結串列,然後遍歷連結串列看哪一個鍵值與KEY的記憶體地址是一致的,

如果是一致,則視同是同一個KEY,並且取出該KEY對應的VALUE.  不同的KEY可以有相同的hashcode,但他們在記憶體中的地址一定是不同的。

2.如何解決deadlock

目前並沒有能夠很好解決deadlock的方法,常用的可以增加資源供給,使用單執行緒或排隊,但在多執行緒並且資源有限的情況下,只能根據經驗或通過分析JVM中的死鎖來解決。

3.Spring Bean的scope

Singleton,prototype,request,session,global session

後三種只能在WEB應用中使用。

4.HashMap、HashTable、ConcurrentHashMap的區別?

HashMap的KEY和VALUE可以為空,但不是執行緒安全。

HashTable的KEY和VALUE不可以為空,但是執行緒安全,加了synchronized鎖。

ConcurrentHashMap的KEYKEY和VALUE不可以為空,但是執行緒安全,加了分段鎖ReentrantLock,預設16個段。

雖然都是執行緒安全的,但ConcurrentHashMap的效率要比HashTable高。

5. ReentrantLock與Synchronized的區別

(1)ReentrantLock 擁有Synchronized相同的併發性和記憶體語義,此外還多了 鎖投票,定時鎖等候和中斷鎖等候

         執行緒A和B都要獲取物件O的鎖定,假設A獲取了物件O鎖,B將等待A釋放對O的鎖定,
        如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷
        如果使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以後,中斷等待,而幹別的事情
 
(2)ReentrantLock獲取鎖定與三種方式:
    a) lock(), 如果獲取了鎖立即返回,如果別的執行緒持有鎖,當前執行緒則一直處於休眠狀態,直到獲取鎖
    b) tryLock(), 如果獲取了鎖立即返回true,如果別的執行緒正持有鎖,立即返回false;
    c)tryLock(long timeout,TimeUnit unit),   如果獲取了鎖定立即返回true,如果別的執行緒正持有鎖,會等待引數給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false;
    d) lockInterruptibly:如果獲取了鎖定立即返回,如果沒有獲取鎖定,當前執行緒處於休眠狀態,直到或者鎖定,或者當前執行緒被別的執行緒中斷
 
(3)synchronized是在JVM層面上實現的,不但可以通過一些監控工具監控synchronized的鎖定,而且在程式碼執行時出現異常,JVM會自動釋放鎖定,但是使用Lock則不行,lock是通過程式碼實現的,要保證鎖定一定會被釋放,就必須將unLock()放到finally{}中
 

(4)在資源競爭不是很激烈的情況下,Synchronized的效能要優於ReetrantLock,但是在資源競爭很激烈的情況下,Synchronized的效能會下降幾十倍,但是ReetrantLock的效能能維持常態

(5)什麼時候才應該使用 ReentrantLock 
        在確實需要一些 synchronized 所沒有的特性的時候,比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變數或者鎖投票。

6.如何分析程式中是否存在死鎖?

下面寫幾個類來模擬死鎖,分別是兩個資源類ResourceA、ResourceB,以及兩個執行緒MyThread1、MyThread2

public class ResourceA {
	private int count = 1;

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}
}
public class ResourceB {
	private int count = 1;

	public int getCount() {
		return count;
	}

	public void setCount(int count) {
		this.count = count;
	}
}

public class MyThread1 implements Runnable {
	private ResourceA resourceA;
	
	private ResourceB resourceB;

	public MyThread1(ResourceA resourceA, ResourceB resourceB){
		this.resourceA = resourceA;
		this.resourceB = resourceB;
	}

	@Override
	public void run() {
		System.out.println("thread1 ,wait to get lock from resourceA...");
		synchronized (resourceA) {
			System.out.println("thread1,already get lock from resourceA...");
			resourceA.setCount(resourceA.getCount() -1);
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			System.out.println("thread1,wait to get lock from resourceB...");
			synchronized (resourceB) {
				System.out.println("thread1,already get lock from resourceB...");
				resourceB.setCount(resourceB.getCount() -1);
			}
			System.out.println("thread1, already release lock from resourceB...");
		}
		System.out.println("thread1, already release lock from resourceA...");
		
	}

}

public class MyThread2 implements Runnable {
	private ResourceA resourceA;
	
	private ResourceB resourceB;

	public MyThread2(ResourceA resourceA, ResourceB resourceB){
		this.resourceA = resourceA;
		this.resourceB = resourceB;
	}

	@Override
	public void run() {
		System.out.println("thread2 ,wait to get lock from resourceB...");
		synchronized (resourceB) {
			System.out.println("thread2,already get lock from resourceB...");
			resourceB.setCount(resourceB.getCount() -1);
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			System.out.println("thread2 ,wait to get lock from resourceA...");
			synchronized (resourceA) {
				System.out.println("thread2,already get lock from resourceA...");
				resourceA.setCount(resourceA.getCount() -1);
			}
			System.out.println("thread2, already release lock from resourceA...");
		}
		System.out.println("thread2, already release lock from resourceB...");
		
	}

}

public class TestDeadLock {

	public static void main(String[] args) {
		System.out.println("begin....");
		ResourceA resourceA = new ResourceA();
		ResourceB resourceB = new ResourceB();
		MyThread1 myThread1 = new MyThread1(resourceA, resourceB);
		MyThread2 myThread2 = new MyThread2(resourceA, resourceB);
		
		Thread thread1 = new Thread(myThread1);
		Thread thread2 = new Thread(myThread2);
		thread1.start();
		thread2.start();
		
		System.out.println("end....");

	}

}

程式執行的結果是
begin....
thread1 ,wait to get lock from resourceA...
thread1,already get lock from resourceA...
end....
thread2 ,wait to get lock from resourceB...
thread2,already get lock from resourceB...
thread1,wait to get lock from resourceB...
thread2 ,wait to get lock from resourceA...

(1)通過jps找到當前程序號

C:\Program Files\Java\jdk1.7.0_17\bin>jps
6284 TestDeadLock
7168 Jps
5628

可以發現TestDeadLock這個類的程序號是6284

(2)通過jstack檢視堆疊資訊

查出程序號是6284的棧資訊(執行緒儲存在JVM的棧中)

C:\Program Files\Java\jdk1.7.0_17\bin>jstack -l 6284
2016-03-07 23:58:00
Full thread dump Java HotSpot(TM) Client VM (23.7-b01 mixed mode, sharing):

"DestroyJavaVM" prio=6 tid=0x00879400 nid=0x774 waiting on condition [0x00000000
]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Thread-1" prio=6 tid=0x02b57800 nid=0x1634 waiting for monitor entry [0x02fdf00
0]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.MyThread2.run(MyThread2.java:27)
        - waiting to lock <0x22bf7fc0> (a com.ResourceA)
        - locked <0x22bf9080> (a com.ResourceB)
        at java.lang.Thread.run(Thread.java:722)

   Locked ownable synchronizers:
        - None

"Thread-0" prio=6 tid=0x02b57400 nid=0x82c waiting for monitor entry [0x02f8f000
]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.MyThread1.run(MyThread1.java:27)
        - waiting to lock <0x22bf9080> (a com.ResourceB)
        - locked <0x22bf7fc0> (a com.ResourceA)
        at java.lang.Thread.run(Thread.java:722)

   Locked ownable synchronizers:
        - None

"Service Thread" daemon prio=6 tid=0x02b18c00 nid=0x1290 runnable [0x00000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C1 CompilerThread0" daemon prio=10 tid=0x02b13000 nid=0x175c waiting on conditi
on [0x00000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Attach Listener" daemon prio=10 tid=0x02b11400 nid=0x16d8 waiting on condition
[0x00000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Signal Dispatcher" daemon prio=10 tid=0x02b0fc00 nid=0x1f44 runnable [0x0000000
0]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Finalizer" daemon prio=8 tid=0x02af5000 nid=0x1f30 in Object.wait() [0x02daf000
]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x22b60fb8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135)
        - locked <0x22b60fb8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)

   Locked ownable synchronizers:
        - None

"Reference Handler" daemon prio=10 tid=0x02af0400 nid=0x1b78 in Object.wait() [0
x02d5f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x22b60da0> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:503)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133)
        - locked <0x22b60da0> (a java.lang.ref.Reference$Lock)

   Locked ownable synchronizers:
        - None

"VM Thread" prio=10 tid=0x02aee800 nid=0x1cf0 runnable

"VM Periodic Task Thread" prio=10 tid=0x02b1c000 nid=0xc44 waiting on condition


JNI global references: 118


Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x02af4d24 (object 0x22bf7fc0, a com.ResourceA),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x02af345c (object 0x22bf9080, a com.ResourceB),
  which is held by "Thread-1"

Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.MyThread2.run(MyThread2.java:27)
        - waiting to lock <0x22bf7fc0> (a com.ResourceA)
        - locked <0x22bf9080> (a com.ResourceB)
        at java.lang.Thread.run(Thread.java:722)
"Thread-0":
        at com.MyThread1.run(MyThread1.java:27)
        - waiting to lock <0x22bf9080> (a com.ResourceB)
        - locked <0x22bf7fc0> (a com.ResourceA)
        at java.lang.Thread.run(Thread.java:722)

<span style="color:#ff0000;">Found 1 deadlock.</span>

可以看得出,執行緒MyThread1鎖住了資源ResourceA,並且請求資源ResourceB;執行緒MyThread2鎖住了資源ResourceB,並且請求資源ResourceA,這樣就匯入了死鎖。

解決這種死鎖的辦法是通過RestrantLock替代synchronized。

待續...