1. 程式人生 > >個人筆記--多線程(鎖)

個人筆記--多線程(鎖)

同步機制 由於 筆記 ted 名詞 string href 同步塊 重復執行

死鎖:就是同步方法中有同步代碼塊,或反之。

例子:

public class DeadLock {
    public static String obj1 = "obj1";
    public static String obj2 = "obj2";
    public static void main(String[] args){
        Thread a = new Thread(new Lock1());
        Thread b = new Thread(new Lock2());
        a.start();
        b.start();
    }    
}
class Lock1 implements Runnable{ @Override public void run(){ try{ System.out.println("Lock1 running"); while(true){ synchronized(DeadLock.obj1){ System.out.println("Lock1 lock obj1"); Thread.sleep(3000);//獲取obj1後先等一會兒,讓Lock2有足夠的時間鎖住obj2
synchronized(DeadLock.obj2){ System.out.println("Lock1 lock obj2"); } } } }catch(Exception e){ e.printStackTrace(); } } } class Lock2 implements Runnable{ @Override public
void run(){ try{ System.out.println("Lock2 running"); while(true){ synchronized(DeadLock.obj2){ System.out.println("Lock2 lock obj2"); Thread.sleep(3000); synchronized(DeadLock.obj1){ System.out.println("Lock2 lock obj1"); } } } }catch(Exception e){ e.printStackTrace(); } } }

同步鎖synchronized和互斥鎖ReentrantLock的區別:

同步是隱示的鎖操作,而Lock對象是顯示的鎖操作。

例子:

ReentrantLock lck = new ReentrantLock();//創建一個互斥鎖對象lck.lock(): 獲取鎖對象
//被同步的內容
lck.unlock();//釋放鎖對象
//監視器
Condition c = lck.newCondition();//獲取監視器對象
c.signal();//喚醒等待中的線程 signalAll()喚醒所有
c.await();//讓線程等待.

synchronized和Lock的區別:

技術分享圖片

Lock中可以自己控制鎖是否公平,而且,默認的是非公平鎖。

1.兩種鎖的底層實現方式:

synchronized:我們知道java是用字節碼指令來控制程序(這裏不包括熱點代碼編譯成機器碼)。在字節指令中,存在有synchronized所包含的代碼塊,那麽會形成2段流程的執行。

技術分享圖片

其實synchronized映射成字節碼指令就是增加來兩個指令:monitorenter和monitorexit。

當一條線程進行執行的遇到monitorenter指令的時候,它會去嘗試獲得鎖,如果獲得鎖那麽鎖計數+1(為什麽會加一呢,因為它是一個可重入鎖,所以需要用這個鎖計數判斷鎖的情況),如果沒有獲得鎖,那麽阻塞。當它遇到monitorexit的時候,鎖計數器-1,當計數器為0,那麽就釋放鎖。

問:上圖有2個monitorexit呀?

答:synchronized鎖釋放有兩種機制,一種就是執行完釋放;另外一種就是發送異常,虛擬機釋放。圖中第二個monitorexit就是發生異常時執行的流程

synchronized是悲觀鎖,Lock是CAS樂觀鎖。

Lock底層主要靠volatile和CAS操作實現的。

建議:盡可能去使用synchronized而不要去使用LOCK。

悲觀鎖和樂觀鎖的使用場景:

讀取頻繁使用樂觀鎖(版本號+時間戳),寫入頻繁使用悲觀鎖(上鎖)。

問:解釋以下名詞:重排序,自旋鎖,偏向鎖,輕量級鎖,可重入鎖,公平鎖,非公平鎖

答:

重入鎖(ReentrantLock)是一種遞歸無阻塞的同步機制。重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數獲得鎖之後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。在JAVA環境下 ReentrantLock 和synchronized都是 可重入鎖。

自旋鎖,由於自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高於互斥鎖。如何旋轉呢?何為自旋鎖,就是如果發現鎖定了,不是睡眠等待,而是采用讓當前線程不停地的在循環體內執行實現的,當循環的條件被其他線程改變時 才能進入臨界區。

