【Java併發編程學習】六、死鎖

/**
 * 可重入內置鎖
 *     每一個Java對象均可以用作一個實現同步的鎖,這些鎖被稱爲內置鎖或監視鎖。線程在進入同步代碼塊以前會自動獲取鎖,而且在退出同步代碼塊時
 * 會自動釋放鎖。獲取內置鎖的惟一途徑是進入由這個鎖保護的同步代碼塊或方法。
 * 「重入」意味着獲取鎖的操做的粒度是「線程」,而不適合調用。
 * 同一個線程在調用本類中其它synchronized方法/塊或父類中的synchronized方法/塊時,都不會阻礙該線程地執行,由於互斥鎖時可重入的。
 */
public class Deadlock extends Object {
    private String objID;

    public Deadlock(String id) {
        objID = id;
    }

    public synchronized void checkOther(Deadlock other) {
        print("entering checkOther()");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {

        }
        print("in checkOther() - about to" + "invoke 'other.action()'");
        other.action();
        print("leaving checkOther()");
    }

    public synchronized void action() {
        print("entering action()");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {

        }
        print("leaving action()");
    }

    public void print(String msg) {
        threadPrint("objID=" + objID + "-" + msg);
    }

    public static void threadPrint(String msg) {
        String threadName = Thread.currentThread().getName();
        System.out.println("[Deadlock] threadName= " + threadName + "#" + msg);
    }

    public static void main(String[] args) {
        final Deadlock obj1 = new Deadlock("obj1");
        final Deadlock obj2 = new Deadlock("obj2");

        Runnable runA = new Runnable() {
            @Override
            public void run() {
                obj1.checkOther(obj2);
            }
        };
        Thread threadA = new Thread(runA, "threadA");
        threadA.start();

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Runnable runB = new Runnable() {
            @Override
            public void run() {
                obj2.checkOther(obj1);
            }
        };
        Thread threadB = new Thread(runB, "threadB");
        threadB.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        threadPrint("about to interrupt() threadA");
        threadA.interrupt();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadPrint("about to interrupt() threadB");
        threadB.interrupt();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        threadPrint("did that break the deadlock?");
    }
}
/**
 * 重入鎖使用java.util.concurrent.locks.ReentrantLock類來實現
 * 爲了保證鎖最終必定會被釋放(可能會有一場發生),要把互斥區放在try語句塊內,並在finally語句塊中釋放鎖,尤爲當有return語句時,
 * return語句必須放在try字句中,以確保unlock()不會過早發生,從而將數據暴露給第二個任務。
 *
 * 有助於提升「鎖」性能的幾點建議:
 *     一、減小鎖持有時間;減小鎖的持有時間有助於下降鎖衝突的可能性,進而提高系統的併發能力。
 *     二、減少鎖粒度
 *        a)線程安全的HashMap:使用Collections.synchronizedMap()方法包裝HashMap
 *          public static Map m = Collections.synchronizedMap(new HashMap())
 *        b)對於ConcurrentHashMap,它內部進一步細分了若干小的HashMap,稱之爲段(SEGMENT)。默認狀況下,一個ConcurrentHashMap被
 *        進一步細分爲16個段。因爲默認有16個段,所以,若是夠幸運的話,ConcurrentHashMap能夠同時接受16個線程同時插入
 *        (若是都插入不一樣的段中),從而大大提升其吞吐量;減小鎖粒度會引入一個新的問題,即:當系統須要取得全局鎖時,其消耗的資源會比較多。
 *        c)所謂減小鎖粒度,就是隻縮小鎖定對象的範圍,從而減小鎖衝突的可能性,進而提升系統的併發能力;
 *     三、讀寫分離鎖來替換獨佔鎖
 *        a)使用讀寫鎖ReadWriteLock能夠提升系統的性能。使用讀寫分離鎖來替代獨佔鎖是減小鎖粒度的一種特殊狀況。
 *        b)減小鎖粒度是經過分割數據結構實現的,那麼,讀寫鎖則是對系統功能點的分割;
 *        c)在讀多寫少的場合,使用讀寫鎖能夠有效提高系統的併發能力;
 *     四、鎖分離
 *
 *     五、鎖粗化
 *        虛擬機在遇到一連串連續地對同一鎖不斷進行請求和釋放的操做時,便會把全部的鎖操做整合成對鎖的一次請求,從而減小對鎖的請求同步次數,
 *        這個操做叫作鎖的粗化。
 *
 *  synchronized和ReetrantLock
 *  1)互斥同步最主要的問題就是進行線程阻塞和喚醒所帶來的性能問題,於是這種同步又稱爲阻塞同步,它屬於一種悲觀的併發策略,即線程得到的是
 *  獨佔鎖。獨佔鎖意味着其它線程只能依靠阻塞來等待線程釋放鎖。而在CPU轉換線程阻塞時會引發線程上下文切換,當有不少線程競爭鎖的時候,
 *  會引發CPU頻繁的上下文切換致使效率很低。synchronized採用的即是這種併發策略。
 *
 *  2)基於衝突檢測的樂觀併發策略,通俗地將就是先進性操做,若是沒有其它線程爭用共享數據,那操做就成功了,若是共享數據被爭用,產生了衝突,
 *  那就在進行其它的補償措施(最多見的補償措施就是不斷的重試,直到試成功爲止),這種樂觀的併發策略的許多實現都不須要把線程掛起,所以這種
 *  同步被稱爲非阻塞同步。ReetrantLock採用的即是這種併發策略。在樂觀的併發策略中,須要操做和衝突檢測這兩個步驟具有原子性,它靠硬件指令
 *  來保證,這裏用的是CAS操做(Compare and Swap)。
 *
 *  3)基本語法上,ReetrantLock與synchronized很類似,它們都具有同樣的線程重入特性,只是代碼寫法上有點區別而已,一個表現爲API層面的互斥鎖(Lock),
 *  一個表現爲原生語法層面的互斥鎖(synchronized)。
 *
 *  《Java併發編程實踐》一書給出了使用ReetrantLock的最佳時機:
 *    當你須要如下高級特性時,才應該使用:可定時的、可輪詢的與可中斷的鎖獲取操做,公平隊列,或者非塊結構的鎖。不然,請使用synchronized。
 */
public class ReentrantLockTest implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;
    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTest reentrantLockTest = new ReentrantLockTest();
        Thread thread1 = new Thread(reentrantLockTest);
        Thread thread2 = new Thread(reentrantLockTest);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println("[ReentrantLockTest] i=" + i);
    }
}
相關文章
相關標籤/搜索