面試題1

1.寫出synchronized的使用方式


synchronized的三種應(yīng)用方式

synchronized關(guān)鍵字最主要有以下3種應(yīng)用方式树瞭,下面分別介紹

修飾實例方法孝偎,作用于當(dāng)前實例加鎖衣盾,進(jìn)入同步代碼前要獲得當(dāng)前實例的鎖

修飾靜態(tài)方法,作用于當(dāng)前類對象加鎖果复,進(jìn)入同步代碼前要獲得當(dāng)前類對象的鎖

修飾代碼塊,指定加鎖對象迈窟,對給定對象加鎖车酣,進(jìn)入同步代碼庫前要獲得給定對象的鎖。

Java 虛擬機中的同步(Synchronization)基于進(jìn)入和退出管程(Monitor)對象實現(xiàn)破衔, 無論是顯式同步(有明確的 monitorenter 和 monitorexit 指令,即同步代碼塊)還是隱式同步都是如此晰筛。在 Java 語言中读第,同步用的最多的地方可能是被 synchronized 修飾的同步方法。

Java虛擬機對synchronized的優(yōu)化

鎖的狀態(tài)總共有四種吴汪,無鎖狀態(tài)漾橙、偏向鎖、輕量級鎖和重量級鎖淘捡。隨著鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖踢京,再升級的重量級鎖瓣距,但是鎖的升級是單向的,也就是說只能從低到高升級逻杖,不會出現(xiàn)鎖的降級荸百,關(guān)于重量級鎖蓝翰,前面我們已詳細(xì)分析過,下面我們將介紹偏向鎖和輕量級鎖以及JVM的其他優(yōu)化手段爆雹,這里并不打算深入到每個鎖的實現(xiàn)和轉(zhuǎn)換過程更多地是闡述Java虛擬機所提供的每個鎖的核心優(yōu)化思想,畢竟涉及到具體過程比較繁瑣拒啰,如需了解詳細(xì)過程可以查閱《深入理解Java虛擬機原理》剩失。

偏向鎖

偏向鎖是Java 6之后加入的新鎖拴孤,它是一種針對加鎖操作的優(yōu)化手段,經(jīng)過研究發(fā)現(xiàn)芒粹,在大多數(shù)情況下,鎖不僅不存在多線程競爭座云,而且總是由同一線程多次獲得朦拖,因此為了減少同一線程獲取鎖(會涉及到一些CAS操作,耗時)的代價而引入偏向鎖捍岳。偏向鎖的核心思想是作喘,如果一個線程獲得了鎖窖贤,那么鎖就進(jìn)入偏向模式,此時Mark Word 的結(jié)構(gòu)也變?yōu)槠蜴i結(jié)構(gòu)授嘀,當(dāng)這個線程再次請求鎖時,無需再做任何同步操作巷折,即獲取鎖的過程锻拘,這樣就省去了大量有關(guān)鎖申請的操作署拟,從而也就提供程序的性能。所以缨恒,對于沒有鎖競爭的場合岭佳,偏向鎖有很好的優(yōu)化效果,畢竟極有可能連續(xù)多次是同一個線程申請相同的鎖。但是對于鎖競爭比較激烈的場合衩辟,偏向鎖就失效了,因為這樣場合極有可能每次申請鎖的線程都是不相同的,因此這種場合下不應(yīng)該使用偏向鎖狈究,否則會得不償失,需要注意的是宁改,偏向鎖失敗后,并不會立即膨脹為重量級鎖谜喊,而是先升級為輕量級鎖鞋邑。下面我們接著了解輕量級鎖逾一。

輕量級鎖

倘若偏向鎖失敗箱玷,虛擬機并不會立即升級為重量級鎖壳坪,它還會嘗試使用一種稱為輕量級鎖的優(yōu)化手段(1.6之后加入的),此時Mark Word 的結(jié)構(gòu)也變?yōu)檩p量級鎖的結(jié)構(gòu)弥虐。輕量級鎖能夠提升程序性能的依據(jù)是“對絕大部分的鎖扩灯,在整個同步周期內(nèi)都不存在競爭”媚赖,注意這是經(jīng)驗數(shù)據(jù)霜瘪。需要了解的是,輕量級鎖所適應(yīng)的場景是線程交替執(zhí)行同步塊的場合惧磺,如果存在同一時間訪問同一鎖的場合,就會導(dǎo)致輕量級鎖膨脹為重量級鎖番捂。

自旋鎖

輕量級鎖失敗后魄梯,虛擬機為了避免線程真實地在操作系統(tǒng)層面掛起肝箱,還會進(jìn)行一項稱為自旋鎖的優(yōu)化手段。這是基于在大多數(shù)情況下粥脚,線程持有鎖的時間都不會太長纤怒,如果直接掛起操作系統(tǒng)層面的線程可能會得不償失诺祸,畢竟操作系統(tǒng)實現(xiàn)線程之間的切換時需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個狀態(tài)之間的轉(zhuǎn)換需要相對比較長的時間,時間成本相對較高,因此自旋鎖會假設(shè)在不久將來,當(dāng)前的線程可以獲得鎖静浴,因此虛擬機會讓當(dāng)前想要獲取鎖的線程做幾個空循環(huán)(這也是稱為自旋的原因)软免,一般不會太久胚委,可能是50個循環(huán)或100循環(huán),在經(jīng)過若干次循環(huán)后荚板,如果得到鎖,就順利進(jìn)入臨界區(qū)婶希。如果還不能獲得鎖壁晒,那就會將線程在操作系統(tǒng)層面掛起不撑,這就是自旋鎖的優(yōu)化方式,這種方式確實也是可以提升效率的。最后沒辦法也就只能升級為重量級鎖了。


2.Java中設(shè)置最大堆和最小堆的參數(shù)是什么

-Xmx128m:設(shè)置JVM最大可用內(nèi)存為128M型型。

????? -Xms128m:設(shè)置JVM最小內(nèi)存為128m。此值可以設(shè)置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內(nèi)存啤握。

