上一篇文章我們主要介紹了java多線程中的一些核心概念。今天我繼續介紹線程中的其他知識。
線程同步互斥
多個線程同時訪問或操作同一資源時,很容易出現數據前後不一致的問題。請看下面的例子:
男孩拿著存摺子去銀行取錢,女孩拿著男孩的銀行卡去西單百貨瘋狂購物男孩走到櫃檯前詢問帳戶餘額,銀行的業務員小姐告訴他:"您還有10000元!"。
女孩看上了一件時髦的衣裳,準備買下。
男孩在思考要取多少錢呢?
女孩到收銀臺準備刷卡消費,收銀臺刷卡機讀取銀行卡餘額為10000元,女孩買衣服刷卡消費5000元。消費清單列印出來,消費:5000元 餘額:5000元。女孩離開商場。
男孩思考了1秒,男孩決定取5000元。銀行的業務員小姐為男孩辦理相關業務手續交易完成銀行的業務員小姐告訴男孩:"您的餘額為5000元",男孩離開銀行。
男孩帳戶中一共有10000元,男孩拿著存摺從銀行取走5000元,女孩拿著男孩的銀行卡購物刷卡消費5000元,最後男孩的帳戶裡卻還剩5000元。顯然這是不正確的,但是為什麼會發生這樣的情況呢?
我們可以這樣分析:男孩可以看作是一條線程,女孩也可以看作是一條線程,在同一時刻,兩個線程都操作了同一個資源,那就是男孩的帳戶。男孩從查看帳戶餘額到取走現金應該被看作是個原子性操作,是不可再分的,然而當男孩查看完餘額正思考取多少錢的時候,女孩購物消費了5000元,也就是說女孩這條線程打斷了男孩這條線程所要執行的任務。所以男孩剛查看完的餘額10000元就不正確了,最終導致帳戶中少減了5000元。
為了避免這樣的事情發生,我們要保證線程同步互斥,所謂同步互斥就是:並發執行的多個線程在某一時間內只允許一個線程在執行以訪問共享數據。
Java中線程互斥的實現機制
由多線程帶來的性能改善是以可靠性為代價的,所以編程出線程安全的類代碼是十分必要的。當多個線程可以訪問共享資源(調用單個對象的屬性和方法,對數據進行讀、寫、修改、刪除等操作)時,應保證同時只有一個線程訪問共享數據,Java對此提出了有效的解決方案—同步鎖。任何線程要進入同步互斥方法(訪問共享資源的方法或代碼段)時,就必須得到這個共享資源對象的鎖,線程進入同步互斥方法後其它線程則不能再進入同步互斥方法,直到擁有共享資源對象鎖的線程執行完同步互斥方法釋放了鎖,下一個線程才能進入同步互斥方法被執行。
Java的這一線程互斥的實現機制可以用一個最通俗的比方來說明:比如公共衛生間就是一個共享資源,每個人都可以使用,但又不能同時使用,所以衛生間裡有一把鎖。一個人進去了,會把門鎖上,其他人就不能進去。當Ta出來的時候,要打開鎖,下一個人才能繼續使用。
利用Synchronized關鍵字用於修飾同步互斥方法
由於synchronized 塊可以針對任意的代碼塊,且可任意指定上鎖的對象,因此靈活性較高。但要注意:
synchronized可以用來限定一個方法或一小段語句或整個類(該類中的所有方法都是synchronized方法)將訪問共享數據的代碼設計為synchronized方法由於可以通過 private 關鍵字來保證數據對象只能被方法訪問,所以只需針對方法提出一套同步鎖定機制。通過synchronized 方法來控制對類中的成員變量(共享數據)的訪問。編寫線程安全的代碼會使系統的總體效率會降低,要適量使用只有某一個線程的synchronized方法執行完後其它線程的synchronized方法才能被執行。當前時間,只有一個線程訪問被鎖定的代碼段,但不能保證其他線程去訪問其他沒有被鎖定的代碼段。因此所有對共享資源進行操作的代碼段都應該加鎖。對資料庫操作時,修改數據的線程要加鎖,而讀數據的線程可以不加鎖高效利用線程-線程池
線程池的好處在於:
(1)重用存在的線程,減少對象創建、消亡的開銷,性能佳。
(2)可有效控制最大並發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。
(3)提供定時執行、定期執行、單線程、並發數控制等功能。
精品推薦:
一起學JAVA——常用類
一起學JAVA——異常處理
一起學JAVA——面向對象(三)
編程技術筆記——Docker試用