1. 程式人生 > >Java程式碼質量改進之:同步物件的選擇

Java程式碼質量改進之:同步物件的選擇

  在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,就相當於讓非靜態方法具備執行緒安全性,這帶來的一個問題是,如果應用程式中該型別存在多個例項,在遇到這個鎖的時候,都會產生同步,而這可能不是我們原先所願意看到的。

第三點:值型別(基本資料型別)物件不能作為同步物件

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

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

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

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

第五點:降低同步物件的可見性

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

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

相關推薦

Java程式碼質量改進同步物件選擇

  在Java中,讓執行緒同步的一種方式是使用synchronized關鍵字,它可以被用來修飾一段程式碼塊,如下: synchronized(被鎖的同步物件) { // 程式碼塊:業務程式碼 }   當synchronized被用來修飾程式碼塊的時候表示

Java學習(7)同步問題生產者與消費者的問題

con runnable pop pre 標記 this auth style about 生產者生產饅頭,消費者消費饅頭。一個籃子,生產者往籃子中放饅頭,消費者從籃子中取饅頭。 /** * 這是一個籃子類 * * @author xcx * @time 2017

Java程式碼保護探索路系列程式碼加密】之一程式碼加密開篇

程式碼加密也是對Java程式碼進行保護的一種重要方式,作為Java程式碼加密開篇的文章,本文先舉例介紹,如何利用加密演算法實現對.class檔案進行加密。注意為說明基本原理,本文程式採用命令列進行操作,後續會給出具有UI介面的Java類加密軟體。 一

提高你的Java程式碼質量危險的邊界

一、分析在單元測試中,有一項測試叫做邊界測試(也有叫做臨界測試),它能避免出現:數字越界使檢驗條件失效。如果一個方法接受的是Int型別的引數,那一些三個值是必須的:0、正最大、負最大、其中正最大和負最大

提高你的Java程式碼質量優先使用整形池

一、分析包裝型別產生物件的兩種方式:1.new產生的Integer物件new宣告的就是要生成一個物件,沒二話,這就是兩個物件,地址肯定不相等。2.裝箱生成的物件裝箱動作是通過valueOf()方法實現的

提高你的Java程式碼質量避免基本型別陣列轉換列表陷阱(Arrays.asList())

一、分析我們在開發的過程中經常會使用Arrays和Collections這兩個工具類在陣列和列表之間轉換。Arrays.asList()方法:輸入一個變長引數,返回一個固定長度的列表。看原始碼:publ

提高你的Java程式碼質量頻繁插入和刪除時使用LinkedList

一、分析 前面有文章分析了列表的表裡方式,也就是“讀”的操作。本文將介紹表的“寫”操作:即插入、刪除、修改動作。 二、場景 1.插入元素 列表中我們使用最多的是ArrayList,下面

Java並發編程同步容器

linked 作者 alt div 每一個 get方法 string 數組 重要   以下是本文的目錄大綱:   一.為什麽會出現同步容器?   二.Java中的同步容器類   三.同步容器的缺陷   若有不正之處請多多諒解,並歡迎批評指正。   請尊重作者勞動成果,轉載

java技術學習路徑Javaweb監聽器總結(應用場景、方法、配置)

配置 包名 quest ner web.xml 監聽器接口 tty 數據 XML JavaWeb中,監聽器是一種組件,能夠監聽項目的啟動和停止,用戶會話的創建和銷毀,以及各種組件的添加、更新和刪除,能夠通過監聽對象的狀態改變,自動做出反應執行響應代碼。 應用場景: 啟動網站

Java併發(十三)同步屏障CyclicBarrier

CyclicBarrier 的字面意思是可迴圈使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最後一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續幹活。 一、應用舉例 public class CyclicBar

堪比 Java 程式碼除錯的方案用 WebStorm + JetBrains IDE Support 外掛,在 WebStorm 內除錯程式碼

準備工作 WebStorm 的 Javascript Debug 只能選擇 chrome(至少在我的電腦上是這樣),所以,JetBrains IDE Support 外掛需要安裝到 chrome 上。 chrome 最新版下載 安裝外掛,在應用中心,搜尋:JetBrai

Java——執行緒回顧彙總同步/生產者消費者模式/定時排程

  一個程序可以產生多個執行緒。同多個程序可以共享作業系統的某些資源一樣,同一程序的多個執行緒也可以共享此程序的某些資源(比如:程式碼、資料),所以執行緒又被稱為輕量級程序(lightweight process)。       1. 一個程序

Java多執行緒10同步不具有繼承性

父類的同步操作子類是不可以繼承獲得的 package unit2; public class Demo8_tongbubujujichengxing { public static void m

Java JDK原始碼解析native方法

初次看見native關鍵字是自己在看Scanner類原始碼中傳遞System.in引數實現列印,之後轉到System觀看原始碼時看見native關鍵字,關於native關鍵字筆者表示,是Java與C語言的通訊介面,因為Java語言沒有操作底層的條件,所以Java

java程式碼中操作Redis單機redis、叢集redis(spring+redis整合)

一、準備 關於redis的一些安裝,可以檢視我的幾篇文章自行安裝:Redis目錄。匯入java的Redis客戶端依賴包Jedis:<dependency> <groupId>redis.clients</groupId

Java程式碼與架構完美優化-實戰經典》筆記

1.避免在一條語句中宣告或賦值多個變數,一行語句只宣告一個變數,避免int a,b;的情況 2.避免賦予臨時變數過多的角色,一個變數只有一種實際意義,即單一職責。 3.避免使用魔法數字,程式裡避免出現大量直接數字(預設0和1是非魔法數字),魔法數字需要用變數宣告用途,避免直接使用數字。 4.使用bi

Java web學習總結10HttpServletRequest物件1

一、HttpServletRequest介紹   HttpServletRequest物件代表客戶端的請求,當客戶端通過HTTP協議訪問伺服器時,HTTP請求頭中的所有資訊都封裝在這個物件中,通過這個物件提供的方法,可以獲得客戶端請求的所有資訊。 二、Request常用方法

JAVA多執行緒Synchronized關鍵字--物件鎖的特點

一,介紹 本文介紹JAVA多執行緒中的synchronized關鍵字作為物件鎖的一些知識點。 所謂物件鎖,就是就是synchronized 給某個物件 加鎖。關於 物件鎖 可參考:這篇文章 二,分析 synchronized可以修飾例項方法,如下形式: 1 public class My

java程式碼(四)構建Document

這裡的示例程式碼都是簡要程式碼,詳細程式碼可以參考我打包好的程式碼,地址:百度雲 mongo-demo.rar 構建Document Document document = new Do

JAVA程式碼實現多級樹結構封裝物件(2018-09-26補充)

我們經常在程式碼裡會造一個樹結構物件,以方便前端使用。 以地區(區、鎮、村)為例 後臺一般對於樹結構物件在資料庫的結構是這樣的: 主鍵ID 名字 父ID ID REGION_NAME PARENT_ID 121100 尼龍區 0 1211