????? -Xmn2g:設(shè)置年輕代大小為2G咖祭。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小骚腥。持久代一般固定大小為64m口予,所以增大年輕代后且轨,將會減小年老代大小嫌变。此值對系統(tǒng)性能影響較大增热,Sun官方推薦配置為整個堆的3/8。

????? -Xss128k:設(shè)置每個線程的堆棧大小讼载。 JDK5.0以后每個線程堆棧大小為1M舆逃,以前每個線程堆棧大小為256K砂轻。根據(jù)應(yīng)用的線程所需內(nèi)存大小進(jìn)行調(diào)整抒痒。在相同物理內(nèi)存下寨辩,減小這個值能生成更 多的線程。但是操作系統(tǒng)對一個進(jìn)程內(nèi)的線程數(shù)還是有限制的缸榛,不能無限生成周叮,經(jīng)驗值在3000~5000左右。

3.volatile的作用

https://blog.csdn.net/roy_70/article/details/69524115

4.多個線程同時讀寫,讀線程的數(shù)量遠(yuǎn)遠(yuǎn)對于寫線程,你認(rèn)為應(yīng)該如何解決 并發(fā)的問題,你會加什么樣的鎖?

https://www.cnblogs.com/zzlp/p/5174745.html

5.java中的AQS是否了解,它是干嘛的呢?

http://www.360doc.com/content/14/0813/15/1073512_401561812.shtml

6. 除了synchronized關(guān)鍵字之外,你是這么來保障線程安全的?


7.什么時候需要加volatile關(guān)鍵字,它能保證線程安全嗎?

https://blog.csdn.net/roy_70/article/details/69524115

8.mybaties怎么防止SQL注入

#{}是經(jīng)過預(yù)編譯的各薇,是安全的项贺;${}是未經(jīng)過預(yù)編譯的,僅僅是取變量的值得糜,是非安全的敬扛,存在SQL注入。

9.Hibernate的緩存機制

10.Hibernate的一級緩存:?Session

11.Hibernate的二級緩存 :?SessionFactory?

Hibernate的緩存包括Session的緩存和SessionFactory的緩存朝抖,其中SessionFactory的緩存又可以分為兩類:內(nèi)置緩存和外置緩存啥箭。Session的緩存是內(nèi)置的,不能被卸載治宣,也被稱為Hibernate的第一級緩存急侥。SessionFactory的內(nèi)置緩存和Session的緩存在實現(xiàn)方式上比較相似砌滞,前者是SessionFactory對象的一些集合屬性包含的數(shù)據(jù),后者是指Session的一些集合屬性包含的數(shù)據(jù)坏怪。SessionFactory的內(nèi)置緩存中存放了映射元數(shù)據(jù)和預(yù)定義SQL語句昔脯,映射元數(shù)據(jù)是映射文件中數(shù)據(jù)的拷貝,而預(yù)定義SQL語句是在Hibernate初始化階段根據(jù)映射元數(shù)據(jù)推導(dǎo)出來护戳,SessionFactory的內(nèi)置緩存是只讀的看成,應(yīng)用程序不能修改緩存中的映射元數(shù)據(jù)和預(yù)定義SQL語句,因此SessionFactory不需要進(jìn)行內(nèi)置緩存與映射文件的同步鹏秋。SessionFactory的外置緩存是一個可配置的插件尊蚁。在默認(rèn)情況下,SessionFactory不會啟用這個插件侣夷。外置緩存的數(shù)據(jù)是數(shù)據(jù)庫數(shù)據(jù)的拷貝横朋,外置緩存的介質(zhì)可以是內(nèi)存或者硬盤。SessionFactory的外置緩存也被稱為Hibernate的第二級緩存百拓。


12.什么的數(shù)據(jù)適合放到第二級緩存中

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? Hibernate二級緩存適用場景

1) 很少被后臺修改的數(shù)據(jù)

2) 不是很重要的數(shù)據(jù)琴锭,允許出現(xiàn)偶爾并發(fā)的數(shù)據(jù)

3) 訪問量大,不會被并發(fā)訪問的數(shù)據(jù)衙传,如個人資料

4) 參考數(shù)據(jù),指的是供應(yīng)用參考的常量數(shù)據(jù)决帖,它的實例數(shù)目有限,它的實例會被許多其他類的實例引用粪牲,實例極少或者從來不會被修改古瓤。


2.不適合存放到第二級緩存的數(shù)據(jù)

1) 經(jīng)常被后臺修改的數(shù)據(jù) ,這里指的是前臺后臺使用了不同的orm實現(xiàn)

2) 財務(wù)數(shù)據(jù)腺阳,絕對不允許出現(xiàn)并發(fā)

3) 與其他應(yīng)用共享的數(shù)據(jù)落君。

4)訪問量不大的數(shù)據(jù)



13.Mybatis和Hibernate的區(qū)別(優(yōu)缺點)

Mybatis優(yōu)勢

MyBatis可以進(jìn)行更為細(xì)致的SQL優(yōu)化,可以減少查詢字段亭引。

MyBatis容易掌握绎速,而Hibernate門檻較高。

Hibernate優(yōu)勢

Hibernate的DAO層開發(fā)比MyBatis簡單焙蚓,Mybatis需要維護(hù)SQL和結(jié)果映射纹冤。

Hibernate對對象的維護(hù)和緩存要比MyBatis好,對增刪改查的對象的維護(hù)要方便购公。

Hibernate數(shù)據(jù)庫移植性很好萌京,MyBatis的數(shù)據(jù)庫移植性不好,不同的數(shù)據(jù)庫需要寫不同SQL宏浩。

Hibernate有更好的二級緩存機制知残,可以使用第三方緩存。MyBatis本身提供的緩存機制不佳比庄。


------

使用Hibernate進(jìn)行編程有以下好處:

? ? 1求妹,消除了代碼的映射規(guī)則乏盐,它全部分離到了xml或者注解里面去配置。

? ? 2制恍,無需在管理數(shù)據(jù)庫連接父能,它也配置到xml里面了。

