
即將到來金三銀四人才招聘的高峰期,渴望跳槽的朋友肯定跟我一樣四處找以往的面試題,但又感覺找的又不完整,在這里我將把我所見到的題目做一總結(jié),并盡力將答案術(shù)語化、標(biāo)準(zhǔn)化。預(yù)祝大家面試順利。
術(shù)語會(huì)讓你的面試更有說服力,讓你感覺更踏實(shí),建議大家多記背點(diǎn)術(shù)語。
一、簡(jiǎn)單說下什么是跨平臺(tái)
術(shù)語:操作系統(tǒng)指令集、屏蔽系統(tǒng)之間的差異
由于各種操作系統(tǒng)所支持的指令集不是完全一致,所以在操作系統(tǒng)之上加個(gè)虛擬機(jī)可以來提供統(tǒng)一接口,屏蔽系統(tǒng)之間的差異。
二、Java有幾種基本數(shù)據(jù)類型
有八種基本數(shù)據(jù)類型。

各自占用幾字節(jié)也記一下。
三、面向?qū)ο筇卣?/h1>
面向?qū)ο蟮木幊陶Z言有封裝、繼承 、抽象、多態(tài)等4個(gè)主要的特征。
封裝: 把描述一個(gè)對(duì)象的屬性和行為的代碼封裝在一個(gè)模塊中,也就是一個(gè)類中,屬性用變量定義,行為用方法進(jìn)行定義,方法可以直接訪問同一個(gè)對(duì)象中的屬性。
抽象: 把現(xiàn)實(shí)生活中的對(duì)象抽象為類。分為過程抽象和數(shù)據(jù)抽象
- 數(shù)據(jù)抽象 -->鳥有翅膀,羽毛等(類的屬性)
- 過程抽象 -->鳥會(huì)飛,會(huì)叫(類的方法)
繼承:子類繼承父類的特征和行為。子類可以有父類的方法,屬性(非private)。子類也可以對(duì)父類進(jìn)行擴(kuò)展,也可以重寫父類的方法。缺點(diǎn)就是提高代碼之間的耦合性。
多態(tài): 多態(tài)是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時(shí)并不確定,而是在程序運(yùn)行期間才確定(比如:向上轉(zhuǎn)型,只有運(yùn)行才能確定其對(duì)象屬性)。方法覆蓋和重載體現(xiàn)了多態(tài)性。
四、為什么要有包裝類型
術(shù)語:讓基本類型也具有對(duì)象的特征

