什么是Java內存模型
前面介紹過了計算機內存模型,這是解決多線程場景下並發問題的一個重要規范。那么具體的實現是如何的呢,不同的編程語言,在實現上可能有所不同。
我們知道,Java程序是需要運行在Java虛擬機上面的,Java內存模型(Java Memory Model ,JMM)就是一種符合內存模型規范的,屏蔽了各種硬件和操作系統的訪問差異的,保證了Java程序在各種平台下對內存的訪問都能保證效果一致的機制及規范。
Java內存模型規定了所有的變量都存儲在主內存中,每條線程還有自己的工作內存,線程的工作內存中保存了該線程中是用到的變量的主內存副本拷貝,線程對變量的所有操作都必須在工作內存中進行,而不能直接讀寫主內存。不同的線程之間也無法直接訪問對方工作內存中的變量,線程間變量的傳遞均需要自己的工作內存和主存之間進行數據同步進行。
而JMM就作用於工作內存和主存之間數據同步過程。他規定了如何做數據同步以及什么時候做數據同步。

這里面提到的主內存和工作內存,讀者可以簡單的類比成計算機內存模型中的主存和緩存的概念。特別需要注意的是,主內存和工作內存與JVM內存結構中的Java堆、棧、方法區等並不是同一個層次的內存划分,無法直接類比。《深入理解Java虛擬機》中認為,如果一定要勉強對應起來的話,從變量、主內存、工作內存的定義來看,主內存主要對應於Java堆中的對象實例數據部分。工作內存則對應於虛擬機棧中的部分區域。
所以,再來總結下,JMM是一種規范,目的是解決由於多線程通過共享內存進行通信時,存在的本地內存數據不一致、編譯器會對代碼指令重排序、處理器會對代碼亂序執行等帶來的問題。
Java內存模型的實現
了解Java多線程的朋友都知道,在Java中提供了一系列和並發處理相關的關鍵字,比如volatile、synchronized、final、concurrent包等。其實這些就是Java內存模型封裝了底層的實現后提供給程序員使用的一些關鍵字。
在開發多線程的代碼的時候,我們可以直接使用synchronized等關鍵字來控制並發,從來就不需要關心底層的編譯器優化、緩存一致性等問題。所以,Java內存模型,除了定義了一套規范,還提供了一系列原語,封裝了底層實現后,供開發者直接使用。
本文並不准備把所有的關鍵字逐一介紹其用法,因為關於各個關鍵字的用法,網上有很多資料。讀者可以自行學習。本文還有一個重點要介紹的就是,我們前面提到,並發編程要解決原子性、有序性和一致性的問題,我們就再來看下,在Java中,分別使用什么方式來保證。
原子性
在Java中,為了保證原子性,提供了兩個高級的字節碼指令monitorenter和monitorexit。在synchronized的實現原理文章中,介紹過,這兩個字節碼,在Java中對應的關鍵字就是synchronized。
因此,在Java中可以使用synchronized來保證方法和代碼塊內的操作是原子性的。
可見性
Java內存模型是通過在變量修改后將新值同步回主內存,在變量讀取前從主內存刷新變量值的這種依賴主內存作為傳遞媒介的方式來實現的。
Java中的volatile關鍵字提供了一個功能,那就是被其修飾的變量在被修改后可以立即同步到主內存,被其修飾的變量在每次是用之前都從主內存刷新。因此,可以使用volatile來保證多線程操作時變量的可見性。
除了volatile,Java中的synchronized和final兩個關鍵字也可以實現可見性。只不過實現方式不同,這里不再展開了。
有序性
在Java中,可以使用synchronized和volatile來保證多線程之間操作的有序性。實現方式有所區別:
volatile關鍵字會禁止指令重排。synchronized關鍵字保證同一時刻只允許一條線程操作。
好了,這里簡單的介紹完了Java並發編程中解決原子性、可見性以及有序性可以使用的關鍵字。讀者可能發現了,好像synchronized關鍵字是萬能的,他可以同時滿足以上三種特性,這其實也是很多人濫用synchronized的原因。
但是synchronized是比較影響性能的,雖然編譯器提供了很多鎖優化技術,但是也不建議過度使用。
1. java事件機制包括三個部分
1、事件。一般繼承自java.util.EventObject類,封裝了事件源對象及跟事件相關的信息。
2、事件監聽器。實現java.util.EventListener接口,注冊在事件源上,當事件源的屬性或狀態改變時,取得相應的監聽器調用其內部的回調方法。
3、事件源。事件發生的地方,由於事件源的某項屬性或狀態發生了改變(比如BUTTON被單擊、TEXTBOX的值發生改變等等)導致某項事件發生。換句話說就是生成了相應的事件對象。因為事件監聽器要注冊在事件源上,所以事件源類中應該要有盛裝監聽器的容器(List,Set等等)。
2. Java的8種基本數據類型的內存占用字節數和取值范圍