? ? 3净神,一個會話中不需要操作多個對象何吝,只需要操作Session對象。

? ? 4强挫,關(guān)閉資源只需要關(guān)閉一個Session便可岔霸。

? ? 這就是Hibernate的優(yōu)勢薛躬,在配置了映射文件和數(shù)據(jù)庫連接文件后俯渤,Hibernate就可以通過Session操作,非常容易型宝,消除了jdbc帶來的大量代碼八匠,大大提高了編程的簡易性和可讀性。Hibernate還提供了級聯(lián)趴酣,緩存梨树,映射,一對多等功能岖寞。Hibernate是全表映射抡四,通過HQL去操作pojo進(jìn)而操作數(shù)據(jù)庫的數(shù)據(jù)。


? ? Hibernate的缺點:

? ? 1仗谆,全表映射帶來的不便指巡,比如更新時需要發(fā)送所有的字段。

? ? 2隶垮,無法根據(jù)不同的條件組裝不同的SQL藻雪。

? ? 3,對多表關(guān)聯(lián)和復(fù)雜的sql查詢支持較差狸吞,需要自己寫sql勉耀,返回后,需要自己將數(shù)據(jù)封裝為pojo蹋偏。

? ? 4便斥,不能有效的支持存儲過程。

? ? 5威始,雖然有HQL枢纠,但是性能較差,大型互聯(lián)網(wǎng)系統(tǒng)往往需要優(yōu)化sql字逗,而hibernate做不到京郑。


Mybatis:

? ? 為了解決Hibernate的不足宅广,Mybatis出現(xiàn)了,Mybatis是半自動的框架些举。之所以稱它為半自動跟狱,是因為它需要手工匹配提供POJO,sql和映射關(guān)系户魏,而全表映射的Hibernate只需要提供pojo和映射關(guān)系即可驶臊。

? ?Mybatis需要提供的映射文件包含了一下三個部分:sql,映射規(guī)則叼丑,pojo关翎。在Mybatis里面你需要自己編寫sql,雖然比Hibernate配置多鸠信,但是Mybatis可以配置動態(tài)sql纵寝,解決了hibernate表名根據(jù)時間變化,不同條件下列不一樣的問題星立,同時你也可以對sql進(jìn)行優(yōu)化爽茴,通過配置決定你的sql映射規(guī)則,也能支持存儲過程绰垂,所以對于一些復(fù)雜和需要優(yōu)化性能的sql查詢它就更加方便室奏。Mybatis幾乎可以做到j(luò)dbc所有能做到的事情。


什么時候使用Hibernate劲装,Mybatis

Hibernate作為留下的Java?orm框架胧沫,它確實編程簡易,需要我們提供映射的規(guī)則占业,完全可以通過IDE生成绒怨,同時無需編寫sql確實開發(fā)效率優(yōu)于Mybatis。此外Hibernate還提供了緩存纺酸,日志窖逗,級聯(lián)等強大的功能,但是Hibernate的缺陷也是十分明顯餐蔬,多表關(guān)聯(lián)復(fù)雜sql碎紊,數(shù)據(jù)系統(tǒng)權(quán)限限制,根據(jù)條件變化的sql樊诺,存儲過程等場景使用Hibernate十分不方便仗考,而性能又難以通過sql優(yōu)化,所以注定了Hibernate只適用于在場景不太復(fù)雜词爬,要求性能不太苛刻的時候使用秃嗜。

? ? 如果你需要一個靈活的,可以動態(tài)生成映射關(guān)系的框架,那么Mybatis確實是一個最好的選擇锅锨。它幾乎可以替代jdbc叽赊,擁有動態(tài)列,動態(tài)表名必搞,存儲過程支持必指,同時提供了簡易的緩存,日志恕洲,級聯(lián)塔橡。但是它的缺陷是需要你提供映射規(guī)則和sql,所以開發(fā)工作量比hibernate要大些霜第。



14.redis的使用場景

https://www.cnblogs.com/NiceCui/p/7794659.html

15.Tomcat本身的參數(shù)你一般會這么調(diào)整

https://blog.csdn.net/ldx891113/article/details/51735171

16.如果有很多的數(shù)據(jù)插入MYSQL,你會選擇什么引擎

MYISAM


19.如何自定義類加載器,你用過哪些場景需要自定義類加載器?

https://www.zhihu.com/question/46719811

http://www.reibang.com/p/acc7595f1b9d

20.堆內(nèi)存設(shè)置的參數(shù)是什么

-Xmx128m:設(shè)置JVM最大可用內(nèi)存為128M葛家。

????? -Xms128m:設(shè)置JVM最小內(nèi)存為128m。此值可以設(shè)置與-Xmx相同泌类,以避免每次垃圾回收完成后JVM重新分配內(nèi)存癞谒。

????? -Xmn2g:設(shè)置年輕代大小為2G。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小末誓。持久代一般固定大小為64m扯俱,所以增大年輕代后,將會減小年老代大小喇澡。此值對系統(tǒng)性能影響較大,Sun官方推薦配置為整個堆的3/8殊校。

????? -Xss128k:設(shè)置每個線程的堆棧大小晴玖。 JDK5.0以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K为流。根據(jù)應(yīng)用的線程所需內(nèi)存大小進(jìn)行調(diào)整呕屎。在相同物理內(nèi)存下,減小這個值能生成更 多的線程敬察。但是操作系統(tǒng)對一個進(jìn)程內(nèi)的線程數(shù)還是有限制的秀睛,不能無限生成,經(jīng)驗值在3000~5000左右莲祸。


24. 1.8后 Perm Space有哪些變動, MetaSpace默認(rèn)是有限的么?還是你們通過什么方式指定


PermGen 空間的狀況

這部分內(nèi)存空間將全部移除蹂安。

JVM的參數(shù):PermSize 和 MaxPermSize 會被忽略并給出警告(如果在啟用時設(shè)置了這兩個參數(shù))。

Metaspace 內(nèi)存分配模型

大部分類元數(shù)據(jù)都在本地內(nèi)存中分配锐帜。

