解決線程安全問題的三種方法


線程安全問題

線程安全出現的根本原因:

    1.存在兩個或者兩個以上的線程對象共享同一個資源;

    2.多線程操作共享資源代碼有多個語句。

一、使用同步代碼塊

如:賣票案例 出現了線程安全 重復的票不能出現

步驟:成員位置建立鎖對象;

synchronized(鎖對象){
  出現安全問題代碼
}

注意事項:

1 鎖對象可以是任意對象 

2 必須保證多個線程使用的是同一個鎖對象 、

3 把{} 讓一個線程進

4.一個線程在同步代碼塊中sleep了,並不會釋放鎖對象;

  5.如果不存在線程安全問題,千萬不要使用同步代碼塊;

  6.鎖對象必須是多線程共享的一個資源,否則鎖不住。

例子:

public class RunnableImpl implements  Runnable{
    // 定義共享資源   線程不安全
    private int ticket = 100;
    //在成員位置創建一個鎖對象
    Object obj = new Object();
    // 線程任務  賣票
    @Override
    public void run() {
        while(true){
            //建立鎖對象
            synchronized (obj){
                if(ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //賣票操作
                    System.out.println(Thread.currentThread().getName()+"正在賣第"+ticket+"張票");
                    ticket--;
                }
            }
        }
    }
}

二、使用同步方法(函數)解決多線程安全

同步函數就是使用synchronized修飾一個函數

步驟

1 創建一個方法 修飾符添加synchronized
2 把訪問了 共享數據的代碼放入到方法中
3 調用同步方法

注意事項

同步函數注意事項:

        1.如果函數是一個非靜態的同步函數,那么鎖對象是this對象;

        2.如果函數是靜態的同步函數,那么鎖對象是當前函數所屬的類的字節碼文件(class對象);

        3.同步函數的鎖對象是固定的,不能由自己指定。

例子:

public class RunnableImpl implements  Runnable{
    // 定義共享資源   線程不安全
    private int ticket = 100;
    // 線程任務  賣票
    @Override
    public void run() {
        while(true){
            payTicket();//調用下面synchronized修飾的方法
        }
    }
    public synchronized void payTicket(){
        if(ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //賣票操作
            System.out.println(Thread.currentThread().getName()+"正在賣第"+ticket+"張票");
            ticket--;
        }
    }
}
推薦使用:同步代碼塊

    原因:

        1.同步代碼塊的鎖對象可以由我們自由指定,方便控制;

        2.同步代碼塊可以方便的控制需要被同步代碼的范圍,同步函數必須同步函數的所有代碼。

三.使用lock鎖

原因:synchronized的缺陷

  synchronized是java中的一個關鍵字,也就是說是Java語言內置的特性。那么為什么會出現Lock呢?

 synchronized 的局限性 與 Lock 的優點 

  如果一個代碼塊被synchronized關鍵字修飾,當一個線程獲取了對應的鎖,並執行該代碼塊時,其他線程便只能一直等待直至占有鎖的線程釋放鎖。事實上,占有鎖的線程釋放鎖一般會是以下三種情況之一:

  1:占有鎖的線程執行完了該代碼塊,然后釋放對鎖的占有;

  2:占有鎖線程執行發生異常,此時JVM會讓線程自動釋放鎖;

  3:占有鎖線程進入 WAITING 狀態從而釋放鎖,例如在該線程中調用wait()方法等。

  試考慮以下三種情況: 

Case 1 :

  在使用synchronized關鍵字的情形下,假如占有鎖的線程由於要等待IO或者其他原因(比如調用sleep方法)被阻塞了,但是又沒有釋放鎖,那么其他線程就只能一直等待,別無他法。
這會極大影響程序執行效率。因此,就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如只等待一定的時間 (解決方案:tryLock(long time, TimeUnit unit))
或者 能夠響應中斷 (解決方案:lockInterruptibly())),這種情況可以通過 Lock 解決。 Case 2 :   我們知道,當多個線程讀寫文件時,讀操作和寫操作會發生沖突現象,寫操作和寫操作也會發生沖突現象,但是讀操作和讀操作不會發生沖突現象。
但是如果采用synchronized關鍵字實現同步的話,就會導致一個問題,即當多個線程都只是進行讀操作時,也只有一個線程在可以進行讀操作,其他線程只能等待鎖的釋放而無法進行讀操作。
因此,需要一種機制來使得當多個線程都只是進行讀操作時,線程之間不會發生沖突。同樣地,Lock也可以解決這種情況 (解決方案:ReentrantReadWriteLock) 。 Case 3 :   我們可以通過Lock得知線程有沒有成功獲取到鎖 (解決方案:ReentrantLock) ,但這個是synchronized無法辦到的。 上面提到的三種情形,我們都可以通過Lock來解決,但 synchronized 關鍵字卻無能為力。
事實上,Lock 是 java.util.concurrent.locks包 下的接口,Lock 實現提供了比 synchronized 關鍵字 更廣泛的鎖操作,它能以更優雅的方式處理線程同步問題。也就是說,Lock提供了比synchronized更多的功能。
總結一下,也就是說Lock提供了比synchronized更多的功能。但是要注意以下幾點:

  1)Lock不是Java語言內置的,synchronized是Java語言的關鍵字,因此是內置特性。Lock是一個類,通過這個類可以實現同步訪問;

  2)Lock和synchronized有一點非常大的不同,采用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之后,系統會自動讓線程釋放對鎖的占用
    而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象
Lock接口實現類的使用
Lock接口有6個方法:
// 獲取鎖  
void lock()   

// 如果當前線程未被中斷,則獲取鎖,可以響應中斷  
void lockInterruptibly()   

// 返回綁定到此 Lock 實例的新 Condition 實例  
Condition newCondition()   

// 僅在調用時鎖為空閑狀態才獲取該鎖,可以響應中斷  
boolean tryLock()   

// 如果鎖在給定的等待時間內空閑,並且當前線程未被中斷,則獲取鎖  
boolean tryLock(long time, TimeUnit unit)   

// 釋放鎖  
void unlock()

注意
lock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly()都是用來獲取鎖的。
unLock()方法是用來釋放鎖的。newCondition() 返回 綁定到此 Lock 的新的 Condition 實例 ,用於線程間的協作,詳細內容請查找關鍵詞:線程間通信與協作。

參考:  https://www.cnblogs.com/myseries/p/10784076.html

        https://www.bilibili.com/video/BV1uJ411k7wy?p=323


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM