寫【高併發專題】有一段時間了,一些讀者朋友留言說,併發編程很難,學習了不少的知識,可是在實際工做中卻無從下手。對於一個線上產生的併發問題,又不知產生這個問題的緣由到底是什麼。對於併發編程,感受上彷佛是掌握了,可是真正用起來卻不是那麼回事!html
其實,形成這種現象的本質緣由就是沒有透徹的理解併發編程的精髓,而學好併發編程的關鍵是須要弄懂三個核心問題:前端
比較官方的解釋爲:分工就是將一個比較大的任務,拆分紅多個大小合適的任務,交給合適的線程去完成,強調的是性能。編程
若是你還不可以理解什麼是分工,這裏,咱們能夠作一個假設。假設你是一個XXX上市公司的CEO,你的工做是如何管理好你的公司。可是,就如何管理好公司而言,涉及到的任務就比較多了,咱們能夠將其看作一個很大的任務,這個很大的任務,細看的話能夠包括:人員招聘和管理、產品設計和開發、運營和推廣、公司稅務等等。那細化後這麼多的任務交給你一我的去作,想必你必定是崩潰的。即便你可以挺住,估計你一我的把這全部的任務完成,那黃花菜也就涼了!到時,估計你就會偷偷的躲在角落裏唱「涼涼」了。。。安全
因此,若是你真的想管理好你的公司,你就須要將這些任務分解,分工細化,將人員招聘和管理的任務交給人力資源部門去完成,將產品的設計交給設計部門去完成,將產品的開發交給開發部門去完成,將運營和推廣交給運營和市場部門去完成,將公司稅務交給財務部門去完成。此時,你的任務就是及時瞭解各個部門的工做狀況,統籌並協調各部門的工做,並思考如何規劃公司的將來。微信
其實,這裏你將管理公司的任務拆解、細化分工以後,你會發現,其實各部門之間的工做是並行執行的。好比:人力資源部門在管理員工的績效考覈時,同時產品設計和開發部門正在設計和開發公司的產品,與此同時,公司的運營正在和設計與開發溝通如何更好的完善公司的產品,而推广部門正在加大力度宣傳和推廣公司的產品。而財務部門正在統計和計算公司的各類財務報表等。一切都是那麼的有條不紊!多線程
因此,安排合適的人去作合適的事情,在實際工做中是很是重要的。映射到併發編程領域也是一樣的道理。若是將全部的任務交給一個線程執行,就比如將公司的全部事情交給你一我的去作同樣。等到把事情作完了,黃花菜也涼了。因此,在併發編程中,咱們一樣須要將任務進行拆解,分工給合適的線程去完成。併發
在併發編程領域,還須要注意一個問題就是:分工給合適的線程去作。也就是說,應該主線程執行的任務不要交給子線程去作,不然,是解決不了問題的。這就比如一家公司的CEO將如何規劃公司的將來交給一個產品開發人員去作同樣,這不只不能規劃好公司的將來,甚至會與公司的價值觀背道而馳。高併發
在JavaSDK中的:Executor、Fork/Join和Future都是實現分工的一種方式。工具
在併發編程中的同步,主要指的是一個線程執行完任務後,如何通知其餘的線程繼續執行,強調的是性能。性能
將任務拆分,而且合理的分工給了每一個人,接下來就是如何同步每一個人的任務了。
假設小明是一名前端開發人員,他渲染頁面的數據須要等待小剛的接口完成,而小剛寫接口又須要等待小李的服務開發完成。也就是說,任務之間是存在依賴關係的,前面的任務完成後,才能進行後面的任務。
對於實際工做中,這種任務的同步,大多數靠的是人與人之間的溝通,小李的服務寫完了,告訴小剛,小剛則立刻進行接口開發,等小剛的接口開發完成後,又告訴了小明,小明立刻調用接口將返回的數據渲染在頁面上。
這種同步機制映射到併發編程領域,就是一個線程的任務執行完畢以後,通知其餘的後續線程執行任務。
對於這種線程之間的同步,咱們可使用下面的 if 僞代碼來表示。
if(前面的任務完成){ 執行當前任務 }else{ 繼續等待前面任務的執行 }
若是爲了更可以及時的判斷出前面的任務是否已經完成,咱們也可使用 while 僞代碼來表示。
while(前面的任務未完成){ 繼續等待前面任務的執行 } 執行當前任務
上述僞代碼表示的意義是相同的:當線程執行的條件不知足時,線程須要繼續等待,一旦條件知足,就須要喚醒等待的線程繼續執行。
在併發編程領域,一個典型的場景就是生產者-消費者模型。當隊列滿時,生產者線程須要等待,隊列不滿時,須要喚醒生產者線程;當隊列爲空時,消費者線程須要等待,隊列不空時,須要喚醒消費者。咱們可使用下面的僞代碼來表示生產者-消費者模型。
while(隊列已滿){ 生產者線程等待 } 喚醒生產者
while(隊列爲空){ 消費者等待 } 喚醒消費者
在Java的SDK中,提供了一些實現線程之間同步的工具類,好比說:CountDownLatch、 CyclicBarrier 等。
同一時刻,只容許一個線程訪問共享變量,強調的是線程執行任務的正確性。
在併發編程領域,分工和同步強調的是執行任務的性能,而線程之間的互斥則強調的是線程執行任務的正確性,也就是線程的安全問題。若是多個線程同時訪問同一個共享變量,則可能會發生意想不到的後果,而這種意想不到的後果主要是由線程的可見性、原子性和有序性問題產生的。而解決可見性、原子性和有序性問題的核心,就是互斥。
關於互斥,咱們能夠用現實中的一個場景來描述:多個岔路口的車輛須要匯入一條道路中,而這條道路一次只能容許經過一輛車,此時,車輛就須要排隊依次進入路口。
Java中提供的synchronized、Lock、ThreadLocal、final關鍵字等均可以解決互斥的問題。
例如,咱們以synchronized爲例來講明如何進行線程間的互斥,僞代碼以下所示。
//修飾方法 public synchronized void xxx(){ } //修飾代碼塊 public void xxx(){ synchronized(obj){ } } //修飾代碼塊 public void xxx(){ synchronized(XXX.class){ } } //修飾靜態方法 public synchronized static void xxx(){ }
併發編程旨在最大限度的利用計算機的資源,提升程序執行的性能,這須要線程之間的分工和同步來實現,在保證性能的同時,又須要保證線程的安全,這就又須要保證線程之間的互斥性。而併發編程的難點問題,每每又是由可見性、原子性和有序性問題致使的。因此,咱們在學習併發編程時,必定要先弄懂線程之間的分工、同步和互斥。
若是以爲文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公衆號,跟冰河學習高併發編程技術。
最後,附上併發編程須要掌握的核心技能知識圖,祝你們在學習併發編程時,少走彎路。