用于描述類元數(shù)據(jù)的“klasses”已經(jīng)被移除田盈。

Metaspace 容量

默認(rèn)情況下,類元數(shù)據(jù)只受可用的本地內(nèi)存限制(容量取決于是32位或是64位操作系統(tǒng)的可用虛擬內(nèi)存大薪裳帧)允瞧。

新參數(shù)(MaxMetaspaceSize)用于限制本地內(nèi)存分配給類元數(shù)據(jù)的大小。如果沒有指定這個參數(shù),元空間會在運行時根據(jù)需要動態(tài)調(diào)整述暂。

Metaspace 垃圾回收

對于僵死的類及類加載器的垃圾回收將在元數(shù)據(jù)使用達(dá)到“MaxMetaspaceSize”參數(shù)的設(shè)定值時進(jìn)行痹升。

適時地監(jiān)控和調(diào)整元空間對于減小垃圾回收頻率和減少延時是很有必要的。持續(xù)的元空間垃圾回收說明畦韭,可能存在類视卢、類加載器導(dǎo)致的內(nèi)存泄漏或是大小設(shè)置不合適。

Java 堆內(nèi)存的影響

一些雜項數(shù)據(jù)已經(jīng)移到Java堆空間中廊驼。升級到JDK8之后据过,會發(fā)現(xiàn)Java堆 空間有所增長。


元空間(Metaspace):

一種新的內(nèi)存空間的誕生

JDK8 HotSpot JVM 使用本地內(nèi)存來存儲類元數(shù)據(jù)信息并稱之為:元空間(Metaspace)妒挎;這與Oracle JRockit?和IBM JVM’s很相似绳锅。這將是一個好消息:意味著不會再有java.lang.OutOfMemoryError: PermGen問題,也不再需要你進(jìn)行調(diào)優(yōu)及監(jiān)控內(nèi)存空間的使用……但請等等酝掩,這么說還為時過早鳞芙。在默認(rèn)情況下,這些改變是透明的期虾,接下來我們的展示將使你知道仍然要關(guān)注類元數(shù)據(jù)內(nèi)存的占用原朝。請一定要牢記,這個新特性也不能神奇地消除類和類加載器導(dǎo)致的內(nèi)存泄漏镶苞。你需求使用不同的方法以及遵守新的命名約定來追蹤這些問題喳坠。我推薦大家閱讀有關(guān)PermGen移除總結(jié)和Jon對此的評論



25.Jstack 是干什么的? Jstat呢? 如果線上程序周期性出現(xiàn)卡頓,你懷疑是否是GC導(dǎo)致的

? 你會這么來排查這個問題? 線程日志你會看其中的什么部分?

http://www.51testing.com/html/92/77492-203728.html

https://www.cnblogs.com/chengJAVA/p/5821218.html

jstack可以定位到線程堆棧茂蚓,根據(jù)堆棧信息我們可以定位到具體代碼壕鹉,所以它在JVM性能調(diào)優(yōu)中使用得非常多。下面我們來一個實例找出某個Java進(jìn)程中最耗費CPU的Java線程并定位堆棧信息聋涨,用到的命令有ps晾浴、top、printf牍白、jstack脊凰、grep。




26.StackOverFlow異常有沒有遇到過,一般會在什么情況下觸發(fā),如何指定堆棧的大小,一般你們寫多少

stackoverflow:

每當(dāng)java程序啟動一個新的線程時茂腥,java虛擬機會為他分配一個棧狸涌,java棧以幀為單位保持線程運行狀態(tài);當(dāng)線程調(diào)用一個方法是础芍,jvm壓入一個新的棧幀到這個線程的棧中杈抢,只要這個方法還沒返回,這個棧幀就存在仑性。?

如果方法的嵌套調(diào)用層次太多(如遞歸調(diào)用),隨著java棧中的幀的增多惶楼,最終導(dǎo)致這個線程的棧中的所有棧幀的大小的總和大于-Xss設(shè)置的值,而產(chǎn)生生StackOverflowError溢出異常。

outofmemory:

2.1歼捐、棧內(nèi)存溢出

java程序啟動一個新線程時何陆,沒有足夠的空間為改線程分配java棧,一個線程java棧的大小由-Xss設(shè)置決定豹储;JVM則拋出OutOfMemoryError異常贷盲。

2.2、堆內(nèi)存溢出

java堆用于存放對象的實例剥扣,當(dāng)需要為對象的實例分配內(nèi)存時巩剖,而堆的占用已經(jīng)達(dá)到了設(shè)置的最大值(通過-Xmx)設(shè)置最大值,則拋出OutOfMemoryError異常钠怯。

方法區(qū)內(nèi)存溢出

方法區(qū)用于存放java類的相關(guān)信息佳魔,如類名、訪問修飾符晦炊、常量池鞠鲜、字段描述、方法描述等断国。在類加載器加載class文件到內(nèi)存中的時候贤姆,JVM會提取其中的類信息,并將這些類信息放到方法區(qū)中稳衬。?

當(dāng)需要存儲這些類信息霞捡,而方法區(qū)的內(nèi)存占用又已經(jīng)達(dá)到最大值(通過-XX:MaxPermSize);將會拋出OutOfMemoryError異常對于這種情況的測試宋彼,基本的思路是運行時產(chǎn)生大量的類去填滿方法區(qū)弄砍,直到溢出。這里需要借助CGLib直接操作字節(jié)碼運行時输涕,生成了大量的動態(tài)類。



27.簡述synchronized? Object;Monitor機制

synchronized的三種應(yīng)用方式

synchronized關(guān)鍵字最主要有以下3種應(yīng)用方式慨畸,下面分別介紹

修飾實例方法莱坎,作用于當(dāng)前實例加鎖,進(jìn)入同步代碼前要獲得當(dāng)前實例的鎖

修飾靜態(tài)方法寸士,作用于當(dāng)前類對象加鎖檐什,進(jìn)入同步代碼前要獲得當(dāng)前類對象的鎖

