博客 / 詳情

返回

java研發工程師必知必會

java作為一種跨平台、面向對象的編程語言,廣泛應用於企業級Web開發和移動應用開發。其核心特性包括可移植性(一次編寫,到處運行)、安全性、分佈式支持以及泛型編程能力。又因其簡單易學深受廣大程序員所喜愛。自1995年推出以來,語言也經歷了多次的迭代更新,而對於一些較早接觸該語言的老程序員來説,對一些新特徵甚至語言層面的優化或許並不熟悉,針對一些較為常見的語言特徵,在本文中進行了較為細緻的入門介紹,希望能在學習語言的過程中起到積極的作用。另外由於水平有限,對某些特性的理解或許存在偏差,如果您發現有任何問題,歡迎隨時交流學習。
1、volatile在線程安全中能保證什麼

通常以為,使用volatile變量就可以保證線程安全.事實真的如此嗎?
其實,volatile只能保障的是可見性和排序,並不能保證操作的原子性,更不能防止競態條件(race conditions)的發生.當一個變量被標記為volatile時,1) 某線程對該變量的任何寫入操作會立即對其他線程可見;2) 讀寫操作不會對該變量的相關操作進行重排序.

volatile int i;
i++;

代碼中變量i雖然使用volatile修飾,但其並不是線程安全的,因為i++不是一個原子操作,它包含1 讀取數值;2 加1;3 寫回數值.因volatile修飾變量不能保證原子性,當兩個線程同時執行上述代碼,便會導致線程安全的問題.
2、是否應該選擇synchronized鎖
長久以來的認知告訴我們,synchronized速度慢,要避免使用.選用替代方案concurrent.lock包下的作為替代方案.從jdk1.6開始,針對synchronized的優化已經顯著提高了其性能,jdk1.6引入了”鎖升級”機制, 將鎖的狀態分為四種:‌無鎖 → 偏向鎖 → 輕量級鎖 → 重量級鎖‌。JVM 會根據線程競爭情況動態升級鎖,避免不必要的開銷.
許多情況下, synchronized由JVM優化,比手動鎖定更快,語義也更容易理解.但當多個線程爭用一個synchronized鎖時,速度會變慢.如下代碼中,如果doSomeThing()拋出異常,會發生什麼情況?

synchronized (lock) {
doSomeThing();
}

鎖會自動釋放!
同樣如果使用手動鎖,則必須注意鎖釋放.如使用ReentrantLock,需注意解鎖某個finally區域,否則,一旦異常該區域將永遠保持鎖定狀態,出現死鎖.

lock.lock ();
try {
    doSomeThing ();
} finally {
    lock.unlock () ;
}

3、可以使用Thread.sleep()協調線程嗎

答案是否定的.因為Thread.sleep()並不能保證時間,不能協調狀態,會在機器低速運轉或重載時發生故障.想要正確的協調線程,需使用:
1、wait()/notify();
2、CountDownLatch;
3、CyclicBarrier;
4、CompletebleFuture.
那麼,wait()和sleep()之間有什麼區別呢?
其區別主要包括
1、sleep()不釋放鎖,線程將佔用cpu資源,wait()會釋放監視器鎖,
2、sleep()可以在任何地方使用,而wait()必須在synchronized代碼塊/方法中使用
3、sleep()作用是暫停執行(休眠),而wait()作用是線程間通信
4、sleep()的喚醒條件是超時自動喚醒,而wait()需等待notify()或notifyAll()
用一個日常生活中的場景大致描述一下,sleep()類似於你在牀上看書,看累了抱着書躺下睡覺,雖然你不再看書,但因為書被你抱着,其他人是沒法去看你手中的書.wait()類似你去排隊出地鐵站,在出站前你發現手機支付地鐵票有問題,於是你讓出排隊通道去修復地鐵卡手機支付,其他人依次跟上排隊出站,等你解決了支付問題,又重新排進了出站隊伍中.
在系統中,特別是多線程系統中使用sleep()只會降低系統性能.
4、多線程環境中,雙重檢查一定是安全的嗎?

以下代碼,在多線程環境中,是否存在問題?

public class MyClass {
    private static MyClass instance;
    public static MyClass getInstance() {
        if (instance == null) {
            synchronized (MyClass.class) {
                if (instance == null) {
                    instance = new MyClass();
                }
            }
        }
        return instance;
    }
} 

  答案是肯定的。問題的根本是因為指令重排的存在,而JVM在執行instance = new MyClass();這行代碼時,其操作並不是原子的,通常分為三個步驟: •

• 1.分配內存空間:為對象分配一塊內存。
• 2.初始化對象:在內存中構造對象(執行構造函數)。
• 3.引用賦值:將 instance 變量指向剛才分配的內存地址。
而JVM和CPU為了優化性能,這三個步驟並不是嚴格的順序執行的.這便可能導致另一個線程可能會看到一個半初始化的對象.而修復此問題便可以通過使用添加volatile修飾禁止其重排序修復後代碼如下

public class MyClass {
	// 必須增加 volatile 關鍵字
	private static volatile MyClass instance;

	public static MyClass getInstance() {
		if (instance == null) {
			synchronized (MyClass.class) {
		if (instance == null) {
			instance = new MyClass();
		}
		}
		}
		return instance;
	}
}

5、Java對象創建經歷哪些階段?

Java對象創建的五個階段——類加載檢查、內存分配、零值初始化、對象頭設置和執行<init>方法——在邏輯上是順序執行的。這些步驟共同構成了Java虛擬機(JVM)實例化對象的過程,按照由前到後的順序保證了對象在內存中的正確配置和初始化。
具體細節如下:
• 1. 類加載檢查:虛擬機檢查new指令的參數能否在常量池中定位到一個類的符號引用,並檢查該類是否已加載、解析和初始化。
• 2. 內存分配:在類加載檢查通過後,虛擬機為對象在堆中分配內存。
• 3. 零值初始化:將分配的內存空間初始化為零值(不包含對象頭),保證字段在該階段就能使用默認值。
• 4. 對象頭設置:設置對象頭,包括哈希碼、GC分代年齡、鎖狀態標誌等。
• 5. 執行<init>方法:執行開發者定義的構造函數,按照程序員的意願對對象進行初始化。

 
user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.