本人經(jīng)歷了幾家大公司的Java研發(fā)的面試,現(xiàn)就面試中货矮,經(jīng)常遇到的問題整理如下:(java研發(fā)工程師)
不吹不捧羊精,本人靠著這一系列題目拿到了校招百度、京東囚玫、螞蟻金服的offer??喧锦。
Java基礎(chǔ)部分
【重點】:線程池
https://www.cnblogs.com/exe19/p/5359885.html
【1】java中對集合的理解?(小米抓督、京東)
答:下圖畫的不是很準(zhǔn)確燃少,具體的類的關(guān)系圖可參考:java集合
分析arrayList與LinkedList的區(qū)別:
1. ArrayList(默認(rèn)長度是10個):數(shù)組列表,可以動態(tài)的擴展空間铃在,隨機查詢一個元素速度比較快阵具,刪除和插入一個元素速度較慢,因為需要移動元素定铜;
2. LinkedList:鏈?zhǔn)酱鎯涌谘粢海蚣喜迦朐貢r效率較高,但是查詢元素效率較低揣炕,因為不支持隨機訪問
附加:Java中隊列有哪些帘皿?
普通隊列
LinkedList實現(xiàn)了queue接口,可以作為隊列使用畸陡。
ArrayDeque:以循環(huán)數(shù)組實現(xiàn)的雙向Queue鹰溜。 普通數(shù)組只能快速在末尾添加元素,為了支持FIFO丁恭,從數(shù)組頭快速取出元素曹动,就需要使用循環(huán)數(shù)組:有隊頭隊尾兩個下標(biāo):彈出元素時,隊頭下標(biāo)遞增涩惑;加入元素時仁期,如果已到數(shù)組空間的末尾,則將元素循環(huán)賦值到數(shù)組[0](如果此時隊頭下標(biāo)大于0,說明隊頭彈出過元素跛蛋,有空位)熬的,同時隊尾下標(biāo)指向0,再插入下一個元素則賦值到數(shù)組[1]赊级,隊尾下標(biāo)指向1押框。如果隊尾的下標(biāo)追上隊頭,說明數(shù)組所有空間已用完理逊,進行雙倍的數(shù)組擴容橡伞。?
PriorityQueue :用二叉堆實現(xiàn)的優(yōu)先級隊列,詳見入門教程晋被,不再是FIFO而是按元素實現(xiàn)的Comparable接口或傳入Comparator的比較結(jié)果來出隊兑徘,數(shù)值越小,優(yōu)先級越高羡洛,越先出隊挂脑。但是注意其iterator()的返回不會排序。
–線程安全的隊列–
?ConcurrentLinkedQueue/ConcurrentLinkedDeque:
?無界的并發(fā)優(yōu)化的Queue欲侮,基于鏈表崭闲,實現(xiàn)了依賴于CAS的無鎖算法。?ConcurrentLinkedQueue的結(jié)構(gòu)是單向鏈表和head/tail兩個指針威蕉,因為入隊時需要修改隊尾元素的next指針刁俭,以及修改tail指向新入隊的元素兩個CAS動作無法原子,所以需要的特殊的算法韧涨,篇幅所限見入門教程牍戚。?
PriorityBlockingQueue :無界的并發(fā)優(yōu)化的PriorityQueue,也是基于二叉堆虑粥。使用一把公共的讀寫鎖翘魄。雖然實現(xiàn)了BlockingQueue接口,其實沒有任何阻塞隊列的特征舀奶,空間不夠時會自動擴容。?
DelayQueue: 內(nèi)部包含一個PriorityQueue斋射,同樣是無界的育勺。元素需實現(xiàn)Delayed接口,每次調(diào)用時需返回當(dāng)前離觸發(fā)時間還有多久罗岖,小于0表示該觸發(fā)了涧至。?pull()時會用peek()查看隊頭的元素,檢查是否到達觸發(fā)時間桑包。ScheduledThreadPoolExecutor用了類似的結(jié)構(gòu)南蓬。
–線程安全的阻塞隊列–
BlockingQueue:的隊列長度受限,用以保證生產(chǎn)者與消費者的速度不會相差太遠,避免內(nèi)存耗盡赘方。隊列長度設(shè)定后不可改變烧颖。
ArrayBlockingQueue :定長的并發(fā)優(yōu)化的BlockingQueue,基于循環(huán)數(shù)組實現(xiàn)窄陡。有一把公共的讀寫鎖與notFull炕淮、notEmpty兩個Condition管理隊列滿或空時的阻塞狀態(tài)。
LinkedBlockingQueue/LinkedBlockingDeque :可選定長的并發(fā)優(yōu)化的BlockingQueue跳夭,基于鏈表實現(xiàn)涂圆,所以可以把長度設(shè)為Integer.MAX_VALUE。利用鏈表的特征币叹,分離了takeLock與putLock兩把鎖润歉,繼續(xù)用notEmpty、notFull管理隊列滿或空時的阻塞狀態(tài)颈抚。
【2】如何理解HashMap和HashTable?(百度)
答:HashMap是非synchronized的踩衩,線程不安全的,,允許null的key和null的value邪意,而HashTable是synchronized九妈,這意味著Hashtable是線程安全的,多個線程可以共享一個Hashtable雾鬼。就單個線程來講萌朱,HashMap速度比HashTable速度要快。
hashmap結(jié)構(gòu)可參考博客:hashmap
*******HashMap是如何擴容的策菜?(必知必會*****)
有哪些map是線程安全的晶疼?
Hashtable、synchronizedMap又憨、ConcurrentHashMap
ConcurrentHashMap為啥是安全的翠霍?
答: 鎖分段技術(shù)HashTable容器在競爭激烈的并發(fā)環(huán)境下表現(xiàn)出效率低下的原因是所有訪問HashTable的線程都必須競爭同一把鎖,那假如容器里有多把 鎖蠢莺,每一把鎖用于鎖容器其中一部分?jǐn)?shù)據(jù)寒匙,那么當(dāng)多線程訪問容器里不同數(shù)據(jù)段的數(shù)據(jù)時,線程間就不會存在鎖競爭躏将,從而可以有效的提高并發(fā)訪問效率锄弱,這就是 ConcurrentHashMap所使用的鎖分段技術(shù),首先將數(shù)據(jù)分成一段一段的存儲祸憋,然后給每一段數(shù)據(jù)配一把鎖会宪,當(dāng)一個線程占用鎖訪問其中一個段數(shù)據(jù) 的時候,其他段的數(shù)據(jù)也能被其他線程訪問蚯窥。
【3】String掸鹅、StringBuffer和StringBuilder三者的區(qū)別塞帐?(京東、小米)
答: 簡言之:String代表字符串常量巍沙,StringBuffer 字符串變量(有syncronized修飾葵姥,線程安全),StringBuilder 字符串變量(非線程安全)赎瞎。
StringBuffer()的初始容量可以容納16個字符牌里,當(dāng)該對象的實體存放的字符的長度大于16時,實體容量就自動增加务甥。經(jīng)常改變內(nèi)容的字符串最好不要用 String牡辽,Java中對String對象進行的操作實際上是一個不斷創(chuàng)建新的對象并且將舊的對象回收的一個過程,所以執(zhí)行速度很慢敞临。
【4】equals和==區(qū)別态辛?(小米、百度)
答:在java中基本數(shù)據(jù)類型(int挺尿、boolean奏黑、char、long编矾、float熟史、double)的比較用==,==比較的是基本數(shù)據(jù)類型的值窄俏。除了基本數(shù)據(jù)類型外蹂匹,java還有復(fù)合的數(shù)據(jù)類型(類),當(dāng)用==比較的時候凹蜈,比較的是他們在內(nèi)存中的存放地址限寞。so,除非是同一個new出來的對象仰坦,比較結(jié)果為true履植,否則,都為false悄晃。equals就不同了玫霎,equals是object類的一個方法,原始的equals()方法是比較內(nèi)存中的地址妈橄,跟==是一樣的鼠渺,但是這個方法在一些類庫當(dāng)中被override了(如String,Integer眷细,Date等),覆寫后的方法比較的不再是內(nèi)存地址而是值鹃祖。
【5】short s1 = 1; s1 = s1 + 1;有錯嗎?(錯)short s1 = 1; s1 += 1;有錯嗎溪椎?(對)
答:
short s1 = 1;
s1 = s1 + 1;
1是int類型,so,s1 + 1的類型也為int類型校读,需要強制類型轉(zhuǎn)換才能賦值給short s1沼侣。而s1+=1相當(dāng)于s1 = (short)(s1 + 1);其中隱藏著強制類型轉(zhuǎn)換。
【6】兩個對象值相同(x.equals(y) == true)歉秫,但卻可有不同的hash code蛾洛,這句話對不對?(不對)(京東)
答:如果對象x和y相同那么他們的hashcode一定相同雁芙,Java對于eqauls方法和hashCode方法是這樣規(guī)定的:
(1)如果兩個對象相同(equals方法返回true)轧膘,那么它們的hashCode值一定要相同;
(2)如果兩個對象的hashCode相同兔甘,它們并不一定相同谎碍。
【7】重載(Overload)和重寫(Override)的區(qū)別。
答:重載和重寫都是實現(xiàn)多態(tài)的方式洞焙,只不過一個是在前期(編譯期實現(xiàn))蟆淀,一個是在后期(運行時實現(xiàn))。overload實現(xiàn)的是編譯時的多態(tài)性澡匪,override實現(xiàn)的是運行時的多態(tài)性熔任。
overload發(fā)生在類中,函數(shù)名相同唁情,但有不同的入?yún)?shù)類型疑苔、個數(shù),不考慮函數(shù)返回類型荠瘪;
override一般是子類繼承了父類夯巷,重寫父類的函數(shù),返回值類型和父類保持一致哀墓。
【8】抽象類(abstract class)和接口(interface)的區(qū)別趁餐。
答:抽象類,顧名思義篮绰,是類的抽象后雷,抽象類不能實例化,抽象類可以有構(gòu)造器吠各,誰繼承了抽象類臀突,就要對抽象類中的所有抽象方法進行實現(xiàn)。接口interface是一種特殊的抽象類贾漏,但是接口中不能有構(gòu)造函數(shù)候学,接口中的所有方法都不能有實現(xiàn),接口可以多繼承纵散。抽象類可以實現(xiàn)(implement)接口梳码,抽象類可以繼承(extends)具體類
【9】String s = new String(“xyz”);創(chuàng)建了幾個字符串對象隐圾?(2)
答:首先在string池內(nèi)找xyz,找到掰茶?不創(chuàng)建string對象暇藏,否則創(chuàng)建, 這樣就一個string對象濒蒋,遇到new運算符號了盐碱,在內(nèi)存上創(chuàng)建string對象,并將其返回給s沪伙,又一個對象瓮顽。所以總共是2個對象。
補充:
詳細內(nèi)容可參考:深入理解string
【10】描述一下Java加載class文件的原理機制焰坪?
答:【詳細參考】
【11】Arrays類的copyOf()函數(shù)趣倾,是淺拷貝。
最后返回都是30某饰。Arrays.copyOf功能是實現(xiàn)數(shù)組的復(fù)制儒恋,返回復(fù)制后的數(shù)組。參數(shù)是被復(fù)制的數(shù)組和復(fù)制的長度黔漂。
【12】你對消息中間件熟悉嗎诫尽?應(yīng)用場景?(小米)
答:消息中間件是分布式系統(tǒng)中的重要組件炬守,主要解決應(yīng)用解耦牧嫉、異步處理、流量削峰和消息通訊的問題(在電商减途、日志處理上應(yīng)用廣泛)酣藻。目前使用較多的消息隊列有:ActiveMQ,Kafka鳍置,RabbitMQ辽剧,ZeroMQ,MetaMQ税产,RocketMQ等怕轿。[詳細參考]
【13】下面程序的運行結(jié)果是(false)
String str1 = "hello";
String str2 = "he" + new String("llo");
System.err.println(str1 == str2);
答:看懂了前面equals和==的問題,以及jvm的相關(guān)知識辟拷,這個就很簡單了撞羽。
【14】GC線程是否為守護線程?()
答案:是衫冻。線程分為守護線程和非守護線程(即用戶線程)诀紊。
只要當(dāng)前JVM實例中尚存在任何一個非守護線程沒有結(jié)束,守護線程就全部工作隅俘;只有當(dāng)最后一個非守護線程結(jié)束時邻奠,守護線程隨著JVM一同結(jié)束工作到推。守護線程最典型的應(yīng)用就是 GC (垃圾回收器)
【15】如何理解volatile和synchronized?(京東)
????????在多線程并發(fā)的問題中,線程對共享變量的操作都是在自己的工作內(nèi)存中進行的惕澎,線程之間無法達到共享,線程之間的變量值的傳遞需要經(jīng)過主內(nèi)存作為橋梁颜骤。
共享變量可見性的原理如下:
(1)工作線程1中更新了的共享變量如果想被工作線程2看到(可見)唧喉,需要先將工作內(nèi)存刷新到主內(nèi)存;
(2)主內(nèi)存中共享變量發(fā)生變化忍抽,更新到工作線程2的工作內(nèi)存中八孝。
想要實現(xiàn)可見性,需要保證兩點:
1.工作線程中的工作內(nèi)存的變量值鸠项,一旦發(fā)生改變要及時地刷新到主內(nèi)存中干跛;
2.主內(nèi)存中的變量一旦發(fā)生變化,工作線程能及時地從主內(nèi)存刷新到自己的工作內(nèi)存祟绊。
接下來楼入,切入我們的正題:
可見性的實現(xiàn)方式:synchronized和volatile
synchronized實現(xiàn)過程如下:
1.獲得互斥鎖;
2.清空工作內(nèi)存
3.從主內(nèi)存拷貝變量的工作副本到自己的工作內(nèi)存
4.執(zhí)行代碼
5.將更改后的共享變量的值刷新到主內(nèi)存中
6.釋放互斥鎖
????????那么牧抽,導(dǎo)致共享變量在線程中不可見的原因是什么嘉熊?主要有這么幾點:多線程交叉執(zhí)行;重排序結(jié)合線程交叉執(zhí)行扬舒;共享變量的值在發(fā)生改變后未能在主內(nèi)存和工作內(nèi)存中及時更新阐肤。synchronized解決的問題是,線程交叉執(zhí)行讲坎,synchronized保證原子性孕惜,重排序集合線程交叉,synchronized保證原子性晨炕,最后一個衫画,synchronized保證可見性。簡化成圖片就是如下:
????????then府瞄,對于volatile碧磅,它又是如何解決可見性問題的呢?volatile是通過加入內(nèi)存屏障和禁止重排序優(yōu)化實現(xiàn)的遵馆。(volatile在執(zhí)行讀操作時候鲸郊,會在讀操作前面加一條load的屏障指令,同理货邓,在執(zhí)行寫操作時候會在寫操作后加一條store的屏障指令)
????????volatile是如何實現(xiàn)的秆撮?通俗的將,變量每次被線程訪問時换况,都會被迫從主內(nèi)存中重讀變量的值职辨,當(dāng)變量的值發(fā)生變化時盗蟆,由會強迫線程將變量的值刷新到主內(nèi)存中,這樣舒裤,任何時刻喳资,不同的線程之間均能看到該變量的最新值。
synchronized和volatile的比較:
【16】ArrayList list = new ArrayList(20);中的list擴充幾次(0)(京東)
答:不擴容腾供。默認(rèn)ArrayList的長度是10個仆邓,如果往list里添加20個元素肯定要擴充一次(擴充為原來的1.5倍),但是這里顯示指明了需要多少空間伴鳖,所以就一次性分配這么多空間节值,也就是不需要擴充了。
【17】下列程序輸出什么榜聂?
輸出順序如下:
static A
static B
I'm A class
HelloA
I'm B class
HelloB
答:對象的初始化順序:(1)類加載之后搞疗,按從上到下(從父類到子類)執(zhí)行被static修飾的語句;(2)當(dāng)static語句執(zhí)行完之后,再執(zhí)行main方法须肆;(3)如果有語句new了自身的對象匿乃,將從上到下執(zhí)行構(gòu)造代碼塊、構(gòu)造器(兩者可以說綁定在一起)
稍微修改下如下:
答案:
static A
static B
-------main start-------
I'm A class
HelloA
I'm B class
HelloB
I'm A class
HelloA
I'm B class
HelloB
-------main end-------
【18】Thread類中run()和start()方法的區(qū)別休吠。
答:run()是執(zhí)行體扳埂,start()是開啟一個多線程,已知線程的狀態(tài)分為:創(chuàng)建瘤礁、就緒阳懂、運行、阻塞柜思、死亡五個岩调。線程狀態(tài)轉(zhuǎn)換圖如下所示。
1.創(chuàng)建狀態(tài):此時還沒調(diào)用start()方法
2.就緒狀態(tài):調(diào)用了start()方法之后赡盘,線程處于就緒狀態(tài)号枕,線程調(diào)度程序還沒把該線程設(shè)置成當(dāng)前線程。當(dāng)然陨享,程序運行后葱淳,等待或者睡眠之后也會處于就緒狀態(tài)。
3.運行狀態(tài):程序調(diào)度器將就緒線程設(shè)置成當(dāng)前線程抛姑,此時就會執(zhí)行程序執(zhí)行體run()函數(shù)赞厕。
4.阻塞狀態(tài):正在運行的程序被阻塞,一般是sleep()定硝、suspend()皿桑、wait()等方法導(dǎo)致。
5.死亡狀態(tài):run()執(zhí)行結(jié)束或者調(diào)用了stop()方法,線程會處于死亡狀態(tài)诲侮。死亡的線程不能再通過start()方法使其處于就緒狀態(tài)镀虐。
多線程原理:
????????相當(dāng)于多個人玩玩具,只有一個玩具(cpu)沟绪,好多人都想玩刮便,怎么辦?排隊U来取I悼А顾孽!start()就起到了排隊的作用脖含。等cpu輪到你蝴猪,你就run()漓摩,當(dāng)cpu的運行時間結(jié)束裙士,就繼續(xù)排隊,等待下一次的run()管毙。
????????調(diào)用start()后腿椎,線程會被放到等待隊列,等待CPU調(diào)度夭咬,并不一定要馬上開始執(zhí)行啃炸,只是將這個線程置于可運行狀態(tài)。然后通過JVM卓舵,線程Thread會調(diào)用run()方法南用,執(zhí)行本線程的線程體。先調(diào)用start后調(diào)用run掏湾,這么麻煩裹虫,為了不直接調(diào)用run?就是為了實現(xiàn)多線程的優(yōu)點融击,沒這個start不行筑公。
所以,start()方法用來啟動線程尊浪,此時線程并沒有處于運行狀態(tài)匣屡,只是就緒狀態(tài)。start()方法執(zhí)行后可不用等待run()方法里面的程序執(zhí)行完拇涤,可以繼續(xù)執(zhí)行下面的程序捣作。
如果run()方法被當(dāng)做普通方法來執(zhí)行,不能實現(xiàn)多線程并發(fā)的效果工育。下面的程序需要等待run()里面的代碼執(zhí)行結(jié)束才能執(zhí)行虾宇,程序中只有主線程這一個線程。
【19】sleep()和wait()的區(qū)別如绸?(百度嘱朽、新浪)
答:wait()和sleep()的關(guān)鍵的區(qū)別在于旭贬,wait()是用于線程間通信的,而sleep()是用于短時間暫停當(dāng)前線程搪泳。其中稀轨,sleep()是Thread類中的靜態(tài)方法,wait()是是定義在Object類中的岸军。
另外奋刽,sleep()是使當(dāng)前的程序處于阻塞狀態(tài),在其睡眠時間段內(nèi)艰赞,該線程不會獲得執(zhí)行的機會佣谐。而wait(),線程會釋放掉它所占有的“鎖標(biāo)志”方妖,從而使別的線程有機會搶占該鎖狭魂,wait()和notify()必須在synchronized函數(shù)或synchronized block中進行調(diào)用。
【20】下面哪個流類屬于面向 字符 的輸入流(?D )
A. BufferedWriter?????????? B. FileInputStream? ? ??
C. ObjectInputStream? ? D. InputStreamReader
答:看圖一目了然党觅。
對于Io,分為字符流和字節(jié)流雌澄。
字節(jié)流:reader writer,
字符流:InputStream(FileInpuStream)杯瞻、OutputStream(FileOutputStream->BufferedOutputStream)具體如下表所示:
1.什么時候使用字節(jié)流镐牺?什么時候使用字符流?
答:
所有的輸入都是轉(zhuǎn)化成字節(jié)流后魁莉,在內(nèi)存中轉(zhuǎn)化成字符流睬涧。因此一般建議使用字符流,當(dāng)遇到中文出現(xiàn)亂碼時旗唁,使用字節(jié)流宙地。(所有硬盤上保存的文件或者傳輸文件時,都是通過字節(jié)流的形式進行的逆皮,包括圖片也是字節(jié)流宅粥,在內(nèi)存中才轉(zhuǎn)化成字符流,使用字節(jié)的操作是最多的)
2.遞歸讀取文件夾的文件电谣,代碼怎么實現(xiàn)秽梅?
代碼如下:
【詳細參考】
【21】泛型的作用?
答:泛型主要是用在集合中剿牺,如果不指定加入集合的元素的類型企垦,集合就會忘記被“丟進”的元素類型。在程序編譯過程中晒来,如果不使用泛型钞诡,編譯器不會報錯,但是在foreach過程中會出現(xiàn)ClassCastException異常。
【22】如何理解多態(tài)性荧降?(新浪)
答:用專業(yè)一點的話說就是接箫,類中所定義引用變量或者由引用變量所調(diào)用的方法在程序編譯期間并不能夠知曉,只有在程序運行期間才能知道朵诫,調(diào)用了哪個類的方法辛友。、
????????如果講給大街上的老頭剪返,桌子上有三杯酒废累,a(茅臺) b(五糧液) c(二鍋頭),都是白酒脱盲,在沒喝(相當(dāng)于程序運行)之前邑滨,我們不知道誰是誰,只有品嘗了一口钱反,才知道驼修,哦,哪個是二鍋頭诈铛,哪個是茅臺(abc都是酒的子類)。
【23】解釋內(nèi)存中的棧(stack)墨礁、堆(heap)和方法區(qū)(method area)的用法幢竹。
答:通常我們定義一個基本數(shù)據(jù)類型的變量,一個對象的引用恩静,還有就是函數(shù)調(diào)用的現(xiàn)場保存都使用JVM中的椈篮粒空間;
????????通過new關(guān)鍵字和構(gòu)造器創(chuàng)建的對象則放在堆空間驶乾,堆是垃圾收集器管理的主要區(qū)域邑飒,由于現(xiàn)在的垃圾收集器都采用分代收集算法,所以堆空間還可以細分為新生代和老生代级乐,再具體一點可以分為Eden疙咸、Survivor(又可分為From Survivor和To Survivor)、Tenured风科;
????????方法區(qū)和堆都是各個線程共享的內(nèi)存區(qū)域撒轮,用于存儲已經(jīng)被JVM加載的類信息、常量贼穆、靜態(tài)變量题山、JIT編譯器編譯后的代碼等數(shù)據(jù);程序中的字面量(literal)如直接書寫100故痊、"hello"和常量都是放在常量池中顶瞳,常量池是方法區(qū)的一部分。棧空間操作起來最快但是棧很小慨菱,通常大量的對象都是放在堆空間焰络,棧和堆的大小都可以通過JVM的啟動參數(shù)來進行調(diào)整,椔帐粒空間用光了會引發(fā)StackOverflowError舔琅,而堆和常量池空間不足則會引發(fā)OutOfMemoryError。
String str = new String("hello");
????上面的語句中變量str放在棧上洲劣,用new創(chuàng)建出來的字符串對象放在堆上备蚓,而"hello"這個字面量是放在方法區(qū)的。
最后囱稽,堆郊尝、棧在數(shù)據(jù)上面的共享問題(棧區(qū),本地方法區(qū)和pc計數(shù)器屬于線程內(nèi)存战惊,每個獨立線程都擁有自己的線程內(nèi)存流昏,這些是不可共享的, 堆區(qū)吞获,方法區(qū)和常量池是可以共享的)
更加詳細的內(nèi)存模型及GC算法內(nèi)容參考【詳細參考】
【24】如何理解線程和進程况凉?線程之間如何通訊的?(面試必問※)
答:在操作系統(tǒng)中各拷,每一個任務(wù)的執(zhí)行都會占用系統(tǒng)一部分資源(或者說刁绒,系統(tǒng)為每一個任務(wù)分配一定的cpu資源),每一個任務(wù)的執(zhí)行稱為一個進程烤黍,進程具有獨立性知市,也就說相互進程之間無法進行直接的交流,進程具有并發(fā)性速蕊,在操作系統(tǒng)中嫂丙,每一時刻僅有一個進程運行,但是多個進程之間是輪換轉(zhuǎn)變的规哲,宏觀上講進程具有并發(fā)性跟啤。
線程是進程的微小單元,一個任務(wù)的執(zhí)行是一個進程唉锌,一個進程可以包含多個線程腥光,這樣的好處就是,線程之間可以共享內(nèi)存糊秆,而進程之間是無法共享的武福。
當(dāng)操作系統(tǒng)創(chuàng)建一個進程時,系統(tǒng)需要為每一個進程分配一定的內(nèi)存空間及相關(guān)的資源痘番,而線程的創(chuàng)建則代價小得多捉片,因此多線程的并發(fā)要比多進程的并發(fā)性能高的多平痰。
線程之間的通訊方式?
我們知道線程之間是可以進行內(nèi)存共享的伍纫,如何進行共享的呢宗雇?
1.傳統(tǒng)的通訊方式是基于同步代碼塊、同步方法(也就是由synchronized修飾的時候)莹规,用到三個方法赔蒲,wait(),notify()及notifyAll()良漱,但這三個方法必須有同步監(jiān)視器對象來調(diào)用舞虱。分兩種情況:
1. synchronized修飾同步代碼塊,同步監(jiān)視器是 synchronized后面括號里面的對象母市,使用該對象調(diào)用三個方法矾兜。
2. synchronized修飾同步方法,因為該類的實例默認(rèn)(this)就是同步監(jiān)視器患久,所以可以直接調(diào)用這三個方法椅寺。
wait()方法是object類方法,是針對線程之間的蒋失,使用wait()方法會使得當(dāng)前線程等待返帕,調(diào)用wait()方法的線程會釋放對該同步監(jiān)視器的鎖。直到其他線程調(diào)用該同步監(jiān)視器的notify()或者notifyAll()方法來喚醒篙挽。
notify()喚醒在此同步監(jiān)視器上等待的單個線程
notifyAll()喚醒在此同步監(jiān)視器上等待的所有線程荆萤。
2.使用Condition控制線程通訊
倘若程序中沒有使用synchronized關(guān)鍵字來修飾,而是使用了同步鎖Lock來保證同步嫉髓,系統(tǒng)中沒有了隱式的同步監(jiān)視器,也就無法使用wait()邑闲,notify()及notifyAll()了算行,此時可使用Condition類。
condition實例被綁定在一個Lock對象上苫耸,要獲得特定的Lock實例的Condition實例州邢,調(diào)用Lock對象的newCondition()方法即可。其提供的三個方法:
1.await()類似于wait()方法褪子,導(dǎo)致線程等待量淌,直到其他線程調(diào)用signal()或signalAll()喚醒
2.signal()喚醒單個線程
3.signalAll()喚醒所有線程。
//顯示定義Lock對象
private final Lock lock = new ReentrantLock();
//獲得指定Lock對象對應(yīng)的Condition
private final Condition cond? = lock.newCondition();
3.使用阻塞隊列(BlockingQueue)控制線程通訊
【25】線程池的使用原理及示例嫌褪?
1.為什么使用線程池呀枢?
合理的使用線程池便可重復(fù)利用已創(chuàng)建的線程,以減少在創(chuàng)建線程和銷毀線程上花費的時間和資源笼痛。
2. 線程池的簡要工作模型
【26】利用多線程循環(huán)輸出AB
答:
【27】final關(guān)鍵字的理解和認(rèn)識裙秋?
答:在使用匿名內(nèi)部類的時候可能會經(jīng)常用到final關(guān)鍵字琅拌。在Java中String類就是一個final類。
final可以修飾類摘刑、方法和變量(成員變量进宝、局部變量)。
1.修飾類
final修飾類時候枷恕,此類不能被繼承党晋。
2.修飾方法
一個類中用final修飾方法,繼承此類的子類無法override父類的方法徐块。
3.修飾變量
當(dāng)final修飾類的成員變量時未玻,定義變量的時候要對變量進行初始化,而且final變量一旦被初始化賦值之后蛹锰,就不能再被賦值了深胳。
final修飾的成員變量和普通變量有啥區(qū)別?(true铜犬、false)
final修飾基本數(shù)據(jù)類型及string類型時舞终,在編譯期間,變量就被認(rèn)為是編譯常量癣猾,會直接加載敛劝,不會等到運行期間再加載,因此final修飾的string b="hello",b在編譯期間就被直接調(diào)用了纷宇。
被final修飾的引用變量指向的對象內(nèi)容可變嗎夸盟?
在上面提到被final修飾的引用變量一旦初始化賦值之后就不能再指向其他的對象,那么該引用變量指向的對象的內(nèi)容可變嗎像捶?看下面這個例子:
這段代碼可以順利編譯通過并且有輸出結(jié)果上陕,輸出結(jié)果為1。這說明引用變量被final修飾之后拓春,雖然不能再指向其他對象释簿,但是它指向的對象的內(nèi)容是可變的。
最后硼莽,final和static有什么區(qū)別庶溶?
static作用于成員變量用來表示只保存一份副本,而final的作用是用來保證變量不可變懂鸵∑荩看下面這個例子:
運行這段代碼就會發(fā)現(xiàn),每次打印的兩個j值都是一樣的匆光,而i的值卻是不同的套像。
補充:為什么string類要final來修飾?【詳細參考】
兩個方面回答:1.安全性终息;2.提高效率
【28】Java中內(nèi)部類有什么作用凉夯?
答:內(nèi)部類分為:1.成員內(nèi)部類货葬;2.局部內(nèi)部類;3.匿名內(nèi)部類劲够;4.靜態(tài)內(nèi)部類
1.成員內(nèi)部類是最普通的內(nèi)部類震桶,它的定義為位于另一個類的內(nèi)部。成員內(nèi)部類可以無條件( 事實上征绎,編譯器在進行編譯的時候蹲姐,會將成員內(nèi)部類單獨編譯成一個字節(jié)碼文件)訪問外部類的所有成員屬性和成員方法(包括private成員和靜態(tài)成員)
2. 局部內(nèi)部類是定義在一個方法或者一個作用域里面的類,它和成員內(nèi)部類的區(qū)別在于局部內(nèi)部類的訪問僅限于方法內(nèi)或者該作用域內(nèi)人柿。
3. 匿名內(nèi)部類應(yīng)該是平時我們編寫代碼時用得最多的柴墩,在編寫事件監(jiān)聽的代碼時使用匿名內(nèi)部類不但方便,而且使代碼更加容易維護凫岖。多用于安卓監(jiān)聽器中江咳。
注意: 局部內(nèi)部類和匿名內(nèi)部類只能訪問局部final變量。
4. 靜態(tài)內(nèi)部類也是定義在另一個類里面的類哥放,只不過在類的前面多了一個關(guān)鍵字static歼指。靜態(tài)內(nèi)部類是不需要依賴于外部類的,這點和類的靜態(tài)成員屬性有點類似甥雕,并且它不能使用外部類的非static成員變量或者方法踩身。
為什么在Java中需要內(nèi)部類?總結(jié)一下主要有以下四點:
1.每個內(nèi)部類都能獨立的繼承一個接口的實現(xiàn)社露,所以無論外部類是否已經(jīng)繼承了某個(接口的)實現(xiàn)挟阻,對于內(nèi)部類都沒有影響。內(nèi)部類使得多繼承的解決方案變得完整峭弟。
2.方便將存在一定邏輯關(guān)系的類組織在一起附鸽,又可以對外界隱藏。
3.方便編寫事件驅(qū)動程序
4.方便編寫線程代碼
【29】Dubbo架構(gòu)原理
答:【詳細參考】