修飾代碼塊,指定加鎖對象弱卡,對給定對象加鎖乃正,進(jìn)入同步代碼庫前要獲得給定對象的鎖。

Java 虛擬機中的同步(Synchronization)基于進(jìn)入和退出管程(Monitor)對象實現(xiàn)婶博, 無論是顯式同步(有明確的 monitorenter 和 monitorexit 指令,即同步代碼塊)還是隱式同步都是如此瓮具。在 Java 語言中,同步用的最多的地方可能是被 synchronized 修飾的同步方法。


Java虛擬機對synchronized的優(yōu)化

鎖的狀態(tài)總共有四種名党,無鎖狀態(tài)叹阔、偏向鎖、輕量級鎖和重量級鎖传睹。隨著鎖的競爭耳幢,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖欧啤,但是鎖的升級是單向的睛藻,也就是說只能從低到高升級,不會出現(xiàn)鎖的降級邢隧,關(guān)于重量級鎖店印,前面我們已詳細(xì)分析過,下面我們將介紹偏向鎖和輕量級鎖以及JVM的其他優(yōu)化手段府框,這里并不打算深入到每個鎖的實現(xiàn)和轉(zhuǎn)換過程更多地是闡述Java虛擬機所提供的每個鎖的核心優(yōu)化思想吱窝,畢竟涉及到具體過程比較繁瑣,如需了解詳細(xì)過程可以查閱《深入理解Java虛擬機原理》迫靖。

偏向鎖

偏向鎖是Java 6之后加入的新鎖院峡,它是一種針對加鎖操作的優(yōu)化手段,經(jīng)過研究發(fā)現(xiàn)系宜,在大多數(shù)情況下照激,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得盹牧,因此為了減少同一線程獲取鎖(會涉及到一些CAS操作,耗時)的代價而引入偏向鎖俩垃。偏向鎖的核心思想是,如果一個線程獲得了鎖汰寓,那么鎖就進(jìn)入偏向模式口柳,此時Mark Word 的結(jié)構(gòu)也變?yōu)槠蜴i結(jié)構(gòu),當(dāng)這個線程再次請求鎖時有滑,無需再做任何同步操作跃闹,即獲取鎖的過程,這樣就省去了大量有關(guān)鎖申請的操作毛好,從而也就提供程序的性能望艺。所以,對于沒有鎖競爭的場合肌访,偏向鎖有很好的優(yōu)化效果找默,畢竟極有可能連續(xù)多次是同一個線程申請相同的鎖。但是對于鎖競爭比較激烈的場合吼驶,偏向鎖就失效了惩激,因為這樣場合極有可能每次申請鎖的線程都是不相同的店煞,因此這種場合下不應(yīng)該使用偏向鎖,否則會得不償失咧欣,需要注意的是浅缸,偏向鎖失敗后,并不會立即膨脹為重量級鎖魄咕,而是先升級為輕量級鎖衩椒。下面我們接著了解輕量級鎖。

輕量級鎖

倘若偏向鎖失敗哮兰,虛擬機并不會立即升級為重量級鎖毛萌,它還會嘗試使用一種稱為輕量級鎖的優(yōu)化手段(1.6之后加入的),此時Mark Word 的結(jié)構(gòu)也變?yōu)檩p量級鎖的結(jié)構(gòu)喝滞。輕量級鎖能夠提升程序性能的依據(jù)是“對絕大部分的鎖阁将,在整個同步周期內(nèi)都不存在競爭”,注意這是經(jīng)驗數(shù)據(jù)右遭。需要了解的是做盅,輕量級鎖所適應(yīng)的場景是線程交替執(zhí)行同步塊的場合,如果存在同一時間訪問同一鎖的場合窘哈,就會導(dǎo)致輕量級鎖膨脹為重量級鎖吹榴。

自旋鎖

輕量級鎖失敗后,虛擬機為了避免線程真實地在操作系統(tǒng)層面掛起滚婉,還會進(jìn)行一項稱為自旋鎖的優(yōu)化手段图筹。這是基于在大多數(shù)情況下,線程持有鎖的時間都不會太長让腹,如果直接掛起操作系統(tǒng)層面的線程可能會得不償失远剩,畢竟操作系統(tǒng)實現(xiàn)線程之間的切換時需要從用戶態(tài)轉(zhuǎn)換到核心態(tài),這個狀態(tài)之間的轉(zhuǎn)換需要相對比較長的時間骇窍,時間成本相對較高瓜晤,因此自旋鎖會假設(shè)在不久將來,當(dāng)前的線程可以獲得鎖腹纳,因此虛擬機會讓當(dāng)前想要獲取鎖的線程做幾個空循環(huán)(這也是稱為自旋的原因)活鹰,一般不會太久,可能是50個循環(huán)或100循環(huán)只估,在經(jīng)過若干次循環(huán)后,如果得到鎖着绷,就順利進(jìn)入臨界區(qū)蛔钙。如果還不能獲得鎖,那就會將線程在操作系統(tǒng)層面掛起荠医,這就是自旋鎖的優(yōu)化方式吁脱,這種方式確實也是可以提升效率的桑涎。最后沒辦法也就只能升級為重量級鎖了。



28.簡述 happen-before規(guī)則

http://mp.weixin.qq.com/s?__biz=MzUxNDA1NDI3OA==&mid=2247484068&idx=1&sn=ee27564362a1db89e114ae173d0d930d&chksm=f94a834dce3d0a5b52694e9e85983975b79c434b69575a10ca682b780aa8e7589ebb357b16b4&mpshare=1&scene=1&srcid=0330lEoyj8JDo7DCze8oSt2E#rd


29. JUC和 Object; Monitor機制區(qū)別是什么; 簡述 AQS原理

synchronized和lock的用法區(qū)別

synchronized:在需要同步的對象中加入此控制兼贡,synchronized可以加在方法上攻冷,也可以加在特定代碼塊中,括號中表示需要鎖的對象遍希。

