併發同步知多少

孫豪傑的部落格發表於2016-02-26

  找工作的時候是否經常看到要求有高併發,分散式系統的開發設計經驗,或者高併發,分散式系統的開發設計經驗者優先等字樣,這時候情不自禁的搜尋一下什麼是併發,多少算高併發,再思索一下自己的經歷哪些是符合這個要求的?那麼什麼是併發,開發中的併發是怎麼處理的,簡單瞭解一下吧。

        在介紹併發之前我們先了解一下序列和並行:

        熱鬧的景點,買票人很多,這時只有一個視窗售票,大家排隊依次買票就可以理解為序列。

        排隊人太多了,旁邊又加開了幾個視窗,多人在不同的視窗同時買票可以理解為並行。

        如果只能開一個視窗,這時好多著急的人圍上來,有問價格的,有掏錢的,又有取票的,在這個過程中售票員在同時應對多個買票人,可以理解為併發。

        我們經常在計算機上一邊聽歌一邊寫文件(或者處理其他的事情),這就是一種併發行為,表面看兩個程式是同時進行,為什麼不是並行呢?計算機只有一個CPU所以只能支援一個執行緒執行。有人拍磚說:我家裡計算機是多核CPU可以同時支援多個執行緒執行,確實是這樣,為此我也特地去百度了一下有如下幾點認知:

        1、雖然是多核CPU但是,系統匯流排,記憶體是共用的,在載入記憶體資料時仍然需要序列訪問。

        2、目前的程式設計語言仍然是過程型開發,沒有和好的方法能自動的切割任務使平行計算。

        3、作業系統線上程排程時隨著核心的增加複雜性遞增,目前最多支援8核

        所以基於以上認知,我們在討論併發和同步這個問題時仍然按照CPU單核來討論。

        那麼計算機是如何做到一邊播放歌曲一邊支援文件編輯呢?作業系統會把CPU的執行時間劃分微妙級別的時間片段,每一個時間片內去排程一個執行緒執行,多個執行緒不斷的切換執行,因此在人類可感知的時間段(秒級)內執行緒是同時執行的,所以多個執行緒在某個時間段內的同時執行就是併發。

序列、並行和併發如下圖所示:

序列並行併發

        網際網路應用基本上都是支援多使用者多請求同時訪問伺服器端的,所以網際網路應用都是支援併發的,那麼高併發的主要困難是什麼呢?作業系統會給每個執行緒分配獨立的記憶體空間和時間片,所以執行緒間是隔離的。但是如果執行緒訪問執行緒外的記憶體空間,檔案系統,輸入輸出裝置,資料庫或者其他儲存裝置時就會發生資源競爭,共享資源的訪問必須序列,保證序列訪問資源的機制就是同步,JAVA中經常使用的同步機制有synchronized關鍵字,java.util.concurrent.locks.Lock系列類。

  同步的場景有以下幾種:

  1、執行緒獲取同步鎖,獲取失敗則阻塞等待

同步1

  適用場景:

  a、同步獲取序列號生成器,當有其他執行緒獲取序列號時,其他執行緒等待

  java程式碼例項:

public class SynchronizedProcessor implements Processor {
 