偏向鎖(Biased Locking)是Java6引入的一項多線程優化,它會偏向於第一個訪問鎖的線程,如果在運行過程中,同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發同步的,這種情況下,就會給線程加一個偏向鎖。 如果在運行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會被掛起,JVM會消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖。

輕量級鎖是由偏向所升級來的,偏向鎖運行在一個線程進入同步塊的情況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級為輕量級鎖。

公平鎖,就是很公平,在並發環境中,每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果為空,或者當前線程線程是等待隊列的第一個,就占有鎖,否則就會加入到等待隊列中,以後會按照FIFO的規則從隊列中取到自己

非公平鎖比較粗魯,上來就直接嘗試占有鎖。在公平的鎖上,線程按照他們發出請求的順序獲取鎖,但在非公平鎖上,則允許‘插隊’:當一個線程請求非公平鎖時,如果在發出請求的同時該鎖變成可用狀態,那麽這個線程會跳過隊列中所有的等待線程而獲得鎖。 非公平的ReentrantLock 並不提倡 插隊行為,但是無法防止某個線程在合適的時候進行插隊。

問:什麽時候應該使用可重入鎖?

答:

場景1:如果已加鎖,則不再重復加鎖。a、忽略重復加鎖。b、用在界面交互時點擊執行較長時間請求操作時,防止多次點擊導致後臺重復執行(忽略重復觸發)。以上兩種情況多用於進行非重要任務防止重復執行,(如:清除無用臨時文件,檢查某些資源的可用性,數據備份操作等)

場景2:如果發現該操作已經在執行,則嘗試等待一段時間,等待超時則不執行(嘗試等待執行)這種其實屬於場景2的改進,等待獲得鎖的操作有一個時間的限制,如果超時則放棄執行。用來防止由於資源處理不當長時間占用導致死鎖情況(大家都在等待資源,導致線程隊列溢出)。

場景3:如果發現該操作已經加鎖,則等待一個一個加鎖(同步執行,類似synchronized)這種比較常見大家也都在用,主要是防止資源使用沖突,保證同一時間內只有一個操作可以使用該資源。但與synchronized的明顯區別是性能優勢(伴隨jvm的優化這個差距在減小)。同時Lock有更靈活的鎖定方式,公平鎖與不公平鎖,而synchronized永遠是公平的。這種情況主要用於對資源的爭搶(如:文件操作,同步消息發送,有狀態的操作等)

場景4:可中斷鎖。synchronized與Lock在默認情況下是不會響應中斷(interrupt)操作,會繼續執行完。lockInterruptibly()提供了可中斷鎖來解決此問題。(場景3的另一種改進,沒有超時,只能等待中斷或執行完畢)這種情況主要用於取消某些操作對資源的占用。如:(取消正在同步運行的操作,來防止不正常操作長時間占用造成的阻塞)

問:簡述鎖的等級方法鎖、對象鎖、類鎖

答:

方法鎖(synchronized修飾方法時)通過在方法聲明中加入 synchronized關鍵字來聲明 synchronized方法。synchronized方法控制對類成員變量的訪問: 每個類實例對應一把鎖,每個synchronized方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨占該鎖,直到從該方法返回時才將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對於每一個類實例,其所有聲明為 synchronized的成員函數中至多只有一個處於可執行狀態,從而有效避免了類成員變量的訪問沖突。

對象鎖(synchronized修飾方法或代碼塊)當一個對象中有synchronized method或synchronized block的時候調用此對象的同步方法或進入其同步區域時,就必須先獲得對象鎖。如果此對象的對象鎖已被其他調用者占用,則需要等待此鎖被釋放。(方法鎖也是對象鎖)。java的所有對象都含有1個互斥鎖,這個鎖由JVM自動獲取和釋放。線程進入synchronized方法的時候獲取該對象的鎖,當然如果已經有線程獲取了這個對象的鎖,那麽當前線程會等待;synchronized方法正常返回或者拋異常而終止,JVM會自動釋放對象鎖。這裏也體現了用synchronized來加鎖的1個好處,方法拋異常的時候,鎖仍然可以由JVM來自動釋放。