lock:需要顯示指定起始位置和終止位置等曼。一般使用ReentrantLock類做為鎖,多個線程中必須要使用一個ReentrantLock類做為對象才能保證鎖的生效凿蒜。且在加鎖和解鎖處需要通過lock()和unlock()顯示指出禁谦。所以一般會在finally塊中寫unlock()以防死鎖。

ReentrantLock 擁有Synchronized相同的并發(fā)性和內(nèi)存語義废封,此外還多了 鎖投票州泊,定時鎖等候和中斷鎖等候

線程A和B都要獲取對象O的鎖定,假設(shè)A獲取了對象O鎖漂洋,B將等待A釋放對O的鎖定遥皂,

如果使用 synchronized ,如果A不釋放刽漂,B將一直等下去演训,不能被中斷

如果 使用ReentrantLock,如果A不釋放爽冕,可以使B在等待了足夠長的時間以后仇祭,中斷等待,而干別的事情

ReentrantLock獲取鎖定與三種方式:

a) lock(), 如果獲取了鎖立即返回颈畸,如果別的線程持有鎖乌奇,當(dāng)前線程則一直處于休眠狀態(tài),直到獲取鎖眯娱。

b) tryLock(), 如果獲取了鎖立即返回true礁苗,如果別的線程正持有鎖,立即返回false徙缴。

c)tryLock(long timeout,TimeUnit unit)试伙, 如果獲取了鎖定立即返回true,如果別的線程正持有鎖于样,會等待參數(shù)給定的時間疏叨,在等待的過程中,如果獲取了鎖定穿剖,就返回true蚤蔓,如果等待超時,返回false糊余。

d) lockInterruptibly:如果獲取了鎖定立即返回秀又,如果沒有獲取鎖定单寂,當(dāng)前線程處于休眠狀態(tài),直到獲得鎖定吐辙,或者當(dāng)前線程被別的線程中斷宣决。


在非常復(fù)雜的同步應(yīng)用中,請考慮使用ReentrantLock昏苏,特別是遇到下面2種需求的時候尊沸。

某個線程在等待一個鎖的控制權(quán)的這段時間需要中斷

需要分開處理一些wait-notify,ReentrantLock里面的Condition應(yīng)用捷雕,能夠控制notify哪個線程

具有公平鎖功能椒丧,每個到來的線程都將排隊等候

2、synchronized是在JVM層面上實現(xiàn)的救巷,不但可以通過一些監(jiān)控工具監(jiān)控synchronized的鎖定壶熏,而且在代碼執(zhí)行時出現(xiàn)異常,JVM會自動釋放鎖定浦译,但是使用Lock則不行棒假,lock是通過代碼實現(xiàn)的,要保證鎖定一定會被釋放精盅,就必須將unLock()放到finally{}中

3帽哑、在資源競爭不是很激烈的情況下,Synchronized的性能要優(yōu)于ReetrantLock叹俏,但是在資源競爭很激烈的情況下妻枕,Synchronized的性能會下降幾十倍,但是ReetrantLock的性能能維持常態(tài)粘驰;


AQS:

https://segmentfault.com/a/1190000008471362


30. 簡述 DCL失效原因,解決方法

https://blog.csdn.net/zhaojw_420/article/details/70477921

DCL失效原因是:獲得鎖的線程正在執(zhí)行構(gòu)造函數(shù)的時候屡谐,其他的線程執(zhí)行到第一次檢查if?(m_instance?==?null)的時候,會返回false蝌数,因為已經(jīng)在執(zhí)行構(gòu)造函數(shù)了愕掏,就不是null,因此,會把沒有構(gòu)造完全的對象返回給線程使用顶伞。這是不安全的饵撑。


解決方案:?

1、最簡單而且安全的解決方法是使用static內(nèi)部類的思想唆貌,它利用的思想是:一個類直到被使用時才被初始化滑潘,而類初始化的過程是非并行的,這些都有JLS保證锨咙。?

如下述代碼:



31.簡述nio原理


33.gc算法有哪些; gc收集器有哪些

https://blog.csdn.net/canot/article/details/51038328

https://www.cnblogs.com/ityouknow/p/5614961.html

34.簡述class加載各階段過程

https://blog.csdn.net/tangdong3415/article/details/53768099

35, 簡述JDK命令行工具

https://blog.csdn.net/ochangwen/article/details/52971913

36.簡述字節(jié)碼文件組成




37.講講你平常是如何針對具體的SQL做優(yōu)化

http://www.reibang.com/p/22b5add93264

38.mysql的存儲有哪些,區(qū)別

innodb . ?MYISAM


39.gc內(nèi)存模型




40.如何實現(xiàn)一個定時調(diào)度和循環(huán)調(diào)度的工具類,當(dāng)提交任務(wù)處理不過來的時候,拒絕機制應(yīng)該如何處理,線程池默認(rèn)有哪幾種拒絕機制

https://blog.csdn.net/heyutao007/article/details/38797335

http://www.importnew.com/19011.html

41. 如何實現(xiàn)一個 ThreadLocal

當(dāng)前線程Id 為 key 的Map

http://www.reibang.com/p/ee8c9dccc953

http://www.reibang.com/p/ee8c9dccc953

42. 說說你了解的線程安全隊列


在Java多線程應(yīng)用中众羡,隊列的使用率很高,多數(shù)生產(chǎn)消費模型的首選數(shù)據(jù)結(jié)構(gòu)就是隊列蓖租。Java提供的線程安全的Queue可以分為阻塞隊列和非阻塞隊列粱侣,其中阻塞隊列的典型例子是BlockingQueue,非阻塞隊列的典型例子是ConcurrentLinkedQueue蓖宦,在實際應(yīng)用中要根據(jù)實際需要選用阻塞隊列或者非阻塞隊列齐婴。

注:什么叫線程安全?這個首先要明確稠茂。線程安全的類?柠偶,指的是類內(nèi)共享的全局變量的訪問必須保證是不受多線程形式影響的。如果由于多線程的訪問(比如修改睬关、遍歷诱担、查看)而使這些變量結(jié)構(gòu)被破壞或者針對這些變量操作的原子性被破壞,則這個類就不是線程安全的电爹。