3. 包裝類型
包裝類型是對基本數據類型不足之處的補充。
基本數據類型的傳遞方式是值傳遞,而包裝類型是引用傳遞,同時提供了很多數據類型間轉換的方法。
Java1.5 以后可以自動裝箱和拆箱。
6. int 和 Integer 有什么區別
1、Integer是int的包裝類,int則是java的一種基本數據類型
2、Integer變量必須實例化后才能使用,而int變量不需要
3、Integer實際是對象的引用,當new一個Integer時,實際上是生成一個指針指向此對象;而int則是直接存儲數據值
4、Integer的默認值是null,int的默認值是0
5.Java和C++的區別?
- 都是面向對象的語言,都支持封裝、繼承和多態
- Java 不提供指針來直接訪問內存,程序內存更加安全
- Java 的類是單繼承的,C++ 支持多重繼承;雖然 Java 的類不可以多繼承,但是接口可以多繼承。
- Java 有自動內存管理機制,不需要程序員手動釋放無用內存
8. == 和 equals()區別
== : 它的作用是判斷兩個對象的地址是不是相等。即,判斷兩個對象是不是同一個對象(基本數據類型==比較的是值,引用數據類型==比較的是內存地址)。
equals() : 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況:
情況1:類沒有覆蓋 equals() 方法。則通過 equals() 比較該類的兩個對象時,等價於通過“==”比較這兩個對象。
情況2:類覆蓋了 equals() 方法。一般,我們都覆蓋 equals() 方法來比較兩個對象的內容是否相等;若它們的內容相等,則返回 true (即,認為這兩個對象相等)。
說明:
String 中的 equals 方法是被重寫過的,因為 object 的 equals 方法是比較的對象的內存地址,而 String 的 equals 方法比較的是對象的值。
當創建 String 類型的對象時,虛擬機會在常量池中查找有沒有已經存在的值和要創建的值相同的對象,如果有就把它賦給當前引用。如果沒有就在常量池中重新創建一個 String 對象。
9. 面向對象的特征有哪些方面
Java 的四大特性總結如下:
封裝:把對象的屬性和行為(數據)封裝為一個獨立的整體,並盡可能隱藏對象的內部實現細節;
繼承:一種代碼重用機制;
多態:分離了做什么和怎么做,從另一個角度將接口和實現分離開來,消除類型之間的耦合關系;表現形式:重載與重寫;
抽象:對繼承的另一種表述;表現形式:接口(契約)與抽象類(模板)。
一、封裝
利用抽象數據類型將數據和基於數據的操作封裝在一起,使其構成一個不可分割的獨立實體。數據被保護在抽象數據類型的內部,盡可能地隱藏內部的細節,只保留一些對外接口使之與外部發生聯系。用戶無需知道對象內部的細節,但可以通過對象對外提供的接口來訪問該對象。
優點:
減少耦合:可以獨立地開發、測試、優化、使用、理解和修改
減輕維護的負擔:可以更容易被程序員理解,並且在調試的時候可以不影響其他模塊
有效地調節性能:可以通過剖析確定哪些模塊影響了系統的性能
提高軟件的可重用性
降低了構建大型系統的風險:即使整個系統不可用,但是這些獨立的模塊卻有可能是可用的
二、繼承
繼承實現了 IS-A 關系,繼承應該遵循里氏替換原則,子類對象必須能夠替換掉所有父類對象。
三、多態
多態分為編譯時多態和運行時多態。編譯時多態主要指方法的重載,運行時多態指程序中定義的對象引用所指向的具體類型在運行期間才確定。
運行時多態有三個條件:
繼承
覆蓋(重寫)
向上轉型
6.String,StringBuffer與StringBuilder的區別
可變性
簡單的來說:String 類中使用 final 關鍵字修飾字符數組來保存字符串,private final char value[],所以 String 對象是不可變的。而StringBuilder 與 StringBuffer 都繼承自 AbstractStringBuilder 類,在 AbstractStringBuilder 中也是使用字符數組保存字符串char[]value 但是沒有用 final 關鍵字修飾,所以這兩種對象都是可變的。
StringBuilder 與 StringBuffer 的構造方法都是調用父類構造方法也就是 AbstractStringBuilder 實現的,大家可以自行查閱源碼。
AbstractStringBuilder.java
abstract class AbstractStringBuilder implements Appendable, CharSequence {
char[] value;
int count;
AbstractStringBuilder() {
}
AbstractStringBuilder(int capacity) {
value = new char[capacity];
}
線程安全性
String 中的對象是不可變的,也就可以理解為常量,線程安全。AbstractStringBuilder 是 StringBuilder 與 StringBuffer 的公共父類,定義了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。StringBuffer 對方法加了同步鎖或者對調用的方法加了同步鎖,所以是線程安全的。StringBuilder 並沒有對方法進行加同步鎖,所以是非線程安全的。
性能
每次對 String 類型進行改變的時候,都會生成一個新的 String 對象,然后將指針指向新的 String 對象。StringBuffer 每次都會對 StringBuffer 對象本身進行操作,而不是生成新的對象並改變對象引用。相同情況下使用 StringBuilder 相比使用 StringBuffer 僅能獲得 10%~15% 左右的性能提升,但卻要冒多線程不安全的風險。
對於三者使用的總結:
1. 操作少量的數據: 適用String
2. 單線程操作字符串緩沖區下操作大量數據: 適用StringBuilder
3. 多線程操作字符串緩沖區下操作大量數據: 適用StringBuffer
13. 靜態變量和實例變量區別
- 在語法定義上的區別:靜態變量前要加static關鍵字,而實例變量前則不加。
- 在程序運行時的區別:實例變量屬於某個對象的屬性,必須創建了實例對象,其中的實例變量才會被分配空間,才能使用這個實例變量。
- 靜態變量不屬於某個實例對象,而是屬於類,所以也稱為類變量,只要程序加載了類的字節碼,不用創建任何實例對象,靜態變量就會被分配空間,靜態變量就可以被使用了。總之,實例變量必須創建對象后才可以通過這個對象來使用,靜態變量則可以直接使用類名來引用
14. final,finally,finalize的區別。
final用於聲明屬性,方法和類分別表示屬性不可變,方法不可覆蓋,類不可繼承。
finally是異常處理語句結構的一部分,表示總是執行。
finalize是Object類的一個方法在垃圾收集器執行的時候會調用被回收對象的此方法
什么是類,什么是對象
- 類就是具備某些共同特征的實體的集合,它是一種抽象的數據類型,它是對所具有相同特征實體的抽象。在面向對象的程序設計語言中,類是對一類“事物”的屬性與行為的抽象。
- 對象就是一個真實世界中的實體,對象與實體是一一對應關系的,意思就是現實世界的每一個實體都是一個對象,所以對象是一個具體的概念。
類是對象的集合,對象是類的實例;對象是通過new className產生的,用來調用類的方法;類的構造方法 .。
8.重寫和重載的區別( Override和Overload的含義以及區別)
重載: 發生在同一個類中,方法名必須相同,參數類型不同、個數不同、順序不同,方法返回值和訪問修飾符可以不同,發生在編譯時。
重寫: 發生在父子類中,方法名、參數列表必須相同,返回值范圍小於等於父類,拋出的異常范圍小於等於父類,訪問修飾符范圍大於等於父類;如果父類方法訪問修飾符為 private 則子類就不能重寫該方法。
方法重寫只能在子類中重寫一次,而方法的重載可以重載多次
15. 抽象類和接口區別
(1)抽象類可以有構造方法,接口中不能有構造方法。
(2)抽象類中可以有普通成員變量,接口中沒有普通成員變量
(3)抽象類中可以包含靜態方法,接口中不能包含靜態方法
(4) 一個類可以實現多個接口,但只能繼承一個抽象類。
(5)接口可以被多重實現,抽象類只能被單一繼承
(6)如果抽象類實現接口,則可以把接口中方法映射到抽象類中作為抽象方法而不必實現,而在抽象類的子類中實現接口中方法
接口和抽象類的相同點:
(1) 都可以被繼承
(2) 都不能被實例化
(3) 都可以包含方法聲明
(4) 派生類必須實現未實現的方法
17. 為什么java中在重寫equals()方法后必須對hashCode()方法進行重寫
重寫equals但不重寫HashCode會出現的問題
在使用Set時,若向其加入兩個相同(equals返回為true)的對象,由於hashCode函數沒有進行重寫,那么這兩個對象的hashCode值必然不同,它們很有可能被分散到不同的桶中,容易造成重復對象的存在。

18. Object方法
1.clone方法
保護方法,實現對象的淺復制,只有實現了Cloneable接口才可以調用該方法,否則拋出CloneNotSupportedException異常。
2.getClass方法
final方法,獲得運行時類型。
3.toString方法
該方法用得比較多,一般子類都有覆蓋。
4.finalize方法
該方法用於釋放資源。因為無法確定該方法什么時候被調用,很少使用。
5.equals方法
該方法是非常重要的一個方法。一般equals和==是不一樣的,但是在Object中兩者是一樣的。子類一般都要重寫這個方法。
6.hashCode方法
該方法用於哈希查找,重寫了equals方法一般都要重寫hashCode方法。這個方法在一些具有哈希功能的Collection中用到。
一般必須滿足obj1.equals(obj2)==true。可以推出obj1.hash- Code()==obj2.hashCode(),但是hashCode相等不一定就滿足equals。不過為了提高效率,應該盡量使上面兩個條件接近等價。
7.wait方法
wait方法就是使當前線程等待該對象的鎖,當前線程必須是該對象的擁有者,也就是具有該對象的鎖。wait()方法一直等待,直到獲得鎖或者被中斷。wait(long timeout)設定一個超時間隔,如果在規定時間內沒有獲得鎖就返回。
調用該方法后當前線程進入睡眠狀態,直到以下事件發生。
(1)其他線程調用了該對象的notify方法。
(2)其他線程調用了該對象的notifyAll方法。
(3)其他線程調用了interrupt中斷該線程。
(4)時間間隔到了。
此時該線程就可以被調度了,如果是被中斷的話就拋出一個InterruptedException異常。
8.notify方法
該方法喚醒在該對象上等待的某個線程。
9.notifyAll方法
該方法喚醒在該對象上等待的所有線程。
19. switch支持的數據類型
基本數據類型:byte, short, char, int
包裝數據類型:Byte, Short, Character, Integer
枚舉類型:Enum
字符串類型:String(Jdk 7+ 開始支持)
20. 內部類
內部類指的是在一個類的內部所定義的類,類名不需要和源文件名相同。在Java中,內部類是一個編譯時的概念,一旦編譯成功,內部類和外部類就會成為兩個完全不同的類,共有四種類型:
成員內部類:成員內部類是外圍類的一個成員,是依附於外圍類的,所以,只有先創建了外圍類對象才能夠創建內部類對象。也正是由於這個原因,成員內部類也不能含有 static 的變量和方法;
靜態內部類:靜態內部類,就是修飾為static的內部類,該內部類對象不依賴於外部類對象,就是說我們可以直接創建內部類對象,但其只可以直接訪問外部類的所有靜態成員和靜態方法;
局部內部類:局部內部類和成員內部類一樣被編譯,只是它的作用域發生了改變,它只能在該方法和屬性中被使用,出了該方法和屬性就會失效;
匿名內部類:定義匿名內部類的前提是,內部類必須要繼承一個類或者實現接口,格式為 new 父類或者接口(){定義子類的內容(如函數等)}。也就是說,匿名內部類最終提供給我們的是一個 匿名子類的對象。