中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java中鎖和synchronized的概念和作用

發布時間:2021-06-23 09:20:01 來源:億速云 閱讀:168 作者:chen 欄目:大數據

本篇內容主要講解“java中鎖和synchronized的概念和作用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“java中鎖和synchronized的概念和作用”吧!

鎖的常見概念

  • 互斥: 同一時刻只有一個線程執行

  • 臨界區:一段需要互斥執行的代碼

  • 細粒度鎖: 用不同的鎖對受保護資源進行精細化管理。 細粒度鎖可以提高并行度,是性能優化的一個重要手段

  • 死鎖 :一組互相競爭資源的線程因互相等待,導致“永久”阻塞的現象 。

用鎖的最佳實踐
  1. 永遠只再更新對象的成員變量時加鎖。

  2. 永遠只在訪問可變的成員變量時加鎖。

  3. 永遠不再調用其它對象的方法時加鎖。

  4. 減少所得持有時間,減小鎖的粒度。

同步與異步
  • 調用方法如果需要等待結果,就是同步;如果不需要等待結果就是異步。

  • 同步是Java代碼默認的處理方式。

如何實現程序支持異步:

  1. 異步調用: 調用方創建一個子線程,再子線程中執行方法調用。

  2. 異步方法: 被調用方;方法實現的時候,創建一個顯得線程執行主要邏輯,主線程直接return。

synchronized
class  X{
	//修飾非靜態方法
	synchronized void foo(){
 	   //臨界區
	}
	//修飾靜態方法
	synchronized static void bar(){
 	   //臨界區
	}
	
	//修飾代碼塊
	Object obj = new Object();
	void baz(){
   		synchronized(obj){
        	//臨界區
    	}
	}
}

Java編譯器會在synchronized修飾的方法或代碼塊前后自動加上加鎖lock()和解鎖unlock(),這樣做的好處就是加鎖lock()和解鎖unlock()一定 是成對出現的,畢竟忘記解鎖unlock()可是個致命的Bug(意味著其他線程只能死等下去了)。

修飾靜態方法:
//修飾靜態方法是用當前類的字節碼文件作為鎖
class  X{
	//修飾靜態方法
	synchronized(X.class) static void bar(){
 	   //臨界區
	}
}
修飾非靜態方法:
//修飾非靜態方法是用當前對象作為鎖
class  X{
	//修飾非靜態方法
	synchronized(this) static void bar(){
 	   //臨界區
	}
}

如何用一把鎖保護多個資源

受保護資源和鎖之間合理的關聯關系應該是N:1的關系,也就是說可以用一把鎖來保護多個資源,但是不能用多把鎖來保護一個資源,

使用鎖的正確姿勢

依轉賬業務作為示例

示例一:

public class Account {
    /**
     *鎖:保護賬?余額
     */
    private	final	Object	balLock	= new Object();
    /**
     * 賬?余額
     */
    private	Integer	balance;
   
    /**
     * 錯誤的做法
     * 非靜態方法的鎖是this, 
     * this這把鎖可以保護自己的余額this.balance,保護不了別人的余額 target.balance
     * 
     */
   synchronized void transfer(Account target,int amt){
        if (this.balance > amt) {
            this.balance -= amt;
            target.balance += amt;//這段代碼會出現線程安全,要保證線程安全的話要使用同一個鎖
        }
    }
}

示例二:

public class Account {
    /**
     *鎖:保護賬?余額
     */
    private	final	Object	balLock	= new Object();
    /**
     * 賬?余額
     */
    private	Integer	balance;
   

    /**
     * 正確的做法,但是會導致整個轉賬系統的串行
     *
     * Account.class是所有Account對象共享的,
     * 而且這個對象是Java虛擬機在加載Account類的時候創建的,
     * 所以我們不用擔心它的唯一性
     *
     * 這樣還有個弊端:所有的轉賬都是串行了
     */
    void transfer2(Account target,int amt){
        synchronized(Account.class){
            if (this.balance > amt) {
                this.balance -= amt;
                target.balance += amt;
            }
        }
    }
}

這樣的話轉賬操作就成了串行的了,正常的邏輯應該只鎖轉入賬號和被轉入賬戶;不影響其他的轉賬操作。稍作改造:

示例三:

public class Account {
    /**
     *鎖:保護賬?余額
     */
    private	final Object lock;
    /**
     * 賬?余額
     */
    private	Integer	balance;
   
    //私有化無參構造
    private Account(){}
    //設置一個傳遞lock的有參構造
    private Account(Object lock){
        this.lock = lock;
    }
    
    /**
     * 轉賬
     */
    void transfer(Account target,int amt){
        //此處檢查所有對象共享鎖
        synchronized(lock){
            if (this.balance > amt) {
                this.balance -= amt;
                target.balance += amt;
            }
        }
    }
}

這個方法雖然能夠解決問題,但是它要求創建Account對象的時候必須傳入同一個對象,

還有就是傳遞對象過于麻煩,寫法繁瑣缺乏可行性。

示例四:

public class Account {
    
    /**
     * 賬?余額
     */
    private	Integer	balance;
    