今天就聊聊這兩種Queue蔫仙,本文分為以下兩個部分,用分割線分開:?

BlockingQueue? 阻塞算法

ConcurrentLinkedQueue丐箩,非阻塞算法

Queue是什么就不需要多說了吧摇邦,一句話:隊列是先進(jìn)先出。相對的屎勘,棧是后進(jìn)先出施籍。如果不熟悉的話先找本基礎(chǔ)的數(shù)據(jù)結(jié)構(gòu)的書看看吧。?


從上表可以很明顯看出每個方法的作用概漱,這個不用多說丑慎。我想說的是:?

add(e) remove() element() 方法不會阻塞線程。當(dāng)不滿足約束條件時瓤摧,會拋出IllegalStateException 異常竿裂。例如:當(dāng)隊列被元素填滿后,再調(diào)用add(e)姻灶,則會拋出異常铛绰。

offer(e) poll() peek() 方法即不會阻塞線程,也不會拋出異常产喉。例如:當(dāng)隊列被元素填滿后捂掰,再調(diào)用offer(e),則不會插入元素曾沈,函數(shù)返回false这嚣。

要想要實現(xiàn)阻塞功能,需要調(diào)用put(e) take() 方法塞俱。當(dāng)不滿足約束條件時姐帚,會阻塞線程。

------------

下面再來說說ConcurrentLinkedQueue障涯,它是一個無鎖的并發(fā)線程安全的隊列罐旗。?

以下部分的內(nèi)容參照了這個帖子http://yanxuxin.iteye.com/blog/586943

對比鎖機制的實現(xiàn)膳汪,使用無鎖機制的難點在于要充分考慮線程間的協(xié)調(diào)。簡單的說就是多個線程對內(nèi)部數(shù)據(jù)結(jié)構(gòu)進(jìn)行訪問時九秀,如果其中一個線程執(zhí)行的中途因為一些原因出現(xiàn)故障遗嗽,其他的線程能夠檢測并幫助完成剩下的操作。這就需要把對數(shù)據(jù)結(jié)構(gòu)的操作過程精細(xì)的劃分成多個狀態(tài)或階段鼓蜒,考慮每個階段或狀態(tài)多線程訪問會出現(xiàn)的情況痹换。

ConcurrentLinkedQueue有兩個volatile的線程共享變量:head,tail都弹。要保證這個隊列的線程安全就是保證對這兩個Node的引用的訪問(更新娇豫,查看)的原子性和可見性,由于volatile本身能夠保證可見性畅厢,所以就是對其修改的原子性要被保證冯痢。




43. Atomic包的實現(xiàn)原理

Compare and swap(CAS) ?通過CAS實現(xiàn)的,可能 涉及ABA問題

https://blog.csdn.net/zhangerqing/article/details/43057799

44.CAS是這么保證原子性的

在JDK 5之前Java語言是靠synchronized關(guān)鍵字保證同步的,這會導(dǎo)致有鎖(后面的章節(jié)還會談到鎖)或详。

? ? ? 鎖機制存在以下問題:

? ? ? (1)在多線程競爭下系羞,加鎖、釋放鎖會導(dǎo)致比較多的上下文切換和調(diào)度延時霸琴,引起性能問題椒振。

? ? ? (2)一個線程持有鎖會導(dǎo)致其它所有需要此鎖的線程掛起。

? ? ? (3)如果一個優(yōu)先級高的線程等待一個優(yōu)先級低的線程釋放鎖會導(dǎo)致優(yōu)先級倒置梧乘,引起性能風(fēng)險澎迎。

? ? ? volatile是不錯的機制,但是volatile不能保證原子性选调。因此對于同步最終還是要回到鎖機制上來夹供。

? ? ? 獨占鎖是一種悲觀鎖,synchronized就是一種獨占鎖仁堪,會導(dǎo)致其它所有需要鎖的線程掛起哮洽,等待持有鎖的線程釋放鎖。而另一個更加有效的鎖就是樂觀鎖弦聂。所謂樂觀鎖就是鸟辅,每次不加鎖而是假設(shè)沒有沖突而去完成某項操作,如果因為沖突失敗就重試莺葫,直到成功為止匪凉。

? ? ? CAS 操作

? ? ? 上面的樂觀鎖用到的機制就是CAS,Compare and Swap捺檬。

? ? ? CAS有3個操作數(shù)再层,內(nèi)存值V,舊的預(yù)期值A(chǔ)缺猛,要修改的新值B览徒。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時不脯,將內(nèi)存值V修改為B启摄,否則什么都不做。

非阻塞算法 (nonblocking algorithms)

一個線程的失敗或者掛起不應(yīng)該影響其他線程的失敗或掛起的算法贴届。

? ? ? 現(xiàn)代的CPU提供了特殊的指令卤材,可以自動更新共享數(shù)據(jù)洲胖,而且能夠檢測到其他線程的干擾瘫俊,而 compareAndSet() 就用這些代替了鎖定。

? ? ? 拿出AtomicInteger來研究在沒有鎖的情況下是如何做到數(shù)據(jù)正確性的悴灵。

private volatile int value;

? ? 首先毫無疑問扛芽,在沒有鎖的機制下需要借助volatile原語,保證線程間的數(shù)據(jù)是可見的(共享的)积瞒,這樣獲取變量值的時候才能直接讀取川尖。

public final int get() {

return value;

}

? ? ? 然后來看看++i是怎么做到的。

public final int incrementAndGet() {

for (;;) {

int current = get();

int next = current + 1;

if (compareAndSet(current, next))

return next;

}

}

? ? ? 在這里采用了CAS操作茫孔,每次從內(nèi)存中讀取數(shù)據(jù)然后將此數(shù)據(jù)和+1后的結(jié)果進(jìn)行CAS操作叮喳,如果成功就返回結(jié)果,否則重試直到成功為止缰贝。

? ? ? 而compareAndSet利用JNI來完成CPU指令的操作馍悟。