類鎖(synchronized修飾靜態的方法或代碼塊),由於一個class不論被實例化多少次,其中的靜態方法和靜態變量在內存中都只有一份。所以,一旦一個靜態的方法被申明為synchronized。此類所有的實例化對象在調用此方法,共用同一把鎖,我們稱之為類鎖。對象鎖是用來控制實例方法之間的同步,類鎖是用來控制靜態方法(或靜態變量互斥體)之間的同步。類鎖只是一個概念上的東西,並不是真實存在的,它只是用來幫助我們理解鎖定實例方法和靜態方法的區別的。java類可能會有很多個對象,但是只有1個Class對象,也就是說類的不同實例之間共享該類的Class對象。Class對象其實也僅僅是1個java對象,只不過有點特殊而已。由於每個java對象都有1個互斥鎖,而類的靜態方法是需要Class對象。所以所謂的類鎖,不過是Class對象的鎖而已。獲取類的Class對象有好幾種,最簡單的就是[類名.class]的方式。

問:怎麽檢測一個線程是否擁有鎖?

答:

java.lang.Thread中有一個方法叫holdsLock(),它返回true如果當且僅當當前線程擁有某個具體對象的鎖。

問:如何實現分布式鎖?

答:

基於數據庫實現分布式鎖

基於緩存(redis,memcached,tair)實現分布式鎖

基於Zookeeper實現分布式鎖

參考:

分布式鎖的實現方式

例子1:

/*
 * 編寫一個程序,開啟 3 個線程,這三個線程的 ID 分別為 A、B、C,每個線程將自己的 ID 在屏幕上打印 10 遍,要求輸出的結果必須按順序顯示。
 *    如:ABCABCABC…… 依次遞歸
 */
public class TestABCAlternate {
    public static void main(String[] args) {
        AlternateDemo ad = new AlternateDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 20; i++) {
                    ad.loopA(i);
                }
            }
        }, "A").start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                
                for (int i = 1; i <= 20; i++) {
                    ad.loopB(i);
                }
            }
        }, "B").start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 20; i++) {
                    ad.loopC(i);
                    System.out.println("-----------------------------------");
                }
            }
        }, "C").start();
    }
}

class AlternateDemo{
    private int number = 1; //當前正在執行線程的標記
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    
    /**
     * @param totalLoop : 循環第幾輪
     */
    public void loopA(int totalLoop){
        lock.lock();
        try {
            //1. 判斷
            if(number != 1){
                condition1.await();
            }
            //2. 打印
            for (int i = 1; i <= 1; i++) {
        System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
            }
            //3. 喚醒
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void loopB(int totalLoop){
        lock.lock();
        try {
            //1. 判斷
            if(number != 2){
                condition2.await();
            }
            //2. 打印
            for (int i = 1; i <= 1; i++) {
        System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
            }
            
            //3. 喚醒
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void loopC(int totalLoop){
        lock.lock();
        try {
            //1. 判斷
            if(number != 3){
                condition3.await();
            }
            //2. 打印
            for (int i = 1; i <= 1; i++) {
        System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
            }
            //3. 喚醒
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

例子2:

/*
 * ReadWriteLock : 讀寫鎖
 * 
 * 寫寫/讀寫 需要“互斥”
 * 讀讀 不需要互斥
 * 
 */
public class TestReadWriteLock {
    public static void main(String[] args) {
        ReadWriteLockDemo rw = new ReadWriteLockDemo();
        new Thread(new Runnable() {
            @Override
            public void run() {
                rw.set((int)(Math.random() * 101));
            }
        }, "Write:").start();
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    rw.get();
                }
            }).start();
        }
    }
}
class ReadWriteLockDemo{
    private int number = 0;
    private ReadWriteLock lock = new ReentrantReadWriteLock();
    //
    public void get(){
        lock.readLock().lock(); //上鎖
        try{
        System.out.println(Thread.currentThread().getName() + " : " + number);
        }finally{
            lock.readLock().unlock(); //釋放鎖
        }
    }
    //
    public void set(int number){
        lock.writeLock().lock();
        try{
    System.out.println(Thread.currentThread().getName());
            this.number = number;
        }finally{
            lock.writeLock().unlock();
        }
    }
}

個人筆記--多線程(鎖)