    /**
     * 轉賬
     */
    void transfer(Account target,int amt){
        //此處檢查所有對象共享鎖
        synchronized(Account.class){
            if (this.balance > amt) {
                this.balance -= amt;
                target.balance += amt;
            }
        }
    }
}

用Account.class作為共享的鎖,鎖定的范圍太大。 Account.class是所有Account對象共享的,而且這個對象是Java虛擬機在加載Account類的時候創建的,所以我們不用擔心它的唯一性。使用Account.class作為共享的鎖,我們就無需在創建Account對象時傳入了。

這樣新的問題就出來了雖然用Account.class作為互斥鎖,來解決銀行業務里面的轉賬問題,雖然這個方案不存在 并發問題,但是所有賬戶的轉賬操作都是串行的,例如賬戶A轉賬戶B、賬戶C轉賬戶D這兩個轉賬操作現實 世界里是可以并行的,但是在這個方案里卻被串行化了,這樣的話,性能太差。所以如果考慮并發量這種方法也不行的

正確的寫法是這樣的(使用細粒度鎖):

示例五:

public class Account {
    
    /**
     * 賬?余額
     */
    private	Integer	balance;
    
    /**
     * 轉賬
     */
    void transfer(Account target,int amt){
        //鎖定轉出賬戶
        synchronized(this){
             //鎖住轉入賬戶
            synchronized(target){
                if (this.balance > amt) {
                    this.balance -= amt;
                    target.balance += amt;
                }
            }
        }
    }
}

我們試想在古代,沒有信息化,賬戶的存在形式真的就是一個賬本,而且每個賬戶都有一個賬本,這些賬本 都統一存放在文件架上。銀行柜員在給我們做**轉賬時,要去文件架上把轉出賬本和轉入賬本都拿到手,然后做轉賬。**這個柜員在拿賬本的時候可能遇到以下三種情況:

  1. 文件架上恰好有轉出賬本和轉入賬本,那就同時拿走;

  2. 如果文件架上只有轉出賬本和轉入賬本之一,那這個柜員就先把文件架上有的賬本拿到手,同時等著其 他柜員把另外一個賬本送回來;

  3. 轉出賬本和轉入賬本都沒有,那這個柜員就等著兩個賬本都被送回來。

細粒度鎖有可能會出現死鎖
  • 死鎖 :一組互相競爭資源的線程因互相等待,導致“永久”阻塞的現象 。

  • 兩個線程彼此拿著對方的資源都不釋放就會導致死鎖,

  • 使用細粒度鎖可能會導致死鎖

如果有客戶找柜員張三做個轉賬業務:賬戶 A轉賬戶B 100元,此時另一個客戶找柜員李四也做個轉賬業務:賬戶B轉賬戶A 100元,于是張三和李四同時都去文件架上拿賬本,這時候有可能湊巧張三拿到了賬本A,李四拿到了賬本B。張三拿到賬本A后就等著 賬本B(賬本B已經被李四拿走),而李四拿到賬本B后就等著賬本A(賬本A已經被張三拿走),他們要等 多久呢?他們會永遠等待下去…因為張三不會把賬本A送回去,李四也不會把賬本B送回去。我們姑且稱為死等吧。

如何避免死鎖
  1. 互斥,共享資源X和Y只能被一個線程占用;

  2. 占有且等待,線程T1已經取得共享資源X,在等待共享資源Y的時候,不釋放共享資源x;

  3. 不可搶占,其他線程不能強行搶占線程T1占有的資源;

  4. 循環等待,線程1等待線程T2占有的資源,線程T2等待線程T1占有的資源,就是循環等待。

只要破壞其中一個就可以避免死鎖

等待-通知機制

用synchronized實現等待-通知機制

  • synchronized 配合wait(),notif(),notifyAll()這三個方法能夠輕松實現.

  • wait(): 當前線程釋放鎖,進入阻塞狀態

  • notif(),notifAll(): 通知阻塞的線程有可以繼續執行,線程進入可執行狀態

  • notif()是會隨機地地通知等待隊歹一個線程

  • notifyAll()會通知等待隊列中的所有線程,建議使用notifAll()

wait與sleep區別:

sleep是Object的中的方法,wait是Thread中的方法

wait會釋放鎖,sleep不會釋放鎖

wait需要用notif喚醒,sleep設置時間,時間到了喚醒

wait無需捕獲異常,而sleep需要

wait(): 當前線程進入阻塞

到此,相信大家對“java中鎖和synchronized的概念和作用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

得荣县| 新绛县| 乐业县| 车险| 大洼县| 汽车| 咸宁市| 兴化市| 武功县| 莱州市| 哈密市| 德化县| 布尔津县| 东平县| 体育| 承德县| 鹤岗市| 临沂市| 凤山县| 桂平市| 林芝县| 金昌市| 尚志市| 宁乡县| 壤塘县| 河源市| 桓台县| 禹城市| 桦川县| 清徐县| 府谷县| 康定县| 兴安盟| 会宁县| 清新县| 涞源县| 夹江县| 博客| 沁阳市| 镶黄旗| 陆丰市|