public final boolean compareAndSet(int expect, int update) {

return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

}

? ? ? 整體的過程就是這樣子的,利用CPU的CAS指令剩晴,同時借助JNI來完成Java的非阻塞算法锣咒。其它原子操作都是利用類似的特性完成的。

? ? ? 而整個J.U.C都是建立在CAS之上的赞弥,因此對于synchronized阻塞算法毅整,J.U.C在性能上有了很大的提升。參考資料的文章中介紹了如果利用CAS構(gòu)建非阻塞計數(shù)器绽左、隊列等數(shù)據(jù)結(jié)構(gòu)悼嫉。

? ? ? CAS看起來很爽,但是會導(dǎo)致“ABA問題”拼窥。

? ? ? CAS算法實現(xiàn)一個重要前提需要取出內(nèi)存中某時刻的數(shù)據(jù)戏蔑,而在下時刻比較并替換,但是在這個時間差內(nèi)任何變化都可能發(fā)生闯团。

? ? ? 比如說一個線程one從內(nèi)存位置V中取出A辛臊,這時候另一個線程two也從內(nèi)存中取出A,并且two進(jìn)行了一些操作變成了B房交,然后two又將V位置的數(shù)據(jù)變成A彻舰,這時候線程one進(jìn)行CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A,然后one操作成功。盡管線程one的CAS操作成功刃唤,但是不代表這個過程就是沒有問題的隔心。如果鏈表的頭在變化了兩次后恢復(fù)了原值,但是不代表鏈表就沒有變化尚胞。要解決"ABA問題"硬霍,我們需要增加一個版本號,在更新變量值的時候不應(yīng)該只更新一個變量值笼裳,而應(yīng)該更新兩個值唯卖,分別是變量值和版本號,AtomicStampedReference支持在兩個變量上進(jìn)行原子的條件更新躬柬,可以使用該類進(jìn)行更新操作拜轨。



45.string分析 1000次循環(huán) substring用了多少內(nèi)存


http://www.importnew.com/14105.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市允青,隨后出現(xiàn)的幾起案子橄碾,更是在濱河造成了極大的恐慌,老刑警劉巖颠锉,帶你破解...
    沈念sama閱讀 222,946評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件法牲,死亡現(xiàn)場離奇詭異,居然都是意外死亡琼掠,警方通過查閱死者的電腦和手機拒垃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來眉枕,“玉大人恶复,你說我怎么就攤上這事∷偬簦” “怎么了谤牡?”我有些...
    開封第一講書人閱讀 169,716評論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長姥宝。 經(jīng)常有香客問我翅萤,道長,這世上最難降的妖魔是什么腊满? 我笑而不...
    開封第一講書人閱讀 60,222評論 1 300
  • 正文 為了忘掉前任套么,我火速辦了婚禮,結(jié)果婚禮上碳蛋,老公的妹妹穿的比我還像新娘胚泌。我一直安慰自己,他們只是感情好肃弟,可當(dāng)我...
    茶點故事閱讀 69,223評論 6 398
  • 文/花漫 我一把揭開白布玷室。 她就那樣靜靜地躺著零蓉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪穷缤。 梳的紋絲不亂的頭發(fā)上敌蜂,一...
    開封第一講書人閱讀 52,807評論 1 314
  • 那天,我揣著相機與錄音津肛,去河邊找鬼章喉。 笑死,一個胖子當(dāng)著我的面吹牛身坐,可吹牛的內(nèi)容都是我干的秸脱。 我是一名探鬼主播,決...
    沈念sama閱讀 41,235評論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼部蛇,長吁一口氣:“原來是場噩夢啊……” “哼撞反!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起搪花,我...
    開封第一講書人閱讀 40,189評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嘹害,沒想到半個月后撮竿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,712評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡笔呀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,775評論 3 343
  • 正文 我和宋清朗相戀三年幢踏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片许师。...
    茶點故事閱讀 40,926評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡房蝉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出微渠,到底是詐尸還是另有隱情搭幻,我是刑警寧澤,帶...
    沈念sama閱讀 36,580評論 5 351
  • 正文 年R本政府宣布逞盆,位于F島的核電站檀蹋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏云芦。R本人自食惡果不足惜俯逾,卻給世界環(huán)境...
    茶點故事閱讀 42,259評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舅逸。 院中可真熱鬧桌肴,春花似錦、人聲如沸琉历。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,750評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至灼捂,卻和暖如春离例,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背悉稠。 一陣腳步聲響...
    開封第一講書人閱讀 33,867評論 1 274
  • 我被黑心中介騙來泰國打工宫蛆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人的猛。 一個月前我還...
    沈念sama閱讀 49,368評論 3 379
  • 正文 我出身青樓耀盗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親卦尊。 傳聞我的和親對象是個殘疾皇子叛拷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,930評論 2 361

推薦閱讀更多精彩內(nèi)容

  • Java8張圖 11、字符串不變性 12岂却、equals()方法忿薇、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,710評論 0 11
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法躏哩,類相關(guān)的語法署浩,內(nèi)部類的語法,繼承相關(guān)的語法扫尺,異常的語法筋栋,線程的語...
    子非魚_t_閱讀 31,669評論 18 399
  • 去年的此時此刻弊攘,耳機里循環(huán)播放的歌曲是《匆匆那年》,今年則換成《小幸運》姑曙。一年一年周而復(fù)始襟交,隨著時間的推移,總有些...
    張胤laji92454閱讀 225評論 1 1
  • 兩個人的回憶一個人過 其實你把我刪了也挺好的渣磷,這樣我就不會再心存幻想了婿着,第152天,每天睡覺時候都會夢到你醋界,整個人...
    野馬阿姨閱讀 525評論 0 1
  • 水果就要吃應(yīng)季的竟宋,我最喜歡草莓,春天的草莓是最好吃的形纺。隔著盒子都能聞到草莓的香甜味丘侠。很有天然的草莓味道,也很甜逐样,很...
    花衣企鵝閱讀 328評論 0 0