為了讓基本類型也具有對(duì)象的特征,就出現(xiàn)了包裝類型(如我們?cè)谑褂眉项愋虲ollection時(shí)就一定要使用包裝類型而非基本類型)因?yàn)槿萜鞫际茄bobject的,這是就需要這些基本類型的包裝器類了。
自動(dòng)裝箱:new Integer(6);,底層調(diào)用:Integer.valueOf(6)
自動(dòng)拆箱: int i = new Integer(6);,底層調(diào)用i.intValue();方法實(shí)現(xiàn)。
Integer i = 6;
Integer j = 6;
System.out.println(i==j);
答案在下面這段代碼中找:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
二者的區(qū)別:
- 聲明方式不同:基本類型不使用new關(guān)鍵字,而包裝類型需要使用new關(guān)鍵字來在堆中分配存儲(chǔ)空間;
- 存儲(chǔ)方式及位置不同:基本類型是直接將變量值存儲(chǔ)在棧中,而包裝類型是將對(duì)象放在堆中,然后通過引用來使用;
- 初始值不同:基本類型的初始值如int為0,boolean為false,而包裝類型的初始值為null;
- 使用方式不同:基本類型直接賦值直接使用就好,而包裝類型在集合如Collection、Map時(shí)會(huì)使用到。
五、==和equals區(qū)別
- ==較的是兩個(gè)引用在內(nèi)存中指向的是不是同一對(duì)象(即同一內(nèi)存空間),也就是說在內(nèi)存空間中的存儲(chǔ)位置是否一致。如果兩個(gè)對(duì)象的引用相同時(shí)(指向同一對(duì)象時(shí)),“==”操作符返回true,否則返回flase。
- equals用來比較某些特征是否一樣。我們平時(shí)用的String類等的equals方法都是重寫后的,實(shí)現(xiàn)比較兩個(gè)對(duì)象的內(nèi)容是否相等。
我們來看看String重寫的equals方法:
它不止判斷了內(nèi)存地址,還增加了字符串是否相同的比較。
public boolean equals(Object anObject) {
//判斷內(nèi)存地址是否相同
if (this == anObject) {
return true;
}
// 判斷參數(shù)類型是否是String類型
if (anObject instanceof String) {
// 強(qiáng)轉(zhuǎn)
String anotherString = (String)anObject;
int n = value.length;
// 判斷兩個(gè)字符串長(zhǎng)度是否相等
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
// 一一比較 字符是否相同
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
六、String、StringBuffer和StringBuilder區(qū)別
java中String、StringBuffer、StringBuilder是編程中經(jīng)常使用的字符串類,他們之間的區(qū)別也是經(jīng)常在面試中會(huì)問到的問題。現(xiàn)在總結(jié)一下,看看他們的不同與相同。
1. 數(shù)據(jù)可變和不可變
- String底層使用一個(gè)不可變的字符數(shù)組private final char value[];所以它內(nèi)容不可變。
- StringBuffer和StringBuilder都繼承了AbstractStringBuilder底層使用的是可變字符數(shù)組:char[] value;
2. 線程安全
- StringBuilder是線程不安全的,效率較高;而StringBuffer是線程安全的,效率較低。
通過他們的append()方法來看,StringBuffer是有同步鎖,而StringBuilder沒有:
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}
3. 相同點(diǎn)
StringBuilder與StringBuffer有公共父類AbstractStringBuilder。
最后,操作可變字符串速度:StringBuilder > StringBuffer > String,這個(gè)答案就顯得不足為奇了。
七、講一下Java中的集合
- Collection下:List系(有序、元素允許重復(fù))和Set系(無序、元素不重復(fù))
set根據(jù)equals和hashcode判斷,一個(gè)對(duì)象要存儲(chǔ)在Set中,必須重寫equals和hashCode方法
- Map下:HashMap線程不同步;ConcurrentMap線程同步
- Collection系列和Map系列:Map是對(duì)Collection的補(bǔ)充,兩個(gè)沒什么關(guān)系
八、ArrayList和LinkedList區(qū)別?
之前專門有寫過ArrayList和LinkedList源碼的文章。
- ArrayList是實(shí)現(xiàn)了基于動(dòng)態(tài)數(shù)組的數(shù)據(jù)結(jié)構(gòu),LinkedList基于鏈表的數(shù)據(jù)結(jié)構(gòu)。
- 對(duì)于隨機(jī)訪問get和set,ArrayList覺得優(yōu)于LinkedList,因?yàn)長(zhǎng)inkedList要移動(dòng)指針。
- 對(duì)于新增和刪除操作add和remove,LinedList比較占優(yōu)勢(shì),因?yàn)锳rrayList要移動(dòng)數(shù)據(jù)。
九、ConcurrentModificationException異常出現(xiàn)的原因
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
list.remove(integer);
}
}
}
執(zhí)行上段代碼是有問題的,會(huì)拋出ConcurrentModificationException異常。
原因:調(diào)用list.remove()方法導(dǎo)致modCount和expectedModCount的值不一致。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
解決辦法:在迭代器中如果要?jiǎng)h除元素的話,需要調(diào)用Iterator類的remove方法。
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
iterator.remove(); //注意這個(gè)地方
}
}
}
十、HashMap和HashTable、ConcurrentHashMap區(qū)別?
相同點(diǎn):
- HashMap和Hashtable都實(shí)現(xiàn)了Map接口
- 都可以存儲(chǔ)key-value數(shù)據(jù)
不同點(diǎn):
- HashMap可以把null作為key或value,HashTable不可以
- HashMap線程不安全,效率高。HashTable線程安全,效率低。
- HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。
什么是fail-fast? 就是最快的時(shí)間能把錯(cuò)誤拋出而不是讓程序執(zhí)行。
1. 如何保證線程安全又效率高?
Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴(kuò)展性更好。
ConcurrentHashMap將整個(gè)Map分為N個(gè)segment(類似HashTable),可以提供相同的線程安全,但是效率提升N倍,默認(rèn)N為16。
2. 我們能否讓HashMap同步?
HashMap可以通過下面的語句進(jìn)行同步: Map m = Collections.synchronizeMap(hashMap);
十一、拷貝文件的工具類使用字節(jié)流還是字符流
答案:字節(jié)流
1. 什么是字節(jié)流,什么是字符流?
- 字節(jié)流:傳遞的是字節(jié)(二進(jìn)制),
- 字符流:傳遞的是字符
2. 答案
我們并不支持下載的文件有沒有包含字節(jié)流(圖片、影像、音源),所以考慮到通用性,我們會(huì)用字節(jié)流。
十二、線程創(chuàng)建方式
這個(gè)之前自己做過總結(jié),也算比較全面。
1. 方法一:繼承Thread類,作為線程對(duì)象存在(繼承Thread對(duì)象)
public class CreatThreadDemo1 extends Thread{
/**
* 構(gòu)造方法: 繼承父類方法的Thread(String name);方法
* @param name
*/
public CreatThreadDemo1(String name){
super(name);
}
@Override
public void run() {
while (!interrupted()){
System.out.println(getName()+"線程執(zhí)行了...");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
CreatThreadDemo1 d1 = new CreatThreadDemo1("first");
CreatThreadDemo1 d2 = new CreatThreadDemo1("second");
d1.start();
d2.start();
d1.interrupt(); //中斷第一個(gè)線程
}
}
常規(guī)方法,不多做介紹了,interrupted方法,是來判斷該線程是否被中斷。(終止線程不允許用stop方法,該方法不會(huì)施放占用的資源。所以我們?cè)谠O(shè)計(jì)程序的時(shí)候,要按照中斷線程的思維去設(shè)計(jì),就像上面的代碼一樣)。
讓線程等待的方法
- Thread.sleep(200); //線程休息2ms
- Object.wait(); //讓線程進(jìn)入等待,直到調(diào)用Object的notify或者notifyAll時(shí),線程停止休眠
2. 方法二:實(shí)現(xiàn)runnable接口,作為線程任務(wù)存在
public class CreatThreadDemo2 implements Runnable {
@Override
public void run() {
while (true){
System.out.println("線程執(zhí)行了...");
}
}
public static void main(String[] args) {
//將線程任務(wù)傳給線程對(duì)象
Thread thread = new Thread(new CreatThreadDemo2());
//啟動(dòng)線程
thread.start();
}
}
Runnable 只是來修飾線程所執(zhí)行的任務(wù),它不是一個(gè)線程對(duì)象。想要啟動(dòng)Runnable對(duì)象,必須將它放到一個(gè)線程對(duì)象里。
3. 方法三:匿名內(nèi)部類創(chuàng)建線程對(duì)象
public class CreatThreadDemo3 extends Thread{
public static void main(String[] args) {
//創(chuàng)建無參線程對(duì)象
new Thread(){
@Override
public void run() {
System.out.println("線程執(zhí)行了...");
}
}.start();
//創(chuàng)建帶線程任務(wù)的線程對(duì)象
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程執(zhí)行了...");
}
}).start();
//創(chuàng)建帶線程任務(wù)并且重寫run方法的線程對(duì)象
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable run 線程執(zhí)行了...");
}
}){
@Override
public void run() {
System.out.println("override run 線程執(zhí)行了...");
}
}.start();
}
}
4. 方法四:創(chuàng)建帶返回值的線程
public class CreatThreadDemo4 implements Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CreatThreadDemo4 demo4 = new CreatThreadDemo4();
FutureTask<Integer> task = new FutureTask<Integer>(demo4); //FutureTask最終實(shí)現(xiàn)的是runnable接口
Thread thread = new Thread(task);
thread.start();
System.out.println("我可以在這里做點(diǎn)別的業(yè)務(wù)邏輯...因?yàn)镕utureTask是提前完成任務(wù)");
//拿出線程執(zhí)行的返回值
Integer result = task.get();
System.out.println("線程中運(yùn)算的結(jié)果為:"+result);
}
//重寫Callable接口的call方法
@Override
public Object call() throws Exception {
int result = 1;
System.out.println("業(yè)務(wù)邏輯計(jì)算中...");
Thread.sleep(3000);
return result;
}
}
Callable接口介紹:
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
返回指定泛型的call方法。然后調(diào)用FutureTask對(duì)象的get方法得道call方法的返回值。
5. 方法五:定時(shí)器Timer
public class CreatThreadDemo5 {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定時(shí)器線程執(zhí)行了...");
}
},0,1000); //延遲0,周期1s
}
}
6. 方法六:線程池創(chuàng)建線程
public class CreatThreadDemo6 {
public static void main(String[] args) {
//創(chuàng)建一個(gè)具有10個(gè)線程的線程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
long threadpoolUseTime = System.currentTimeMillis();
for (int i = 0;i<10;i++){
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"線程執(zhí)行了...");
}
});
}
long threadpoolUseTime1 = System.currentTimeMillis();
System.out.println("多線程用時(shí)"+(threadpoolUseTime1-threadpoolUseTime));
//銷毀線程池
threadPool.shutdown();
threadpoolUseTime = System.currentTimeMillis();
}
}
7. 方法七:利用java8新特性 stream 實(shí)現(xiàn)并發(fā)
public class CreatThreadDemo7 {
public static void main(String[] args) {
List<Integer> values = Arrays.asList(10,20,30,40);
//parallel 平行的,并行的
int result = values.parallelStream().mapToInt(p -> p*2).sum();
System.out.println(result);
//怎么證明它是并發(fā)處理呢
values.parallelStream().forEach(p-> System.out.println(p));
}
}
200
40
10
20
30
怎么證明它是并發(fā)處理呢,他們并不是按照順序輸出的 。
十三、兩個(gè)對(duì)象的hashCode相同,則equals也一定為true,對(duì)嗎?
不對(duì),答案見下面的代碼:
@Override
public int hashCode() {
return 1;
}
兩個(gè)對(duì)象equals為true,則hashCode也一定相同,對(duì)嗎?
這塊肯定是有爭(zhēng)議的。面試的時(shí)候這樣答:如果按照官方設(shè)計(jì)要求來打代碼的話,hashcode一定相等。但是如果不按官方照設(shè)計(jì)要求、不重寫hashcode方法,就會(huì)出現(xiàn)不相等的情況。
十四、Java線程池用過沒有?
Executors提供了四種方法來創(chuàng)建線程池。
- newFixedThreadPool() :創(chuàng)建固定大小的線程池。
- newCachedThreadPool(): 創(chuàng)建無限大小的線程池,線程池中線程數(shù)量不固定,可根據(jù)需求自動(dòng)更改。
- newSingleThreadPool() : 創(chuàng)建單個(gè)線程池,線程池中只有一個(gè)線程。
- newScheduledThreadPool() 創(chuàng)建固定大小的線程池,可以延遲或定時(shí)的執(zhí)行任務(wù)。
手寫一個(gè):
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
threadPool.execute(() -> {
for (int i = 0; i< 20;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
});
threadPool.shutdown();
}
線程池作用
- 限制線程個(gè)數(shù),避免線程過多導(dǎo)致系統(tǒng)運(yùn)行緩慢或崩潰。
- 不需要頻繁的創(chuàng)建和銷毀,節(jié)約資源、響應(yīng)更快。
十五、Math.round(-2.5)等于多少?
不要認(rèn)為它是四舍五入!不要認(rèn)為它是四舍五入!不要認(rèn)為它是四舍五入!
口訣:+0.5后向下取整。所以結(jié)果是-2。
留個(gè)題,Math.round(-2.6)結(jié)果和Math.round(2.6)結(jié)果
十六、面向?qū)ο罅笤瓌t
- 單一職責(zé)原則——SRP:讓每個(gè)類只專心處理自己的方法。
- 開閉原則——OCP:軟件中的對(duì)象(類,模塊,函數(shù)等)應(yīng)該對(duì)于擴(kuò)展是開放的,但是對(duì)于修改是關(guān)閉的。
- 里式替換原則——LSP:子類可以去擴(kuò)展父類,但是不能改變父類原有的功能。
- 依賴倒置原則——DIP:應(yīng)該通過調(diào)用接口或抽象類(比較高層),而不是調(diào)用實(shí)現(xiàn)類(細(xì)節(jié))。
- 接口隔離原則——ISP:把接口分成滿足依賴關(guān)系的最小接口,實(shí)現(xiàn)類中不能有不需要的方法。
- 迪米特原則——LOD:高內(nèi)聚,低耦合。
十七、Static和Final區(qū)別

