Java 虛擬機是一個能夠執行 Java 字節碼的虛擬機進程。Java 源文件被編譯成能被 Java 虛擬機執行的字節碼文件。
Java 被設計成容許應用程序能夠運行在任意的平臺,而不須要程序員爲每個平臺單獨重寫或者是從新編譯。
Java 虛擬機讓這個變爲可能,由於它知道底層硬件平臺的指令長度和其餘特性。javascript
JDK:java開發工具包,包含了JRE、編譯器和其它工具(如:javaDOc、java調試器)
JRE:java運行環境,包含java虛擬機和java程序所需的核心類庫。
若是隻是想跑java程序,那麼只需安裝JRE,若是要寫java程序而且運行,那就須要JDK了。java
若是一個類的變量或者方法前面有static修飾,那麼代表這個方法或者變量屬於這個類,也就是說能夠在不建立對象的狀況下直接使用
當父類的方法被private修飾時,代表該方法爲父類私有,對其餘任何類都是不可見的,所以若是子類定了一個與父類同樣的方法,這對於子類來講至關因而一個新的私有方法,且若是要進行向上轉型,而後去調用該「覆蓋方法」,會產生編譯錯誤程序員
class Parent { private fun() { ... } } class Child extends Parent { private fun() { ... } } class Test { public static void main(String[] args) { Parent c = new Child(); c.fun(); //編譯出錯 } }
static方法時編譯時靜態綁定的,屬於類,而覆蓋是運行時動態綁定的(動態綁定的多態),所以不能覆蓋.面試
java支持的基本數據類型有如下9種:byte,shot,int,long,float,double,char,boolean,void.
自動拆裝箱是java從jdk1.5引用,目的是將原始類型自動的裝換爲相對應的對象,也能夠逆向進行,即拆箱。這也體現java中一切皆對象的宗旨。
所謂自動裝箱就是將原始類型自動的轉換爲對應的對象,而拆箱就是將對象類型轉換爲基本類型。java中的自動拆裝箱一般發生在變量賦值的過程當中,如:算法
Integer object = 3; //自動裝箱 int o = object; //拆箱
在java中,應該注意自動拆裝箱,由於有時可能由於java自動裝箱機制,而致使建立了許多對象,對於內存小的平臺會形成壓力。編程
覆蓋也叫重寫,發生在子類與父類之間,表示子類中的方法能夠與父類中的某個方法的名稱和參數徹底相同,經過子類建立的實例對象調用這個方法時,將調用子類中的定義方法,這至關於把父類中定義的那個徹底相同的方法給覆蓋了,這也是面向對象編程的多態性的一種表現。
重載是指在一個類中,能夠有多個相同名稱的方法,可是他們的參數列表的個數或類型不一樣,當調用該方法時,根據傳遞的參數類型調用對應參數列表的方法。當參數列表相同但返回值不一樣時,將會出現編譯錯誤,這並非重載,由於jvm沒法根據返回值類型來判斷應該調用哪一個方法。數組
在java中是單繼承的,也就是說一個類只能繼承一個父類。
java中實現多繼承有兩種方式,一是接口,而是內部類.緩存
//實現多個接口 若是兩個接口的變量相同 那麼在調用該變量的時候 編譯出錯 interface interface1 { static String field = "dd"; public void fun1(); } interface interface2 { static String field = "dddd"; public void fun2(); } class child implements interface1,interface2 { static String field = "dddd"; @Override public void fun2() { } @Override public void fun1() { } } //內部類 間接多繼承 class Child { class Father { private void strong() { System.out.println("父類"); } } class Mother { public void getCute() { System.out.println("母親"); } } public void getStrong() { Father f = new Father(); f.strong(); } public void getCute() { Mother m = new Mother(); m.getCute(); } }
值傳遞就是在方法調用的時候,實參是將本身的一份拷貝賦給形參,在方法內,對該參數值的修改不影響原來實參,常見的例子就是剛開始學習c語言的時候那個交換方法的例子了。
引用傳遞是在方法調用的時候,實參將本身的地址傳遞給形參,此時方法內對該參數值的改變,就是對該實參的實際操做。
在java中只有一種傳遞方式,那就是值傳遞.可能比較讓人迷惑的就是java中的對象傳遞時,對形參的改變依然會影響到該對象的內容。
下面這個例子來講明java中是值傳遞.安全
public class Test { public static void main(String[] args) { StringBuffer sb = new StringBuffer("hello "); getString(sb); System.out.println(sb); } public static void getString(StringBuffer s) { //s = new StringBuffer("ha"); s.append("world"); } }
在上面這個例子中,當前輸出結果爲:hello world。這並無什麼問題,可能就是你們日常所理解的引用傳遞,那麼固然會改變StringBuffer的內容。可是若是把上面的註釋去掉,那麼就會輸出:hello.此時sb的值並無變成ha hello. 假如說是引用傳遞的話,那麼形參的s也就是sb的地址,此時在方法裏new StringBuffer(),並將該對象賦給s,也就是說s如今指向了這個新建立的對象.按照引用傳遞的說法,此時對s的改變就是對sb的操做,也就是說sb應該也指向新建立的對象,那麼輸出的結果應該爲ha world.但實際上輸出的僅是hello.這說明sb指向的仍是原來的對象,而形參s指向的纔是建立的對象,這也就驗證了java中的對象傳遞也是值傳遞。數據結構
不一樣點在於:
- 接口中全部的方法隱含的都是抽象的。而抽象類則能夠同時包含抽象和非抽象的方法。
- 類能夠實現不少個接口,可是隻能繼承一個抽象類
- 類若是要實現一個接口,它必需要實現接口聲明的全部方法。可是,類能夠不實現抽象類聲明的全部方法,固然,在這種狀況下,類也必須得聲明成是抽象的。
- 抽象類能夠在不提供接口方法實現的狀況下實現接口。
- Java 接口中聲明的變量默認都是 final 的。抽象類能夠包含非 final 的變量。
- Java 接口中的成員函數默認是 public 的。抽象類的成員函數能夠是 private,protected 或者是 public 。
- 接口是絕對抽象的,不能夠被實例化(java 8已支持在接口中實現默認的方法)。抽象類也不能夠被實例化,可是,若是它包含 main 方法的話是能夠被調用的。
構造方法是不能被子類重寫的,可是構造方法能夠重載,也就是說一個類能夠有多個構造方法。
Math.round(11.5)==12 Math.round(-11.5)==-11 round 方法返回與參數 最接近的長整數,參數加 1/2 後求其 floor.
tring 的長度是不可變的;
StringBuffer的長度是可變的,若是你對字符串中的內容常常進行操做,特別是內容要修改時,那麼使用 StringBuffer,若是最後須要 >String,那麼使用 StringBuffer 的 toString() 方法;線程安全;
StringBuilder 是從 JDK 5 開始,爲StringBuffer該類補充了一個單個線程使用的等價類;一般應該優先使用 StringBuilder 類,因>爲它支持全部相同的操做,但因爲它不執行同步,因此速度更快。
使用字符串的時候要特別當心,若是對一個字符串要常常改變的話,就必定不要用String,不然會建立許多無用的對象出來.
來看一下比較
String s = "hello"+"world"+"i love you"; StringBuffer Sb = new StringBuilder("hello").append("world").append("i love you");
這個時候s有多個字符串進行拼接,按理來講會有多個對象產生,可是jvm會對此進行一個優化,也就是說只建立了一個對象,此時它的執行速度要比StringBuffer拼接快.再看下面這個:
String s2 = "hello"; String s3 = "world"; String s4 = "i love you"; String s1 = s2 + s3 + s4;
上面這種狀況,就會多建立出來三個對象,形成了內存空間的浪費.
java虛擬機主要分爲如下一個區:
方法區:
- 有時候也成爲永久代,在該區內不多發生垃圾回收,可是並不表明不發生GC,在這裏進行的GC主要是對方法區裏的常量池和對類型的卸載
- 方法區主要用來存儲已被虛擬機加載的類的信息、常量、靜態變量和即時編譯器編譯後的代碼等數據。
- 該區域是被線程共享的。
- 方法區裏有一個運行時常量池,用於存放靜態編譯產生的字面量和符號引用。該常量池具備動態性,也就是說常量並不必定是編譯時肯定,運行時生成的常量也會存在這個常量池中。
虛擬機棧:
- 虛擬機棧也就是咱們日常所稱的棧內存,它爲java方法服務,每一個方法在執行的時候都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接和方法出口等信息。
- 虛擬機棧是線程私有的,它的生命週期與線程相同。
- 局部變量表裏存儲的是基本數據類型、returnAddress類型(指向一條字節碼指令的地址)和對象引用,這個對象引用有多是指向對象起始地址的一個指針,也有多是表明對象的句柄或者與對象相關聯的位置。局部變量所需的內存空間在編譯器間肯定
4.操做數棧的做用主要用來存儲運算結果以及運算的操做數,它不一樣於局部變量表經過索引來訪問,而是壓棧和出棧的方式
5.每一個棧幀都包含一個指向運行時常量池中該棧幀所屬方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接.動態連接就是將常量池中的符號引用在運行期轉化爲直接引用。
本地方法棧
本地方法棧和虛擬機棧相似,只不過本地方法棧爲Native方法服務。堆
java堆是全部線程所共享的一塊內存,在虛擬機啓動時建立,幾乎全部的對象實例都在這裏建立,所以該區域常常發生垃圾回收操做。
程序計數器
內存空間小,字節碼解釋器工做時經過改變這個計數值能夠選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理和線程恢復等功能都須要依賴這個計數器完成。該內存區域是惟一一個java虛擬機規範沒有規定任何OOM狀況的區域。
判斷一個對象是否存活有兩種方法:
- 引用計數法
所謂引用計數法就是給每個對象設置一個引用計數器,每當有一個地方引用這個對象時,就將計數器加一,引用失效時,計數器就減一。當一個對象的引用計數器爲零時,說明此對象沒有被引用,也就是「死對象」,將會被垃圾回收.
引用計數法有一個缺陷就是沒法解決循環引用問題,也就是說當對象A引用對象B,對象B又引用者對象A,那麼此時A,B對象的引用計數器都不爲零,也就形成沒法完成垃圾回收,因此主流的虛擬機都沒有采用這種算法。
2.可達性算法(引用鏈法)
該算法的思想是:從一個被稱爲GC Roots的對象開始向下搜索,若是一個對象到GC Roots沒有任何引用鏈相連時,則說明此對象不可用。
在java中能夠做爲GC Roots的對象有如下幾種:
雖然這些算法能夠斷定一個對象是否能被回收,可是當知足上述條件時,一個對象比不必定會被回收。當一個對象不可達GC Root時,這個對象並
不會立馬被回收,而是出於一個死緩的階段,若要被真正的回收須要經歷兩次標記
若是對象在可達性分析中沒有與GC Root的引用鏈,那麼此時就會被第一次標記而且進行一次篩選,篩選的條件是是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法或者已被虛擬機調用過,那麼就認爲是不必的。
若是該對象有必要執行finalize()方法,那麼這個對象將會放在一個稱爲F-Queue的對隊列中,虛擬機會觸發一個Finalize()線程去執行,此線程是低優先級的,而且虛擬機不會承諾一直等待它運行完,這是由於若是finalize()執行緩慢或者發生了死鎖,那麼就會形成F-Queue隊列一直等待,形成了內存回收系統的崩潰。GC對處於F-Queue中的對象進行第二次被標記,這時,該對象將被移除"即將回收"集合,等待回收。
在java中,程序員是不須要顯示的去釋放一個對象的內存的,而是由虛擬機自行執行。在JVM中,有一個垃圾回收線程,它是低優先級的,在正常狀況下是不會執行的,只有在虛擬機空閒或者當前堆內存不足時,纔會觸發執行,掃面那些沒有被任何引用的對象,並將它們添加到要回收的集合中,進行回收。
- 標記-清除:
這是垃圾收集算法中最基礎的,根據名字就能夠知道,它的思想就是標記哪些要被回收的對象,而後統一回收。這種方法很簡單,可是會有兩個主要問題:1.效率不高,標記和清除的效率都很低;2.會產生大量不連續的內存碎片,致使之後程序在分配較大的對象時,因爲沒有充足的連續內存而提早觸發一次GC動做。- 複製算法:
爲了解決效率問題,複製算法將可用內存按容量劃分爲相等的兩部分,而後每次只使用其中的一塊,當一塊內存用完時,就將還存活的對象複製到第二塊內存上,而後一次性清楚完第一塊內存,再將第二塊上的對象複製到第一塊。可是這種方式,內存的代價過高,每次基本上都要浪費通常的內存。
因而將該算法進行了改進,內存區域再也不是按照1:1去劃分,而是將內存劃分爲8:1:1三部分,較大那分內存交Eden區,其他是兩塊較小的內存區叫Survior區。每次都會優先使用Eden區,若Eden區滿,就將對象複製到第二塊內存區上,而後清除Eden區,若是此時存活的對象太多,以致於Survivor不夠時,會將這些對象經過分配擔保機制複製到老年代中。(java堆又分爲新生代和老年代)- 標記-整理
該算法主要是爲了解決標記-清除,產生大量內存碎片的問題;當對象存活率較高時,也解決了複製算法的效率問題。它的不一樣之處就是在清除對象的時候現將可回收對象移動到一端,而後清除掉端邊界之外的對象,這樣就不會產生內存碎片了。
分代收集
如今的虛擬機垃圾收集大多采用這種方式,它根據對象的生存週期,將堆分爲新生代和老年代。在新生代中,因爲對象生存期短,每次回收都會有大量對象死去,那麼這時就採用複製算法。老年代裏的對象存活率較高,沒有額外的空間進行分配擔保,因此可使用標記-整理或者 標記-清除。
java內存模型(JMM)是線程間通訊的控制機制.JMM定義了主內存和線程之間抽象關係。線程之間的共享變量存儲在主內存(main memory)中,每一個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其餘的硬件和編譯器優化。Java內存模型的抽象示意圖以下:
從上圖來看,線程A與線程B之間如要通訊的話,必需要經歷下面2個步驟:
- 首先,線程A把本地內存A中更新過的共享變量刷新到主內存中去。
- 而後,線程B到主內存中去讀取線程A以前已更新過的共享變量。
寫的很好:http://www.infoq.com/cn/articles/java-memory-model-1
java類加載須要經歷一下7個過程:
加載
加載時類加載的第一個過程,在這個階段,將完成一下三件事情:
- 經過一個類的全限定名獲取該類的二進制流。
- 將該二進制流中的靜態存儲結構轉化爲方法去運行時數據結構。
- 在內存中生成該類的Class對象,做爲該類的數據訪問入口。
驗證
驗證的目的是爲了確保Class文件的字節流中的信息不回危害到虛擬機.在該階段主要完成如下四鍾驗證:
- 文件格式驗證:驗證字節流是否符合Class文件的規範,如主次版本號是否在當前虛擬機範圍內,常量池中的常量是否有不被支持的類型.
- 元數據驗證:對字節碼描述的信息進行語義分析,如這個類是否有父類,是否集成了不被繼承的類等。
- 字節碼驗證:是整個驗證過程當中最複雜的一個階段,經過驗證數據流和控制流的分析,肯定程序語義是否正確,主要針對方法體的驗證。如:方法中的類型轉換是否正確,跳轉指令是否正確等。
- 符號引用驗證:這個動做在後面的解析過程當中發生,主要是爲了確保解析動做能正確執行。
準備
準備階段是爲類的靜態變量分配內存並將其初始化爲默認值,這些內存都將在方法區中進行分配。準備階段不分配類中的實例變量的內存,實例變量將會在對象實例化時隨着對象一塊兒分配在Java堆中。
public static int value=123;//在準備階段value初始值爲0 。在初始化階段纔會變爲123 。
解析
該階段主要完成符號引用到直接引用的轉換動做。解析動做並不必定在初始化動做完成以前,也有可能在初始化以後。
初始化
初始化時類加載的最後一步,前面的類加載過程,除了在加載階段用戶應用程序能夠經過自定義類加載器參與以外,其他動做徹底由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的Java程序代碼。
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗,解析和初始化,最終造成能夠被虛擬機直接使用的java類型。
當一個類收到了類加載請求時,不會本身先去加載這個類,而是將其委派給父類,由父類去加載,若是此時父類不能加載,反饋給子類,由子類去完成類的加載。
實現經過類的權限定名獲取該類的二進制字節流的代碼塊叫作類加載器。
主要有一下四種類加載器:
- 啓動類加載器(Bootstrap ClassLoader)用來加載java核心類庫,沒法被java程序直接引用。
- 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。
- 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。通常來講,Java 應用的類都是由它來完成加載的。能夠經過 ClassLoader.getSystemClassLoader()來獲取它。
- 用戶自定義類加載器,經過繼承 java.lang.ClassLoader類的方式實現。
- 對象優先在堆的Eden區分配。
- 大對象直接進入老年代.
- 長期存活的對象將直接進入老年代.
當Eden區沒有足夠的空間進行分配時,虛擬機會執行一次Minor GC.Minor Gc一般發生在新生代的Eden區,在這個區的對象生存期短,每每發生Gc的頻率較高,回收速度比較快;Full Gc/Major GC 發生在老年代,通常狀況下,觸發老年代GC的時候不會觸發Minor GC,可是經過配置,能夠在Full GC以前進行一次Minor GC這樣能夠加快老年代的回收速度。
HashMap內部是經過一個數組實現的,只是這個數組比較特殊,數組裏存儲的元素是一個Entry實體(jdk 8爲Node),這個Entry實體主要包含key、value以及一個指向自身的next指針。HashMap是基於hashing實現的,當咱們進行put操做時,根據傳遞的key值獲得它的hashcode,而後再用這個hashcode與數組的長度進行模運算,獲得一個int值,就是Entry要存儲在數組的位置(下標);當經過get方法獲取指定key的值時,會根據這個key算出它的hash值(數組下標),根據這個hash值獲取數組下標對應的Entry,而後判斷Entry裏的key,hash值或者經過equals()比較是否與要查找的相同,若是相同,返回value,不然的話,遍歷該鏈表(有可能就只有一個Entry,此時直接返回null),直到找到爲止,不然返回null。
HashMap之因此在每一個數組元素存儲的是一個鏈表,是爲了解決hash衝突問題,當兩個對象的hash值相等時,那麼一個位置確定是放不下兩個值的,因而hashmap採用鏈表來解決這種衝突,hash值相等的兩個元素會造成一個鏈表。
1.HashTable基於Dictionary類,而HashMap是基於AbstractMap。Dictionary是任何可將鍵映射到相應值的類的抽象父類,而AbstractMap是基於Map接口的實現,它以最大限度地減小實現此接口所需的工做。(在java 8中我查看源碼發現Hashtable並無繼承Dictionary,並且裏面也沒有同步方法,是否是java 8中Hashtable不在同步的了?有沒有人解釋一下?)
- HashMap的key和value都容許爲null,而Hashtable的key和value都不容許爲null。HashMap遇到key爲null的時候,調用putForNullKey方法進行處理,而對value沒有處理;Hashtable遇到null,直接返回NullPointerException。
- Hashtable是同步的,而HashMap是非同步的,可是咱們也能夠經過Collections.synchronizedMap(hashMap),使其實現同步。
jdk 1.6版:ConcurrenHashMap能夠說是HashMap的升級版,ConcurrentHashMap是線程安全的,可是與Hashtablea相比,實現線程安全的方式不一樣。Hashtable是經過對hash表結構進行鎖定,是阻塞式的,當一個線程佔有這個鎖時,其餘線程必須阻塞等待其釋放鎖。ConcurrentHashMap是採用分離鎖的方式,它並無對整個hash表進行鎖定,而是局部鎖定,也就是說當一個線程佔有這個局部鎖時,不影響其餘線程對hash表其餘地方的訪問。
具體實現:ConcurrentHashMap內部有一個Segment<K,V>數組,該Segment對象能夠充當鎖。Segment對象內部有一個HashEntry<K,V>數組,因而每一個Segment能夠守護若干個桶(HashEntry),每一個桶又有多是一個HashEntry鏈接起來的鏈表,存儲發生碰撞的元素。
每一個ConcurrentHashMap在默認併發級下會建立包含16個Segment對象的數組,每一個數組有若干個桶,當咱們進行put方法時,經過hash方法對key進行計算,獲得hash值,找到對應的segment,而後對該segment進行加鎖,而後調用segment的put方法進行存儲操做,此時其餘線程就不能訪問當前的segment,但能夠訪問其餘的segment對象,不會發生阻塞等待。
jdk 1.8版在jdk 8中,ConcurrentHashMap再也不使用Segment分離鎖,而是採用一種樂觀鎖CAS算法來實現同步問題,但其底層仍是「數組+鏈表->紅黑樹」的實現。
List<String> strList = new ArrayList<>(); //for-each for(String str:strList) { System.out.print(str); } //use iterator 儘可能使用這種 更安全(fail-fast) Iterator<String> it = strList.iterator(); while(it.hasNext) { System.out.printf(it.next()); }
Iterator的fail-fast屬性與當前的集合共同起做用,所以它不會受到集合中任何改動的影響。Java.util包中的全部集合類都被設計爲fail->fast的,而java.util.concurrent中的集合類都爲fail-safe的。當檢測到正在遍歷的集合的結構被改變時,Fail-fast迭代器拋出ConcurrentModificationException,而fail-safe迭代器從不拋出ConcurrentModificationException。
- Array能夠容納基本類型和對象,而ArrayList只能容納對象。
- Array是指定大小的,而ArrayList大小是固定的
ArrayList、HashMap、TreeMap和HashTable類提供對元素的隨機訪問。
經過看源碼知道HashSet的實現是依賴於HashMap的,HashSet的值都是存儲在HashMap中的。在HashSet的構造法中會初始化一個HashMap對象,HashSet不容許值重複,所以,HashSet的值是做爲HashMap的key存儲在HashMap中的,當存儲的值已經存在時返回false。
LinkedHashMap也是基於HashMap實現的,不一樣的是它定義了一個Entry header,這個header不是放在Table裏,它是額外獨立出來的。LinkedHashMap經過繼承hashMap中的Entry,並添加兩個屬性Entry before,after,和header結合起來組成一個雙向鏈表,來實現按插入順序或訪問順序排序。LinkedHashMap定義了排序模式accessOrder,該屬性爲boolean型變量,對於訪問順序,爲true;對於插入順序,則爲false。通常狀況下,沒必要指定排序模式,其迭代順序即爲默認爲插入順序。
- ArrayList是基於數組實現,LinkedList是基於鏈表實現
- ArrayList在查找時速度快,LinkedList在插入與刪除時更具優點
線程可定義爲進程內的一個執行單位,或者定義爲進程內的一個可調度實體。 在具備多線程機制的操做系統中,處理機調度的基本單位不是進程而是線程。一個進程能夠有多個線程,並且至少有一個可執行線程。
打個比喻:進程比如工廠(計算機)裏的車間,一個工廠裏有多個車間(進程)在運轉,每一個車間裏有多個工人(線程)在協同工做,這些工人就能夠理解爲線程。
線程和進程的關係:
- 線程是進程的一個組成部分.
- 進程的多個線程都在進程地址空間活動.
- 系統資源是分配給進程的,線程須要資源時,系統從進程的資源裏分配給線程.
- 處理機調度的基本單位是線程.
start()方法被用來啓動新建立的線程,並且start()內部調用了run()方法,這和直接調用run()方法的效果不同。當你調用run()方法的時候,只會是在原來的線程中調用,沒有新的線程啓動,start()方法纔會啓動新線程。
當多個線程訪問某個類時,無論運行時環境採用何種調度方式或者線程將如何交替執行,而且在主調代碼中不須要任何額外的同步或協同,這個類都能表現出正確的行爲。
線程安全的核心是「正確性」,也就是說當多個線程訪問某個類時,可以獲得預期的結果,那麼就是線程安全的。
自旋鎖:自旋鎖在JDK1.6以後就默認開啓了。基於以前的觀察,共享數據的鎖定狀態只會持續很短的時間,爲了這一小段時間而去掛起和恢復線程有點浪費,因此這裏就作了一個處理,讓後面請求鎖的那個線程在稍等一會,可是不放棄處理器的執行時間,看看持有鎖的線程可否快速釋放。爲了讓線程等待,因此須要讓線程執行一個忙循環也就是自旋操做。
在jdk6以後,引入了自適應的自旋鎖,也就是等待的時間再也不固定了,而是由上一次在同一個鎖上的自旋時間及鎖的擁有者狀態來決定
偏向鎖:在JDK1.以後引入的一項鎖優化,目的是消除數據在無競爭狀況下的同步原語。進一步提高程序的運行性能。 偏向鎖就是偏愛的偏,意思是這個鎖會偏向第一個得到他的線程,若是接下來的執行過程當中,改鎖沒有被其餘線程獲取,則持有偏向鎖的線程將永遠不須要再進行同步。偏向鎖能夠提升帶有同步但無競爭的程序性能,也就是說他並不必定老是對程序運行有利,若是程序中大多數的鎖都是被多個不一樣的線程訪問,那偏向模式就是多餘的,在具體問題具體分析的前提下,能夠考慮是否使用偏向鎖。
輕量級鎖:爲了減小得到鎖和釋放鎖所帶來的性能消耗,引入了「偏向鎖」和「輕量級鎖」,因此在Java SE1.6裏鎖一共有四種狀態,無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,它會隨着競爭狀況逐漸升級。鎖能夠升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是爲了提升得到鎖和釋放鎖的效率,下文會詳細分析
java中以synchronize的形式,爲防止資源衝突提供了內置支持。當任務要執行被synchronize關鍵字保護的代碼段時,它將檢查鎖是否可用,而後獲取鎖——執行代碼——釋放鎖。
全部對象都自動含有單一的鎖。當一個線程正在訪問一個對象的synchronized方法,那麼其餘線程不能訪問該對象的其餘synchronized方法,但能夠訪問非synchronized方法。由於一個對象只有一把鎖,當一個線程獲取了該對象的鎖以後,其餘線程沒法獲取該對象的鎖,因此沒法訪問該對象的其餘synchronized方法。
synchronized代碼塊
synchronized(synObject) { }
當在某個線程中執行這段代碼塊,該線程會獲取對象synObject的鎖,從而使得其餘線程沒法同時訪問該代碼塊。synObject能夠是this,表明獲取當前對象的鎖,也能夠是類中的一個屬性,表明獲取該屬性的鎖。
針對每個類,也有一個鎖,因此static synchronize 方法能夠在類的範圍內防止對static數據的併發訪問。若是一個線程執行一個對象的非static synchronized方法,另一個線程須要執行這個對象所屬類的static synchronized方法,此時不會發生互斥現象,由於訪問static synchronized方法佔用的是類鎖,而訪問非static synchronized方法佔用的是對象鎖,因此不存在互斥現象。
對於synchronized方法或者synchronized代碼塊,當出現異常時,JVM會自動釋放當前線程佔用的鎖,所以不會因爲異常致使出現死鎖現象。
ThreadLocal是一個建立線程局部變量的類。一般狀況下咱們建立的變量,能夠被多個線程訪問並修改,經過ThreadLocal建立的變量只能被當前線程訪問。
ThreadLocal內部實現
ThreadLocal提供了set和get方法.
set方法會先獲取當前線程,而後用當前線程做爲句柄,獲取ThreadLocaMap對象,並判斷該對象是否爲空,若是爲空則建立一個,並設置值,不爲空則直接設置值。
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
ThreadLocal的值是放入了當前線程的一個ThreadLocalMap實例中,因此只能在本線程中訪問,其餘線程沒法訪問。
ThreadLocal並不會致使內存泄露,由於ThreadLocalMap中的key存儲的是ThreadLocal實例的弱引用,所以若是應用使用了線程池,即使以前的線程實例處理完以後出於複用的目的依然存活,也不會產生內存泄露。
這是個設計相關的問題,它考察的是面試者對現有系統和一些廣泛存在但看起來不合理的事物的見解。回答這些問題的時候,你要說明爲何把這些方法放在Object類裏是有意義的,還有不把它放在Thread類裏的緣由。一個很明顯的緣由是JAVA提供的鎖是對象級的而不是線程級的,每一個對象都有鎖,經過線程得到。若是線程須要等待某些鎖那麼調用對象中的wait()方法就有意義了。若是wait()方法定義在Thread類中,線程正在等待的是哪一個鎖就不明顯了。簡單的說,因爲wait,notify和notifyAll都是鎖級別的操做,因此把他們定義在Object類中由於鎖屬於對象。
做者:littleKang連接:https://www.jianshu.com/p/04c0d796d877來源:簡書簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。