Java代碼品質改進之:同步對象的選擇

來源:互聯網
上載者:User

標籤:字元   image   代碼品質   exception   可見   read   abc   子類   編寫   

  在Java中,讓線程同步的一種方式是使用synchronized關鍵字,它可以被用來修飾一段代碼塊,如下:

     synchronized(被鎖的同步對象) {          // 代碼塊:業務代碼     }

  當synchronized被用來修飾代碼塊的時候表示,如果有多個線程正在執行這段代碼塊,那麼需要等到其中一個線程執行完畢,第二個線程才會再執行它。但是!如果被鎖的同步對象沒有被正確選擇的話,上面的結論是不正確的哦。

到底什麼樣的對象能夠成為一個鎖對象(也叫同步對象)?我們在選擇同步對象的時候,應當始終注意以下幾點:

第一點,需要鎖定的對象在多個線程中是可見的、同一個對象

  “可見的”這是顯而易見的,如果對象不可見,就不能被鎖定。“同一個對象”,這理解起來也很好理解,如果鎖定的不是同一個對象,那又如何來同步兩個對象呢?可是,不見得我們在這上面不會犯錯誤。為了闡述本建議,我們先類比一個必須使用到鎖的情境:火車站賣火車票。一列火車一共有100張票,一共有3個視窗在同時賣票,代碼如下:

package com.zuikc.thread;public class SynchronizedSample01 {    public static void main(String[] args) {        // 建立        TicketWindow window1 = new TicketWindow("售票視窗1");        TicketWindow window2 = new TicketWindow("售票視窗2");        TicketWindow window3 = new TicketWindow("售票視窗3");        window1.start();        window2.start();        window3.start();    }}class TicketWindow extends Thread {    // 共100個座位    static int ticket = 100;    public TicketWindow(String name) {        super(name);    }    @Override    public void run() {        // 類比賣票        while (ticket > 0) {            System.out.println(this.getName() + "賣出了座位號:" + ticket);            ticket--;            try {                Thread.sleep(10);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

  可是,運行之後,我們發現我們的火車票有些座位被賣了多次,比如:

  只要多運行幾次,我們就會看到不同的結果。但是幾乎每次都會有被座位號被賣多次的現象發生。

  有同學可能會說,簡單:加synchronized鎖定同步對象,於是我們修改代碼:

class TicketWindow extends Thread {    // 共100個座位    static int ticket = 100;    // 定義被鎖的同步對象    Object obj = new Object();    public TicketWindow(String name) {        super(name);    }    @Override    public void run() {        // 想要同步的代碼塊        synchronized (obj) {            // 類比賣票            while (ticket > 0) {                System.out.println(this.getName() + "賣出了座位號:" + ticket);                ticket--;                try {                    Thread.sleep(10);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        }    }}

       運行之後,我們發現結果沒有任何的改變。為什麼呐?

       因為在3個線程中,我們鎖定的不是同一個對象。

       我們看到,被鎖的是一個執行個體變數,如下:

       Object obj = new Object();

       而存在三個線程,就意味著產生了3個obj,每個線程鎖定的是這3個不同的obj對象,所以,同步代碼塊等於沒有被同步。

       那應該怎麼做呢?最簡單的方法是,我們可以把執行個體變數改成成員變數,即靜態變數,如下:

       Static Object obj = new Object();

       然後,再運行售票代碼,就發現可以解決這個問題了。不信,試試看。

第二個注意事項:非靜態方法中,靜態變數不應作為同步對象

  上面剛說完,要修正第一點中的樣本,需要將obj變成static。這似乎和本注意事項有矛盾。實際上,第一點中的範例程式碼僅出於示範的目的,在編寫多線程代碼時,我們可以遵循這樣的一個原則:類型的靜態方法應當保證安全執行緒,非靜態方法不需實現安全執行緒。而如果將syncObject變成static,就相當於讓非靜態方法具備執行緒安全性,這帶來的一個問題是,如果應用程式中該類型存在多個執行個體,在遇到這個鎖的時候,都會產生同步,而這可能不是我們原先所願意看到的。

第三點:實值型別(基礎資料型別 (Elementary Data Type))對象不能作為同步對象

  實際上,這樣的代碼也不會通過編譯。

實值型別在傳遞另一個線程的時候,會建立一個副本,這相當於每個線程鎖定的也是兩個對象。故,實值型別對象不能作為同步對象。這一點實際也可以歸結到第一點中。

第四點,鎖定字串是完全沒有必要,而且相當危險的

  這整個過程看上去和實值型別正好相反。字串在虛擬機器中會被暫存到記憶體裡,如果有兩個變數被分配了相同內容的字串,那麼這兩個引用會被指向同一塊記憶體。所以,如果有兩個地方同時使用了synchronized (“abc”),那麼它們實際鎖定的是同一個對象,導致整個應用程式被阻滯。

第五點:降低同步對象的可見度

  同步對象一般來說,不應該是一個public變數,我們應該始終考慮降低同步對象的可見度,將我們的同步對象藏起來,只開放給自己或自己的子類就夠了(需要開放給子類的情況其實也不多見)。

以下是廣告時間:最課程(zuikc.com)正在招收Java就業班學員,如果你想學習更多的Java高品質代碼編寫方面的技巧,請聯絡我們哦。

 

Java代碼品質改進之:同步對象的選擇

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.