    /* (non-Javadoc)
     * @see com.sunhaojie.test.thread.Processor#process(java.lang.String)
     */
    public void process(String name) {
        System.out.println(String.format("%s開始處理,當前時間是%d", name, 
System.currentTimeMillis()));
        synchronized (this) {
            System.out.println(String.format("%s獲得鎖%s", name, 
this.toString()));
            try {
                System.out.println(String.format("%s開始sleep", name));
                Thread.sleep(1000);
                System.out.println(String.format("%s結束sleep", name));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(String.format("%s釋放鎖%s", name, 
this.toString()));
        System.out.println(String.format("%s結束處理,當前時間是%d", name, 
System.currentTimeMillis()));
    }
 
}

  2、執行緒獲取同步鎖,獲取失敗結束

同步2

  適用場景:

  a、定時任務,前一個處理執行緒未完成時,新執行緒不能獲取鎖則直接結束

  java程式碼示例:

public class LockFailCloseProcessor implements Processor {
    private static Lock lock = new ReentrantLock();
 
    /* (non-Javadoc)
     * @see com.sunhaojie.test.thread.Processor#process(java.lang.String)
     */
    public void process(String name) {
        System.out.println(String.format("%s開始處理,當前時間是%d", name, 
System.currentTimeMillis()));
        if (lock.tryLock()) {
            System.out.println(String.format("%s獲得鎖%s", name, 
this.toString()));
            try {
                System.out.println(String.format("%s開始sleep", name));
                Thread.sleep(1000);
                System.out.println(String.format("%s結束sleep", name));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
            System.out.println(String.format("%s釋放鎖%s", name, 
this.toString()));
        } else {
            System.out.println(String.format("%s沒有獲得鎖直接退出", 
name));
        }
        System.out.println(String.format("%s結束處理,當前時間是%d", name, 
System.currentTimeMillis()));
    }
}

  3、執行緒獲取同步鎖後,因為其他資源不滿足暫時釋放同步鎖,等待喚醒

同步3

  適用場景:

  a、即使通訊中,傳送者獲取同步鎖發現佇列寫滿時,釋放鎖等待接收者讀取資料

  java程式碼示例:

public class SynchronizedWaitWriteProcessor implements Processor {
 
    /**
     * 是否可讀標記,false:不可讀,可寫 true:可讀,不可寫
     */
    public static int maxSize = 5;
    public static List<String> content = new ArrayList<String>();
 
    /* (non-Javadoc)
     * @see com.sunhaojie.test.thread.Processor#process(java.lang.String)
     */
    public void process(String name) {
        System.out.println(String.format("%s開始處理,當前時間是%d", name, 
System.currentTimeMillis()));
        synchronized (content) {
            System.out.println(String.format("%s獲得鎖%s", name, 
this.toString()));
            try {
                if (content.size() == maxSize) {
                    System.out.println(
String.format("%s臨時釋放鎖%s", name, this.toString()));
                    content.wait();
                }
                System.out.println(
String.format("%s開始寫入資訊", name));
                Random random = new Random();
                for (int i = 0; i < maxSize; i++) {
                    content.add(
String.format("寫入資訊%d", random.nextInt(1000)));
                }
                System.out.println(
String.format("%s結束寫入資訊", name));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            content.notify();
        }
        System.out.println(String.format("%s釋放鎖%s", name, 
this.toString()));
        System.out.println(String.format("%s結束處理,當前時間是%d", name, 
System.currentTimeMillis()));
    }
 
}

  4、執行緒獲取同步鎖後,因為其他資源不滿足結束執行緒

  適用場景:

同步4

  a、即使通訊中,接收者獲取同步鎖發現佇列無資料時,釋放鎖結束執行緒

  java程式碼示例:

public class SynchronizedWaitReadProcessor implements Processor {
 
    /* (non-Javadoc)
     * @see com.sunhaojie.test.thread.Processor#process(java.lang.String)
     */
    public void process(String name) {
        System.out.println(String.format("%s開始處理,當前時間是%d", name, 
System.currentTimeMillis()));
        synchronized (SynchronizedWaitWriteProcessor.content) {
            System.out.println(String.format("%s獲得鎖%s", name, 
this.toString()));
            if (SynchronizedWaitWriteProcessor.content.size() 
!= 0) {
                System.out.println(
String.format("%s開始讀出資訊", name));
                for (int i = 0; 
i < SynchronizedWaitWriteProcessor.content.size(); i++) {
                    System.out.println("讀出資訊:" + SynchronizedWaitWriteProcessor.content.get(i));
                }
                System.out.println(
String.format("%s結束讀出資訊", name));
            }
            SynchronizedWaitWriteProcessor.content.notify();
        }
        System.out.println(String.format("%s釋放鎖%s", name, 
this.toString()));
        System.out.println(String.format("%s結束處理,當前時間是%d", name, 
System.currentTimeMillis()));
    }
 
}

  最後送上執行以上程式的main方法和Processor 介面類:

public interface Processor {
    public void process(String name);
}
public class ThreadTest {
 
    public static void main(String[] args) throws InterruptedException {
        //測試SynchronizedProcessor
        //        Processor processor = new SynchronizedProcessor();
        //        for (int i = 0; i < 10; i++) {
        //            ProcessorThread threadProcessor = 
new ProcessorThread("name" + i, processor);
        //            threadProcessor.start();
        //        }
 
        //測試LockProcessor
        //        Processor processor = new LockProcessor();
        //        for (int i = 0; i < 10; i++) {
        //            ProcessorThread threadProcessor = 
new ProcessorThread("name" + i, processor);
        //            threadProcessor.start();
        //        }
 
        // Processor processor = new LockFailCloseProcessor();
        // for (int i = 0; i < 10; i++) {
        // ProcessorThread threadProcessor = 
new ProcessorThread("name" + i,
        // processor);
        // threadProcessor.start();
        // }
 
        Processor readProcessor = new SynchronizedWaitReadProcessor();
        ProcessorThread readThreadProcessor = 
new ProcessorThread("read", readProcessor);
        readThreadProcessor.start();
        Processor writeProcessor = new SynchronizedWaitWriteProcessor();
        ProcessorThread writeThreadProcessor = 
new ProcessorThread("write", writeProcessor);
        writeThreadProcessor.start();
        Thread.sleep(100);
        ProcessorThread read2ThreadProcessor = 
new ProcessorThread("read2", readProcessor);
        read2ThreadProcessor.start();
    }
}

  多執行緒可以大大提高效能,但是多執行緒的同步又增加了應用的複雜性,是否能平衡多執行緒的效能和複雜性是是否有高併發經驗的要求。

相關文章