十八、String s = "hello"和String s = new String("hello");區(qū)別
String s = new String("hello");可能創(chuàng)建兩個(gè)對(duì)象也可能創(chuàng)建一個(gè)對(duì)象。如果常量池中有hello字符串常量的話,則僅僅在堆中創(chuàng)建一個(gè)對(duì)象。如果常量池中沒有hello對(duì)象,則堆上和常量池都需要?jiǎng)?chuàng)建。
String s = "hello"這樣創(chuàng)建的對(duì)象,JVM會(huì)直接檢查字符串常量池是否已有"hello"字符串對(duì)象,如沒有,就分配一個(gè)內(nèi)存存放"hello",如有了,則直接將字符串常量池中的地址返回給棧。(沒有new,沒有堆的操作)
十九、引用類型是占用幾個(gè)字節(jié)?
hotspot在64位平臺(tái)上,占8個(gè)字節(jié),在32位平臺(tái)上占4個(gè)字節(jié)。
二十、(1<3)?"a":"b")+3+4和(1<3)?"a":"b")+(3+4)區(qū)別
System.out.println(((1<3)?"a":"b")+3+4);
System.out.println(((1<3)?"a":"b")+(3+4));
控制臺(tái):
a34
a7
1. 什么情況下,加號(hào)會(huì)變成字符串連接符
依據(jù)上面的例子來思考。
二十一、Java中的switch選擇結(jié)構(gòu)可以使用數(shù)據(jù)類型的數(shù)據(jù)(JDK1.8)
- char
- byte
- short
- int
- Character
- Byte
- Short
- Integer
- String
- enum
更好的記憶方法:
基本類型中,沒有boolean和浮點(diǎn)類型+長(zhǎng)類型long.相應(yīng)的包裝類型也沒有。
外加String和enum。
二十二、4&5``4^5``4&10>>1各等于多少
// 0100 & 0101 = 0100 = 4
System.out.println(4&5);
// 0100 ^ 0101 = 0001 = 1
System.out.println(4^5);
System.out.println(10>>1);
// 有疑問參考下面的運(yùn)算符優(yōu)先級(jí)
System.out.println(4&10>>1);
4
1
5
4
4|5等于多少呢
答案:5
運(yùn)算符優(yōu)先級(jí)

