JVM中存在一個主存區(Main Memory或Java Heap Memory),Java中全部變量都是存在主存中的,對於全部線程進行共享,而每一個線程又存在本身的工做內存(Working Memory),工做內存中保存的是主存中某些變量的拷貝,線程對全部變量的操做並不是發生在主存區,而是發生在工做內存中,而線程之間是不能直接相互訪問,變量在程序中的傳遞,是依賴主存來完成的。而在多核處理器下,大部分數據存儲在高速緩存中,若是高速緩存不通過內存的時候,也是不可見的一種表現。在Java程序中,內存自己是比較昂貴的資源,其實不只僅針對Java應用程序,對操做系統自己而言內存也屬於昂貴資源,Java程序在性能開銷過程當中有幾個比較典型的可控制的來源。synchronized和volatile關鍵字提供的內存中模型的可見性保證程序使用一個特殊的、存儲關卡(memory barrier)的指令,來刷新緩存,使緩存無效,刷新硬件的寫緩存而且延遲執行的傳遞過程,無疑該機制會對Java程序的性能產生必定的影響。java
在java虛擬機進程中,執行程序代碼的任務是由線程看來完成的。每一個線程都有一個獨立的程序計數器和方法調用棧。程序計數器:pc寄存器,當線程執行一個方法時,程序計數器指向方法區中下一條要執行的字節碼指令。方法調用棧:用來跟蹤線程運行中一系列方法的調用過程,棧中的元素稱爲棧幀。每當線程調用一個方法,就會壓棧一個新幀,幀用來保存方法的參數,局部變量,運算過程當中產生的臨時數據。java虛擬機的主線程是它從啓動類的main()方法開始運行。此外,用戶也能夠建立本身的線程,兩種方式:繼承 Thread 類,實現 Runnable 接口。
可是運行一個線程必須使用Thread.strat(),切記:1.不可直接運行run(),直接運行run()只是單純的方法調用,並不會產出新的線程。2.不要隨意覆蓋start(),若是必須覆蓋記得首先調用super.start()。線程是不會順序執行的,一切都由操做系統調度決定,而且一個線程只能啓動一次,第二次啓動會拋出:IllegalThreadStateException,可是並不會影響以前啓動的線程工做。編程
public class MyRunnable implements Runnable{ @Override public void run() { System.out.println("runnable running"); } } public class MyThread extends Thread{ @Override public void run(){ System.out.println("thread running"); } }
新建狀態:new 語句建立的狀態,此時它和其餘java對象同樣,僅僅在堆中被分配了內存。緩存
就緒狀態:當一個線程被其餘線程調用了start(),此時jvm會爲它建立程序計數器和方法調用棧。處於改狀態的線程位於可運行池,等待獲取CPU的執行權。數據結構
運行狀態:處於改狀態的線程佔用CPU,正在執行程序代碼。若是計算機只有一個單核CPU那麼永遠hi只有一個線程處於改狀態。只有處於就緒狀態的線程纔可能成爲運行狀態。多線程
阻塞狀態:線程由於某些緣由放棄了CPU暫停執行。此時線程放棄CPU的執行權,直到進入就緒狀態纔可能再次變爲運行狀態。阻塞狀態3中狀況:併發
死亡狀態:線程退出run(),有多是正常執行完成,也有可能碰見異常退出。可是都不會對其餘線程形成影響。Thread類有isAlive()(新建與死亡狀態返回false,其他狀態返回true)判斷線程是否存活。jvm
一個單核CPU在一個時刻只能執行一個機器指令。線程只有經過得到CPU才能執行本身的程序代碼。所謂多線程的併發執行,其實從宏觀上來看:各個線程輪流得到CPU的使用權,分別執行各自的任務。jvm採用搶佔式調度模型,是指先讓高優先級線程得到CPU。若是優先級相同,隨機選擇一個執行。處於運行狀態的線程或一直執行,直到不得不放棄CPU,通常有以下緣由:ide
1. jvm讓其放棄CPU轉入就緒狀態。 2. 線程因某些緣由進入阻塞狀態。 3. 運行結束退出run()。
值得注意一點:java的線程優先級使用Thread.setPriority(int)設置,一般三個靜態常量選擇:Thread.MAX_PRIORITY(默認:10),Thread.MIN_PRIORITY(默認:1),Thread.NORM_PRIORITY(默認:5)。可是各個操做系統的線程優先級並不相同,因此爲了確保程序可以在不一樣平臺正常執行,咱們只是用這三個值,不會使用1-10中的其餘數字。經常使用方法:函數
Thread.sleep(long millis): 當前線程放棄CPU進入阻塞狀態,通過milli毫秒後恢復就緒狀態,不放棄對象鎖的持有。性能
Thread.yield(): 讓出CPU執行權進入就緒狀態,給另外一個擁有相同或者大於優先級的線程,若是沒知足條件的線程,則什麼都不作。
Thread.join(): 當前線程調用另外一個線程的join(),而且等待被調用線程執行完後再繼續執行。
Object.wait(): 當前線程必須擁有當前對象鎖。若是當前線程不是此鎖的擁有者,會拋出IllegalMonitorStateException異常。喚醒當前對象鎖的等待線程使用notify或notifyAll方法,也必須擁有相同的對象鎖,不然也會拋出IllegalMonitorStateException異常。waite()和notify()必須synchronized函數或synchronized block中進行調用。若是在non-synchronized函數或non-synchronized block中進行調用,雖然能編譯經過,但在運行時會發生IllegalMonitorStateException的異常。
Object.notify(): 執行該方法的線程隨機喚醒對象等待池中的一個線程,並將其裝入對象鎖池之中。
併發編程三個概念:
原子性:一個操做或者多個操做,要麼所有成功,要麼所有失敗。
可見性:當多個線程訪問同一變量時,一個線程修改了該變量的值,其餘線程能當即看到修改後的值。
有序性:程序執行的順序按照代碼的前後順序執行。(你覺得這是廢話?請了解指令重排序)。這三個特性中2,3能夠由volatile關鍵字保證(2.緩存一致性協議,3.禁止指令重排序),1只能由同步方式保證。
同步是解決資源共享的有效手段。當一個線程在操做共享變量的時候,其餘線程只能等待。只有當該線程執行完同步代碼塊後,其餘線程纔能有機會操做共享資源。一般有以下幾種同步方式:
synchorized關鍵字: 修飾方法或者使用同步代碼塊。
ReentrantLock重入鎖對象: 鎖住共享變量的操做。
使用併發數據結構對象:Atomic系列,Concurrent系列等。
可是同步的操做,代價較大,咱們應該儘量減小同步操做,是的一個線程能儘快的釋放鎖,減小其餘線程執行的時間。因爲等待一個鎖的線程只有在得到了這把鎖以後,
才能繼續執行因此讓持有鎖的線程及時釋放鎖的至關重要的。
如下狀況線程釋放鎖:
執行完同步代碼塊。
執行同步代碼塊的過程當中,碰見異常,線程死亡,鎖被釋放。
如下狀況線程不會釋放鎖:
執行同步代碼塊的過程當中,執行了Thread.sleep(),當前線程放棄CPU開始睡眠進入阻塞狀態,可是不會釋放鎖。
執行同步代碼塊的過程當中,執行了Thread.yield(),當前線程放棄CPU開始睡眠進入就緒狀態,可是不會釋放鎖。
不一樣的線程須要協做完成工做(一種狀況是:線程2須要線程1的執行結果)。
- Object.wait(): 執行該放大的線程釋放它持有的該對象的共享鎖(前提時必須持有該共享鎖),該線程進入對象等待池,等待其餘線程將其喚醒。 - Object.notify(): 執行該方法的線程隨機喚醒對象等待池中的一個線程,並將其裝入對象鎖池之中。
補充:
1.鎖池:假設線程A已經擁有了某個對象(注意:不是類)的鎖,而其它的線程想要調用這個對象的某個synchronized方法(或者synchronized塊),因爲這些線程在進入對象的synchronized方法以前必須先得到該對象的鎖的擁有權。可是該對象的鎖目前正被線程A擁有,因此這些線程就進入了該對象的鎖池中。
2.等待池:假設一個線程A調用了某個對象的wait()方法,線程A就會釋放該對象的鎖(由於wait()方法必須出如今synchronized中,這樣天然在執行wait()方法以前線程A就已經擁有了該對象的鎖),同時線程A就進入到了該對象的等待池中。若是另外的一個線程調用了相同對象的notifyAll()方法,那麼處於該對象的等待池中的線程就會所有進入該對象的鎖池中,準備爭奪鎖的擁有權。若是另外的一個線程調用了相同對象的notify()方法,那麼僅僅有一個處於該對象的等待池中的線程(隨機)會進入該對象的鎖池。步驟以下(後面會有代碼實例):
1. t1執行s的一個同步代碼塊,t1持有s的共享鎖,t2在s的鎖池中等待。 2. t1在同步代碼中執行s.wait(0,t1釋放s的共享鎖,進入s的等待池。 3. s的鎖池中t2得到共享鎖執行s的另外一同步代碼塊。 4. t2在同步代碼塊中執行s.notify(),JVM將t1從s的等待池轉入s的鎖池。 5. t2完成同步代碼,釋放鎖,t1得到鎖繼續執行同步代碼。
eg:兩個線程,一個線程將某個對象的某個成員變量的值加1,而另一個線程將這個成員變量的值減1.使得該變量的值始終處於[0,2].初始值爲0:
當一個線程處於阻塞狀態時,另外一個線程調用阻塞線程的interrupt(),阻塞線程收到InterruptException,並退出阻塞狀態,開始進行異常處理。代碼:
@Override public void run() { System.out.println("runnable running"); try { Thread.sleep(1l); } catch (InterruptedException e) { //-----start異常處理---- e.printStackTrace(); //-----end異常處理----- } }
併發編程的知識很是複雜,以上只是一些皮毛,後續還將學習Synchronized,ReentrantLock,Future,FutureTask,Executor,Fork/Join,CompletableFuture,Map-Reduce等相關知識,最後用一個實際項目來完成這部分知識的學習。