二十三、某些java類為什么要實(shí)現(xiàn)Serializable接口
為了網(wǎng)絡(luò)進(jìn)行傳輸或者持久化
什么是序列化
將對(duì)象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问降倪^程
除了實(shí)現(xiàn)Serializable接口還有什么序列化方式
- Json序列化
- FastJson序列化
- ProtoBuff序列化 ...
二十四、JVM垃圾處理方法
標(biāo)記-清除算法(老年代)
該算法分為“標(biāo)記”和“清除”兩個(gè)階段: 首先標(biāo)記出所有需要回收的對(duì)象(可達(dá)性分析), 在標(biāo)記完成后統(tǒng)一清理掉所有被標(biāo)記的對(duì)象.
該算法會(huì)有兩個(gè)問題:
- 效率問題:標(biāo)記和清除效率不高。
- 空間問題:標(biāo)記清除后會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片, 空間碎片太多可能會(huì)導(dǎo)致在運(yùn)行過程中需要分配較大對(duì)象時(shí)無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集。
所以它一般用于"垃圾不太多的區(qū)域,比如老年代"。
復(fù)制算法(新生代)
該算法的核心是將可用內(nèi)存按容量劃分為大小相等的兩塊, 每次只用其中一塊, 當(dāng)這一塊的內(nèi)存用完, 就將還存活的對(duì)象(非垃圾)復(fù)制到另外一塊上面, 然后把已使用過的內(nèi)存空間一次清理掉.
優(yōu)點(diǎn):不用考慮碎片問題,方法簡(jiǎn)單高效。 缺點(diǎn):內(nèi)存浪費(fèi)嚴(yán)重。
現(xiàn)代商用VM的新生代均采用復(fù)制算法, 但由于新生代中的98%的對(duì)象都是生存周期極短的, 因此并不需完全按照1∶1的比例劃分新生代空間, 而是將新生代劃分為一塊較大的Eden區(qū)和兩塊較小的Survivor區(qū)(HotSpot默認(rèn)Eden和Survivor的大小比例為8∶1), 每次只用Eden和其中一塊Survivor. 當(dāng)發(fā)生MinorGC時(shí), 將Eden和Survivor中還存活著的對(duì)象一次性地拷貝到另外一塊Survivor上, 最后清理掉Eden和剛才用過的Survivor的空間. 當(dāng)Survivor空間不夠用(不足以保存尚存活的對(duì)象)時(shí), 需要依賴?yán)夏甏M(jìn)行空間分配擔(dān)保機(jī)制, 這部分內(nèi)存直接進(jìn)入老年代。
復(fù)制算法的空間分配擔(dān)保: 在執(zhí)行Minor GC前, VM會(huì)首先檢查老年代是否有足夠的空間存放新生代尚存活對(duì)象, 由于新生代使用復(fù)制收集算法, 為了提升內(nèi)存利用率, 只使用了其中一個(gè)Survivor作為輪換備份, 因此當(dāng)出現(xiàn)大量對(duì)象在Minor GC后仍然存活的情況時(shí), 就需要老年代進(jìn)行分配擔(dān)保, 讓Survivor無法容納的對(duì)象直接進(jìn)入老年代, 但前提是老年代需要有足夠的空間容納這些存活對(duì)象. 但存活對(duì)象的大小在實(shí)際完成GC前是無法明確知道的, 因此Minor GC前, VM會(huì)先首先檢查老年代連續(xù)空間是否大于新生代對(duì)象總大小或歷次晉升的平均大小, 如果條件成立, 則進(jìn)行Minor GC, 否則進(jìn)行Full GC(讓老年代騰出更多空間). 然而取歷次晉升的對(duì)象的平均大小也是有一定風(fēng)險(xiǎn)的, 如果某次Minor GC存活后的對(duì)象突增,遠(yuǎn)遠(yuǎn)高于平均值的話,依然可能導(dǎo)致?lián)J?Handle Promotion Failure, 老年代也無法存放這些對(duì)象了), 此時(shí)就只好在失敗后重新發(fā)起一次Full GC(讓老年代騰出更多空間).
標(biāo)記-整理算法(老年代)
標(biāo)記清除算法會(huì)產(chǎn)生內(nèi)存碎片問題, 而復(fù)制算法需要有額外的內(nèi)存擔(dān)保空間, 于是針對(duì)老年代的特點(diǎn), 又有了標(biāo)記整理算法. 標(biāo)記整理算法的標(biāo)記過程與標(biāo)記清除算法相同, 但后續(xù)步驟不再對(duì)可回收對(duì)象直接清理, 而是讓所有存活的對(duì)象都向一端移動(dòng),然后清理掉端邊界以外的內(nèi)存.
二十五、新生代、老年代、持久代都存儲(chǔ)哪些東西
新生代:
- 方法中new一個(gè)對(duì)象,就會(huì)先進(jìn)入新生代。
老年代:
- 新生代中經(jīng)歷了N次垃圾回收仍然存活的對(duì)象就會(huì)被放到老年代中。
- 大對(duì)象一般直接放入老年代。
- 當(dāng)Survivor空間不足。需要老年代擔(dān)保一些空間,也會(huì)將對(duì)象放入老年代。
永久代:
- 指的就是方法區(qū)。
二十六、可達(dá)性算法中,哪些對(duì)象可作為GC Roots對(duì)象。
- 虛擬機(jī)棧中引用的對(duì)象
- 方法區(qū)靜態(tài)成員引用的對(duì)象
- 方法區(qū)常量引用對(duì)象
- 本地方法棧JNI引用的對(duì)象
二十七、什么時(shí)候進(jìn)行MinGC和FullGC
MinGC:
- 當(dāng)Eden區(qū)滿時(shí),觸發(fā)Minor GC.
FullGC:
- 調(diào)用System.gc時(shí),系統(tǒng)建議執(zhí)行Full GC,但是不必然執(zhí)行
- 老年代空間不足
- 方法區(qū)空間不足
- 通過Minor GC后進(jìn)入老年代的平均大小大于老年代的剩余空間
- 堆中分配很大的對(duì)象,而老年代沒有足夠的空間
二十八、如何判定對(duì)象為垃圾對(duì)象
在堆里面存放著Java世界中幾乎所有的對(duì)象實(shí)例, 垃圾收集器在對(duì)堆進(jìn)行回收前, 第一件事就是判斷哪些對(duì)象已死(可回收).
引用計(jì)數(shù)法
在JDK1.2之前,使用的是引用計(jì)數(shù)器算法。 在對(duì)象中添加一個(gè)引用計(jì)數(shù)器,當(dāng)有地方引用這個(gè)對(duì)象的時(shí)候,引用計(jì)數(shù)器的值就+1,當(dāng)引用失效的時(shí)候,計(jì)數(shù)器的值就-1,當(dāng)引用計(jì)數(shù)器被減為零的時(shí)候,標(biāo)志著這個(gè)對(duì)象已經(jīng)沒有引用了,可以回收了!
問題:如果在A類中調(diào)用B類的方法,B類中調(diào)用A類的方法,這樣當(dāng)其他所有的引用都消失了之后,A和B還有一個(gè)相互的引用,也就是說兩個(gè)對(duì)象的引用計(jì)數(shù)器各為1,而實(shí)際上這兩個(gè)對(duì)象都已經(jīng)沒有額外的引用,已經(jīng)是垃圾了。但是該算法并不會(huì)計(jì)算出該類型的垃圾。
可達(dá)性分析法
在主流商用語言(如Java、C#)的主流實(shí)現(xiàn)中, 都是通過可達(dá)性分析算法來判定對(duì)象是否存活的: 通過一系列的稱為 GC Roots 的對(duì)象作為起點(diǎn), 然后向下搜索; 搜索所走過的路徑稱為引用鏈/Reference Chain, 當(dāng)一個(gè)對(duì)象到 GC Roots 沒有任何引用鏈相連時(shí), 即該對(duì)象不可達(dá), 也就說明此對(duì)象是不可用的, 如下圖:雖然E和F相互關(guān)聯(lián), 但它們到GC Roots是不可達(dá)的, 因此也會(huì)被判定為可回收的對(duì)象。
注: 即使在可達(dá)性分析算法中不可達(dá)的對(duì)象, VM也并不是馬上對(duì)其回收, 因?yàn)橐嬲嬉粋€(gè)對(duì)象死亡, 至少要經(jīng)歷兩次標(biāo)記過程: 第一次是在可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈, 第二次是GC對(duì)在F-Queue執(zhí)行隊(duì)列中的對(duì)象進(jìn)行的小規(guī)模標(biāo)記(對(duì)象需要覆蓋finalize()方法且沒被調(diào)用過).
二十九、你能說出來幾個(gè)垃圾收集器
1. Serial
Serial收集器是Hotspot運(yùn)行在Client模式下的默認(rèn)新生代收集器, 它在進(jìn)行垃圾收集時(shí),會(huì)暫停所有的工作進(jìn)程,用一個(gè)線程去完成GC工作
特點(diǎn):簡(jiǎn)單高效,適合jvm管理內(nèi)存不大的情況(十兆到百兆)。
2. Parnew
ParNew收集器其實(shí)是Serial的多線程版本,回收策略完全一樣,但是他們又有著不同。
我們說了Parnew是多線程gc收集,所以它配合多核心的cpu效果更好,如果是一個(gè)cpu,他倆效果就差不多。(可用-XX:ParallelGCThreads參數(shù)控制GC線程數(shù))
3. Cms
CMS(Concurrent Mark Sweep)收集器是一款具有劃時(shí)代意義的收集器, 一款真正意義上的并發(fā)收集器, 雖然現(xiàn)在已經(jīng)有了理論意義上表現(xiàn)更好的G1收集器, 但現(xiàn)在主流互聯(lián)網(wǎng)企業(yè)線上選用的仍是CMS(如Taobao),又稱多并發(fā)低暫停的收集器。
由他的英文組成可以看出,它是基于標(biāo)記-清除算法實(shí)現(xiàn)的。整個(gè)過程分4個(gè)步驟:
- 初始標(biāo)記(CMS initial mark):僅只標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象, 速度很快
- 并發(fā)標(biāo)記(CMS concurrent mark: GC Roots Tracing過程)
- 重新標(biāo)記(CMS remark):修正并發(fā)標(biāo)記期間因用戶程序繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄
- 并發(fā)清除(CMS concurrent sweep: 已死對(duì)象將會(huì)就地釋放)
可以看到,初始標(biāo)記、重新標(biāo)記需要STW(stop the world 即:掛起用戶線程)操作。因?yàn)樽詈臅r(shí)的操作是并發(fā)標(biāo)記和并發(fā)清除。所以總體上我們認(rèn)為CMS的GC與用戶線程是并發(fā)運(yùn)行的。
優(yōu)點(diǎn):并發(fā)收集、低停頓
缺點(diǎn):
- CMS默認(rèn)啟動(dòng)的回收線程數(shù)=(CPU數(shù)目+3)*4 當(dāng)CPU數(shù)>4時(shí), GC線程最多占用不超過25%的CPU資源, 但是當(dāng)CPU數(shù)<=4時(shí), GC線程可能就會(huì)過多的占用用戶CPU資源, 從而導(dǎo)致應(yīng)用程序變慢, 總吞吐量降低.
- 無法清除浮動(dòng)垃圾(GC運(yùn)行到并發(fā)清除階段時(shí)用戶線程產(chǎn)生的垃圾),因?yàn)橛脩艟€程是需要內(nèi)存的,如果浮動(dòng)垃圾施放不及時(shí),很可能就造成內(nèi)存溢出,所以CMS不能像別的垃圾收集器那樣等老年代幾乎滿了才觸發(fā),CMS提供了參數(shù)-XX:CMSInitiatingOccupancyFraction來設(shè)置GC觸發(fā)百分比(1.6后默認(rèn)92%),當(dāng)然我們還得設(shè)置啟用該策略-XX:+UseCMSInitiatingOccupancyOnly
- 因?yàn)镃MS采用標(biāo)記-清除算法,所以可能會(huì)帶來很多的碎片,如果碎片太多沒有清理,jvm會(huì)因?yàn)闊o法分配大對(duì)象內(nèi)存而觸發(fā)GC,因此CMS提供了-XX:+UseCMSCompactAtFullCollection參數(shù),它會(huì)在GC執(zhí)行完后接著進(jìn)行碎片整理,但是又會(huì)有個(gè)問題,碎片整理不能并發(fā),所以必須單線程去處理,所以如果每次GC完都整理用戶線程stop的時(shí)間累積會(huì)很長(zhǎng),所以XX:CMSFullGCsBeforeCompaction參數(shù)設(shè)置隔幾次GC進(jìn)行一次碎片整理(默認(rèn)為0)。
4. G1
同優(yōu)秀的CMS垃圾回收器一樣,G1也是關(guān)注最小時(shí)延的垃圾回收器,也同樣適合大尺寸堆內(nèi)存的垃圾收集,官方也推薦使用G1來代替選擇CMS。G1最大的特點(diǎn)是引入分區(qū)的思路,弱化分代的概念,合理利用垃圾收集各個(gè)周期的資源,解決了其他收集器甚至CMS的眾多缺陷。
因?yàn)槊總€(gè)區(qū)都有E、S、O代,所以在G1中,不需要對(duì)整個(gè)Eden等代進(jìn)行回收,而是尋找可回收對(duì)象比較多的區(qū),然后進(jìn)行回收(雖然也需要STW操作,但是花費(fèi)的時(shí)間是很少的),保證高效率。
新生代收集
G1的新生代收集跟ParNew類似,如果存活時(shí)間超過某個(gè)閾值,就會(huì)被轉(zhuǎn)移到S/O區(qū)。
年輕代內(nèi)存由一組不連續(xù)的heap區(qū)組成, 這種方法使得可以動(dòng)態(tài)調(diào)整各代區(qū)域的大小
老年代收集
分為以下幾個(gè)階段:
- 初始標(biāo)記 (Initial Mark: Stop the World Event) 在G1中, 該操作附著一次年輕代GC, 以標(biāo)記Survivor中有可能引用到老年代對(duì)象的Regions.
- 掃描根區(qū)域 (Root Region Scanning: 與應(yīng)用程序并發(fā)執(zhí)行) 掃描Survivor中能夠引用到老年代的references. 但必須在Minor GC觸發(fā)前執(zhí)行完
- 并發(fā)標(biāo)記 (Concurrent Marking : 與應(yīng)用程序并發(fā)執(zhí)行) 在整個(gè)堆中查找存活對(duì)象, 但該階段可能會(huì)被Minor GC中斷
- 重新標(biāo)記 (Remark : Stop the World Event) 完成堆內(nèi)存中存活對(duì)象的標(biāo)記. 使用snapshot-at-the-beginning(SATB, 起始快照)算法, 比CMS所用算法要快得多(空Region直接被移除并回收, 并計(jì)算所有區(qū)域的活躍度).
- 清理 (Cleanup : Stop the World Event and Concurrent) 在含有存活對(duì)象和完全空閑的區(qū)域上進(jìn)行統(tǒng)計(jì)(STW)、擦除Remembered Sets(使用Remembered Set來避免掃描全堆,每個(gè)區(qū)都有對(duì)應(yīng)一個(gè)Set用來記錄引用信息、讀寫操作記錄)(STW)、重置空regions并將他們返還給空閑列表(free list)(Concurrent)
三十、JVM中對(duì)象的創(chuàng)建過程
1. 拿到內(nèi)存創(chuàng)建指令
當(dāng)虛擬機(jī)遇到內(nèi)存創(chuàng)建的指令的時(shí)候(new 類名),來到了方法區(qū),找 根據(jù)new的參數(shù)在常量池中定位一個(gè)類的符號(hào)引用。
2. 檢查符號(hào)引用
檢查該符號(hào)引用有沒有被加載、解析和初始化過,如果沒有則執(zhí)行類加載過程,否則直接準(zhǔn)備為新的對(duì)象分配內(nèi)存
3. 分配內(nèi)存
虛擬機(jī)為對(duì)象分配內(nèi)存(堆)分配內(nèi)存分為指針碰撞和空閑列表兩種方式;分配內(nèi)存還要要保證并發(fā)安全,有兩種方式。
3.1. 指針碰撞
所有的存儲(chǔ)空間分為兩部分,一部分是空閑,一部分是占用,需要分配空間的時(shí)候,只需要計(jì)算指針移動(dòng)的長(zhǎng)度即可。
3.2. 空閑列表
虛擬機(jī)維護(hù)了一個(gè)空閑列表,需要分配空間的時(shí)候去查該空閑列表進(jìn)行分配并對(duì)空閑列表做更新。
可以看出,內(nèi)存分配方式是由java堆是否規(guī)整決定的,java堆的規(guī)整是由垃圾回收機(jī)制來決定的
3.2.1 安全性問題的思考
假如分配內(nèi)存策略是指針碰撞,如果在高并發(fā)情況下,多個(gè)對(duì)象需要分配內(nèi)存,如果不做處理,肯定會(huì)出現(xiàn)線程安全問題,導(dǎo)致一些對(duì)象分配不到空間等。
3.3 線程同步策略
也就是每個(gè)線程都進(jìn)行同步,防止出現(xiàn)線程安全。
3.4. 本地線程分配緩沖
也稱TLAB(Thread Local Allocation Buffer),在堆中為每一個(gè)線程分配一小塊獨(dú)立的內(nèi)存,這樣以來就不存并發(fā)問題了,Java 層面與之對(duì)應(yīng)的是 ThreadLocal 類的實(shí)現(xiàn)
4. 初始化
分配完內(nèi)存后要對(duì)對(duì)象的頭(Object Header)進(jìn)行初始化,這新信息包括:該對(duì)象對(duì)應(yīng)類的元數(shù)據(jù)、該對(duì)象的GC代、對(duì)象的哈希碼。
抽象數(shù)據(jù)類型默認(rèn)初始化為null,基本數(shù)據(jù)類型為0,布爾為false。。。
5. 調(diào)用對(duì)象的初始化方法
也就是執(zhí)行構(gòu)造方法。
三十一、談?wù)剬?duì)象的訪問定位
對(duì)象創(chuàng)建起來之后,就會(huì)在虛擬機(jī)棧中維護(hù)一個(gè)本地變量表,用于存儲(chǔ)基礎(chǔ)類型和基礎(chǔ)類型的值,引用類型與引用類型的值。 其中引用類型的值就是堆中對(duì)象地址。如何引用堆中地址有兩種方式:
- 句柄:在堆中維護(hù)一個(gè)句柄池,句柄中包含了對(duì)象地址,當(dāng)對(duì)象改變的時(shí)候,只需改變句柄,不需要改變棧中本地變量表的引用
- 直接指針:對(duì)象的地址直接存儲(chǔ)在棧中,這樣做的好處就是訪問速度變快(Hotspot采用該方式)
三十二、JVM將內(nèi)存主要?jiǎng)澐譃槟奈宀糠?/h1>
方法區(qū)、虛擬機(jī)棧、本地方法棧、堆、程序計(jì)數(shù)器。
三十三、String的intern()函數(shù)作用
這個(gè)要分版本來回答:
- 如果是JDK6,如果字符串產(chǎn)量池先前已經(jīng)創(chuàng)建該對(duì)象,則返回引用;否則將其添加到字符串常量池并返回引用。
- 如果是JDK6+,若字符串常量池有則返回引用,如果池中沒有堆中有,則將堆中的引用添加到池中(注意是引用),然后返回引用;若池中也沒有,則在池中創(chuàng)建并返回引用。
三十四、本地方法棧和虛擬機(jī)棧區(qū)別
本地方法棧與虛擬機(jī)棧所發(fā)揮的作用很相似,他們的區(qū)別在于虛擬機(jī)棧為執(zhí)行Java代碼方法服務(wù),而本地方法棧是為Native方法服務(wù)。
三十五、分配堆內(nèi)存指令
-Xms -Xmx
前者是堆的初始值,后者是堆能達(dá)到的最大值。
三十六、程序計(jì)數(shù)器作用
記錄當(dāng)前線程鎖執(zhí)行的字節(jié)碼的行號(hào)。
- 程序計(jì)數(shù)器是一塊較小的內(nèi)存空間。
- 處于線程獨(dú)占區(qū)。
- 執(zhí)行java方法時(shí),它記錄正在執(zhí)行的虛擬機(jī)字節(jié)碼指令地址。執(zhí)行native方法,它的值為undefined
- 該區(qū)域是唯一一個(gè)沒有規(guī)定任何OutOfMemoryError的區(qū)域
三十七、如何將字符串反轉(zhuǎn)?
- 通過 charAt(int index)返回char值進(jìn)行字符串拼接
- 調(diào)用StringBuffer中的reverse方法
三十八、Collection 和 Collections 有什么區(qū)別?
Collection 是一個(gè)集合接口。它提供了對(duì)集合對(duì)象進(jìn)行基本操作的通用接口方法。Collection接口在Java 類庫中有很多具體的實(shí)現(xiàn)。Collection接口的意義是為各種具體的集合提供了最大化的統(tǒng)一操作方式。
Collections 是一個(gè)包裝類。它包含有各種有關(guān)集合操作的靜態(tài)多態(tài)方法。此類不能實(shí)例化,就像一個(gè)工具類,服務(wù)于Java的Collection框架。
三十九、在 Queue 中 poll()和 remove()有什么區(qū)別?
- queue的增加元素方法add和offer的區(qū)別在于,add方法在隊(duì)列滿的情況下將選擇拋異常的方法來表示隊(duì)列已經(jīng)滿了,而offer方法通過返回false表示隊(duì)列已經(jīng)滿了;在有限隊(duì)列的情況,使用offer方法優(yōu)于add方法;
- remove方法和poll方法都是刪除隊(duì)列的頭元素,remove方法在隊(duì)列為空的情況下將拋異常,而poll方法將返回null;
- element和peek方法都是返回隊(duì)列的頭元素,但是不刪除頭元素,區(qū)別在與element方法在隊(duì)列為空的情況下,將拋異常,而peek方法將返回null.
四十、什么是迭代器
Iterator接口提供了很多對(duì)集合元素進(jìn)行迭代的方法。每一個(gè)集合類都包括了可以返回迭代器實(shí)例的迭代方法。迭代器可以在迭代過程中刪除底層集合的元素,但是不可以直接調(diào)用集合的remove(Object obj)刪除,可以通過迭代器的remove()方法刪除
四十一、迭代器的優(yōu)點(diǎn)
如果用的是for循環(huán),就用集合自帶的remove(),而這樣就改變了集合的Size()循環(huán)的時(shí)候會(huì)出錯(cuò)。但如果把集合放入迭代器,既iterator迭代可以遍歷并選擇集合中的每個(gè)對(duì)象而不改變集合的結(jié)構(gòu),而把集合放入迭代器,用迭代器的remove()就不會(huì)出現(xiàn)問題
四十二、Java集合類中的Iterator和ListIterator的區(qū)別
對(duì)List來說,你也可以通過listIterator()取得其迭代器,兩種迭代器在有些時(shí)候是不能通用的,Iterator和ListIterator主要區(qū)別在以下方面:
- iterator()方法在set和list接口中都有定義,但是ListIterator()僅存在于list接口中(或?qū)崿F(xiàn)類中);
- ListIterator有add()方法,可以向List中添加對(duì)象,而Iterator不能
- ListIterator和Iterator都有hasNext()和next()方法,可以實(shí)現(xiàn)順序向后遍歷,但是ListIterator有hasPrevious()和previous()方法,可以實(shí)現(xiàn)逆向(順序向前)遍歷。Iterator就不可以。
- ListIterator可以定位當(dāng)前的索引位置,nextIndex()和previousIndex()可以實(shí)現(xiàn)。Iterator沒有此功能。
- 都可實(shí)現(xiàn)刪除對(duì)象,但是ListIterator可以實(shí)現(xiàn)對(duì)象的修改,set()方法可以實(shí)現(xiàn)。Iierator僅能遍歷,不能修改。
四十三、怎么確保一個(gè)集合不能被修改?
- Java中提供final關(guān)鍵字,對(duì)基本類型進(jìn)行修飾,當(dāng)?shù)谝淮纬跏蓟?,該變量就不可被修?/li>
- Collections工具類中的UnmodifiableList(不可修改的List、Map、Set等)
四十四、并行和并發(fā)區(qū)別
并發(fā)的關(guān)鍵是你有處理多個(gè)任務(wù)的能力,不一定要同時(shí)。
四十五、說一下你對(duì)Daemon線程(守護(hù)線程)的理解?它有什么意義?一般應(yīng)用于什么樣的場(chǎng)景?
所謂守護(hù)線程是指在程序運(yùn)行的時(shí)候在后臺(tái)提供一種通用服務(wù)的線程,比如垃圾回收線程就是一個(gè)很稱職的守護(hù)者,并且這種線程并不屬于程序中不可或缺的部分。因 此,當(dāng)所有的非守護(hù)線程結(jié)束時(shí),程序也就終止了,同時(shí)會(huì)殺死進(jìn)程中的所有守護(hù)線程。反過來說,只要任何非守護(hù)線程還在運(yùn)行,程序就不會(huì)終止。
守護(hù)線程和用戶線程的沒啥本質(zhì)的區(qū)別:唯一的不同之處就在于虛擬機(jī)的離開:如果用戶線程已經(jīng)全部退出運(yùn)行了,只剩下守護(hù)線程存在了,虛擬機(jī)也就退出了。 因?yàn)闆]有了被守護(hù)者,守護(hù)線程也就沒有工作可做了,也就沒有繼續(xù)運(yùn)行程序的必要了。
四十六、sleep() 和 wait() 有什么區(qū)別?
對(duì)于sleep()方法,我們首先要知道該方法是屬于Thread類中的。而wait()方法,則是屬于Object類中的。sleep()方法導(dǎo)致了程序暫停執(zhí)行指定的時(shí)間,讓出cpu該其他線程,但是他的監(jiān)控狀態(tài)依然保持者,當(dāng)指定的時(shí)間到了又會(huì)自動(dòng)恢復(fù)運(yùn)行狀態(tài)。在調(diào)用sleep()方法的過程中,線程不會(huì)釋放對(duì)象鎖。
而當(dāng)調(diào)用wait()方法的時(shí)候,線程會(huì)放棄對(duì)象鎖,進(jìn)入等待此對(duì)象的等待鎖定池,只有針對(duì)此對(duì)象調(diào)用notify()方法后本線程才進(jìn)入對(duì)象鎖定池準(zhǔn)備獲取對(duì)象鎖進(jìn)入運(yùn)行狀態(tài)。
四十七、notify 和 notifyAll 區(qū)別
notify 僅僅通知一個(gè)線程,并且我們不知道哪個(gè)線程會(huì)收到通知,然而 notifyAll 會(huì)通知所有等待中的線程。換言之,如果只有一個(gè)線程在等待一個(gè)信號(hào)燈,notify和notifyAll都會(huì)通知到這個(gè)線程。但如果多個(gè)線程在等待這個(gè)信號(hào)燈,那么notify只會(huì)通知到其中一個(gè),而其它線程并不會(huì)收到任何通知,而notifyAll會(huì)喚醒所有等待中的線程。
四十八、線程中start()和run()的區(qū)別
- 每個(gè)線程都有要執(zhí)行的任務(wù)。線程的任務(wù)處理邏輯可以在Tread類的run實(shí)例方法中直接實(shí)現(xiàn)或通過該方法進(jìn)行調(diào)用,因此run()相當(dāng)于線程的任務(wù)處理邏輯的入口方法,它由Java虛擬機(jī)在運(yùn)行相應(yīng)線程時(shí)直接調(diào)用,而不是由應(yīng)用代碼進(jìn)行調(diào)用。
- 而start()的作用是啟動(dòng)相應(yīng)的線程。啟動(dòng)一個(gè)線程實(shí)際是請(qǐng)求Java虛擬機(jī)運(yùn)行相應(yīng)的線程,而這個(gè)線程何時(shí)能夠運(yùn)行是由線程調(diào)度器決定的。start()調(diào)用結(jié)束并不表示相應(yīng)線程已經(jīng)開始運(yùn)行,這個(gè)線程可能稍后運(yùn)行,也可能永遠(yuǎn)也不會(huì)運(yùn)行。
四十九、線程池的五種狀態(tài)
線程池的5種狀態(tài):Running、ShutDown、Stop、Tidying、Terminated。
五十、線程池中 submit()和 execute()方法有什么區(qū)別?
- execute(Runnable x) 沒有返回值??梢詧?zhí)行任務(wù),但無法判斷任務(wù)是否成功完成。——實(shí)現(xiàn)Runnable接口
- submit(Runnable x) 返回一個(gè)future??梢杂眠@個(gè)future來判斷任務(wù)是否成功完成?!獙?shí)現(xiàn)Callable接口
# 鏈接 Java程序員福利"常用資料分享"
