java并發(fā)面試問題:
1.聊聊你對(duì)CAS的理解:
并發(fā)包下的autoInteger的底層實(shí)現(xiàn),當(dāng)autointeger調(diào)用increment方法的時(shí)候蚂且,多個(gè)線程去調(diào)用這個(gè)方法浅蚪,首先查詢獲取當(dāng)前變量的值俱两,對(duì)變量值進(jìn)行修改,修改后繼續(xù)查詢當(dāng)前值是否為以前的值通孽,如果是的話做加一操作,如果不是前面的值,就獲取最新的值繼續(xù)上面這個(gè)操作产弹,直到修改成功。
image.png
2.concurrentHashMap的實(shí)現(xiàn)原理解析:
java.1.7 把大數(shù)組拆分成多個(gè)小數(shù)組弯囊,每個(gè)數(shù)組對(duì)應(yīng)一把鎖痰哨。
java 1.8 對(duì)鎖的粒度更加細(xì)化,存儲(chǔ)還是一個(gè)大數(shù)組匾嘱,如果兩個(gè)線程對(duì)同一個(gè)位置進(jìn)行put操作的時(shí)候斤斧,同一時(shí)間內(nèi)只有一個(gè)線程能執(zhí)行cas操作,就是剛開始獲取一下數(shù)組[5]這個(gè)位置的值奄毡,然后執(zhí)行CAS,線程1先去比較一下折欠,如果沒有變化,進(jìn)行修改吼过,同一時(shí)間其他線程執(zhí)行CAS都會(huì)失敗锐秦,如果其他人失敗了,就需要在這個(gè)位置基于紅黑樹+鏈表的模式盗忱,synchronized(數(shù)組[5]),加鎖酱床,基于鏈表 或者紅黑樹在這個(gè)位置插進(jìn)去自己的數(shù)據(jù)。
3.AQS的理解:
image.png
線程一和線程二都進(jìn)行CAS獲取鎖趟佃,當(dāng)線程1獲取鎖后扇谣,線程二進(jìn)入一個(gè)隊(duì)列中進(jìn)行等待,如果為非公平鎖闲昭,當(dāng)線程3進(jìn)行CAS的時(shí)候罐寨,剛才鎖釋放,線程三就會(huì)獲取到鎖序矩,如果是公平鎖的話鸯绿,首先判斷隊(duì)列中有沒有線程等待,如果有的話簸淀,先執(zhí)行線程二瓶蝴,
4.線程池的原理:
image.png
線程池原理,首先有了任務(wù)當(dāng)線程池沒有滿的時(shí)候租幕,直接在線程池中執(zhí)行舷手,當(dāng)線程池滿了的時(shí)候就進(jìn)入到阻塞隊(duì)列中,線程池有空余線程了就會(huì)從阻塞隊(duì)列中獲取任務(wù)并且執(zhí)行
5.線程池參數(shù)理解:
corePoolSize:核心線程數(shù)
maximumPoolSize:最大線程數(shù)
keepAliveTime:60s 非核心線程數(shù)的空閑時(shí)間
new ArrayBlockingQueue<Runnable>(200)
線程圖
實(shí)現(xiàn)功能圖: 當(dāng)有任務(wù)進(jìn)來的時(shí)候首先創(chuàng)建核心線程數(shù)劲绪,當(dāng)核心線程數(shù)都滿了的情況男窟,下來的任務(wù)進(jìn)入阻塞隊(duì)列中盆赤,當(dāng)阻塞隊(duì)列也滿了的話,就查看是否設(shè)置了最大線程數(shù)是否大于核心線程數(shù)蝎宇,如果設(shè)置了就開始創(chuàng)建非核心線程數(shù)弟劲,非核心線程就會(huì)去執(zhí)行多余的任務(wù)當(dāng)任務(wù)執(zhí)行完了的話就會(huì)從隊(duì)列中獲取任務(wù)執(zhí)行,當(dāng)任務(wù)都執(zhí)行完了的話姥芥,查看空閑時(shí)間兔乞,空閑時(shí)間到了非核心線程就會(huì)消亡。
如果非核心線程數(shù)數(shù)量也滿了凉唐,就要傳入RejectedExecutionHandler.選擇拒絕策略庸追。
6.[并發(fā)]java內(nèi)存模型的理解:
image.png
package container;
import java.util.*;
public class TestHashMap {
private static int i=0;
public static void main(String[] args) {
Map<String,String> map=new HashMap<String,String>();
new Thread(new Runnable(){
@Override
public void run() {
i++;
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
i++;
}
}).start();
}
}
兩個(gè)線程去修改當(dāng)前的的值,類中有主內(nèi)存台囱,每個(gè)線程有自己的一片工作內(nèi)存淡溯,首先一個(gè)線程使用read讀取數(shù)值,使用load加載到自己的工作內(nèi)存簿训,使用use進(jìn)行操作咱娶,使用assign返回給工作內(nèi)存,把工作內(nèi)存中的一個(gè)值傳遞給主內(nèi)存强品,write寫入主內(nèi)存膘侮,線程二也是相同的操作,所以會(huì)出現(xiàn)并發(fā)問題的榛。這就是并發(fā)線程模型琼了。
7.syncronized和reentrantlock的區(qū)別
syncronized可以鎖代碼塊、方法和對(duì)象
syncronized隱式的獲得釋放鎖夫晌,ReentrantLock顯示的獲得鎖雕薪,或者釋放鎖,
reentrantlock可以實(shí)現(xiàn)公平鎖晓淀,
syncronized是同步阻塞所袁,使用悲觀并發(fā)的策略,Reentranklock使用同步非阻塞凶掰,采用的是樂觀并發(fā)策略燥爷。
syncronized在發(fā)生異常時(shí),會(huì)自動(dòng)釋放線程占有的鎖锄俄,因此不會(huì)導(dǎo)致死鎖現(xiàn)象發(fā)生,而reentranklock在發(fā)生異常時(shí)勺拣,如果沒有主動(dòng)通知unLock()去釋放鎖奶赠,就會(huì)造成死鎖現(xiàn)象。因此需要在finally塊中釋放鎖药有。
ReentrankLock是API級(jí)別的毅戈,syncronized是JVM級(jí)別苹丸。
ReentrantLock的實(shí)現(xiàn)是一種自旋鎖,通過循環(huán)調(diào)用CAS操作來實(shí)現(xiàn)加鎖苇经,
syncronized在1.6之前是重量級(jí)鎖赘理,在1.6之后對(duì)syncronized進(jìn)行了進(jìn)一步的優(yōu)化,
Java虛擬機(jī)是通過進(jìn)入和退出Monitor對(duì)象來實(shí)現(xiàn)代碼塊同步和方法同步的扇单,代碼塊同步使用的是monitorenter和 monitorexit 指令實(shí)現(xiàn)的商模,而方法同步是通過Acc_syncronized后面的標(biāo)識(shí)來確定該方法是否為同步方法。
- 1.6之后syncronized引入了鎖升級(jí)蜘澜,引入了偏向鎖施流,和輕量級(jí)鎖,鎖的狀態(tài)變?yōu)榱怂姆N鄙信,
-鎖膨脹指鎖從無鎖->偏向鎖->輕量級(jí)鎖->重量級(jí)鎖的單項(xiàng)升級(jí)- 消除指的是在某些情況下瞪醋,JVM 虛擬機(jī)如果檢測(cè)不到某段代碼被共享和競(jìng)爭(zhēng)的可能性,就會(huì)將這段代碼所屬的同步鎖消除掉装诡,從而到底提高程序性能的目的
-鎖粗化是指银受,將多個(gè)連續(xù)的加鎖、解鎖操作連接在一起鸦采,擴(kuò)展成一個(gè)范圍更大的鎖
public String method() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10; i++) {
// 偽代碼:加鎖操作
sb.append( + i);
// 偽代碼:解鎖操作
}
return sb.toString();
}
-自旋鎖是指通過自身循環(huán)宾巍,嘗試獲取鎖的一種方式,偽代碼實(shí)現(xiàn)如下:
自旋鎖優(yōu)點(diǎn)在于它避免一些線程的掛起和恢復(fù)操作赖淤,因?yàn)閽炱鹁€程和恢復(fù)線程都需要從用戶態(tài)轉(zhuǎn)入內(nèi)核態(tài)蜀漆,這個(gè)過程是比較慢的,所以通過自旋的方式可以一定程度上避免線程掛起和恢復(fù)所造成的性能開銷咱旱。
8.java內(nèi)存模型中的原子性确丢,有序性,可見性
- 一次操作或者多次操作吐限,要么所有的操作全部都得到有效的執(zhí)行并且不會(huì)受到任何因素的干擾而中斷鲜侥,要么都不執(zhí)行。
- 當(dāng)一個(gè)線程對(duì)共享變量進(jìn)行了修改诸典,其他的線程都是立即可以看到修改后的最新值描函。
volatile(易變關(guān)鍵字)
它可以用來修飾成員變量和靜態(tài)成員變量,他可以避免線程從自己的工作緩存中查找變量的值狐粱,必須到主存中獲取它的值舀寓,線程操作 volatile 變量都是直接操作主內(nèi)存(指示 JVM,這個(gè)變量是共享且不穩(wěn)定肌蜻,每次使用它都到主存中進(jìn)行讀然ツ埂)
-有序性 由于指令重排序問題,代碼的執(zhí)行順序未必就是編寫代碼時(shí)候的順序蒋搜。
static int num = 0;
static int r1 = 0;
static boolean ready = false;
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
System.out.println("r1執(zhí)行1111");
if(ready) {
r1 = num + num;
System.out.println("r1進(jìn)入8888");
System.out.println("r1true");
} else {
System.out.println("r1false");
r1 = 1;
} }
}).start();
new Thread(new Runnable(){
@Override
public void run() {
System.out.println("r2先執(zhí)行");
num = 2;
ready = true; }
}).start();
}
- volatile 加上volatile字段可以阻止指令重排篡撵。
mysql面試問題
1.myIsam和innodb的區(qū)別
- myIsam不支持事務(wù)判莉,不支持外鍵約束,索引文件和數(shù)據(jù)文件分開育谬,對(duì)查詢效果會(huì)很好券盅,支持表鎖,
- innodb 1.支持行鎖膛檀,2.支持外鍵锰镀,3.支持事物,4.讀的效率低于myisam 5.寫的效率高于myisam,6.支持自增AUTO_INCREMENT屬性宿刮。
- mysql的索引實(shí)現(xiàn)原理:
從B樹來分析:B是一種多路平衡樹搜索樹互站,比平衡二叉樹存儲(chǔ)更多的結(jié)點(diǎn),相對(duì)于平衡二叉樹每個(gè)結(jié)點(diǎn)存儲(chǔ)更多結(jié)點(diǎn)僵缺,子節(jié)點(diǎn)最大的個(gè)數(shù)為B樹的階數(shù)胡桃,對(duì)比平衡二叉樹高度也更低,結(jié)點(diǎn)上存儲(chǔ)著鍵和值磕潮。
B+數(shù)是對(duì)B-數(shù)的更好的優(yōu)化:B+數(shù)非葉子節(jié)點(diǎn)不存儲(chǔ)數(shù)據(jù)翠胰,只有葉子節(jié)點(diǎn)存儲(chǔ)數(shù)據(jù),并且相鄰的葉子節(jié)點(diǎn)之間連接起來自脯,方便按照范圍查找之景。
b+樹的高度一般在2-4層,而每次IO能查100次膏潮,所以查詢到數(shù)據(jù)為0.02到0.04秒
myIsam的索引文件和數(shù)據(jù)文件是分開的锻狗,B+數(shù)最后存儲(chǔ)的是一個(gè)key鍵和物理地址,通過物理地區(qū)去數(shù)據(jù)文件中獲取數(shù)據(jù)焕参。
innodb要求必須要有主鍵(即使不創(chuàng)建主鍵系統(tǒng)也會(huì)默認(rèn)創(chuàng)建一個(gè)主鍵)轻纪,默認(rèn)使用主鍵建立索引,他的索引文件也是數(shù)據(jù)文件叠纷。
3.聚簇索引和非聚簇索引
innodb中根據(jù)主鍵建立的索引也叫做聚簇索引刻帚,非聚集索引與聚集索引的區(qū)別在于非聚集索引的葉子節(jié)點(diǎn)不存儲(chǔ)表中的數(shù)據(jù),而是存儲(chǔ)該列對(duì)應(yīng)的主鍵涩嚣,
想要查找數(shù)據(jù)我們還需要根據(jù)主鍵再去聚集索引中進(jìn)行查找崇众,這個(gè)再根據(jù)聚集索引查找數(shù)據(jù)的過程,我們稱為回表
4.索引的使用規(guī)則
-全列匹配
有三個(gè)列航厚,剛好有三個(gè)列顷歌,索引執(zhí)行
- 最左匹配原則
指的是聯(lián)合索引中,優(yōu)先走最左邊列的索引幔睬。對(duì)于多個(gè)字段的聯(lián)合索引眯漩,也同理。如 index(a,b,c) 聯(lián)合索引溪窒,則相當(dāng)于創(chuàng)建了 a 單列索引坤塞,(a,b)聯(lián)合索引,和(a,b,c)聯(lián)合索引
-最左前綴匹配澈蚌,中間某個(gè)值沒有匹配摹芙,先根據(jù)最左邊的開始找,走索引宛瞄,找到后在再這些數(shù)據(jù)中找- 如果是范圍查詢>=,<=between等操作浮禾,你只能符合最左匹配原則才可以范圍,范圍后的就不走索引了
- 如果對(duì)某列用了函數(shù)這一列就不用索引了
5.mysql中事物的ACID:
- 原子性(atomicity):一個(gè)原子事物要么完全執(zhí)行成功份汗,要么干脆不執(zhí)行
- 一致性(Consistency):執(zhí)行事物前后盈电,數(shù)據(jù)保持一致,例如轉(zhuǎn)賬杯活,轉(zhuǎn)賬者和收款者的總額應(yīng)該保持一致
- 隔離性(Isolation):并發(fā)訪問數(shù)據(jù)庫匆帚,一個(gè)用戶的事物不被其他事物所干擾,各并發(fā)事物之間數(shù)據(jù)庫是獨(dú)立的旁钧。
- 持久性(Durability) 一個(gè)事物被提交之后吸重,它對(duì)數(shù)據(jù)庫中數(shù)據(jù)的改變是持久的,即使數(shù)據(jù)庫發(fā)生了故障也不應(yīng)該對(duì)其有任何的影響
6.mysql的事物隔離級(jí)別
- 讀未提交 read uncommitted
所有事物都可以看到未提交事物的執(zhí)行結(jié)果歪今,在這種隔離級(jí)別會(huì)產(chǎn)生各種的問題嚎幸,本隔離級(jí)別很少用于事件中,因?yàn)樽x取未提交的會(huì)產(chǎn)生臟讀的情況寄猩。- 讀已提交 read committed
大多數(shù)數(shù)據(jù)庫采用的隔離級(jí)別(但不是mysql采取的隔離級(jí)別) 只能看見別人已經(jīng)提交的數(shù)據(jù)嫉晶,當(dāng)用戶第一次查詢結(jié)果,和第二次查詢結(jié)果中間出現(xiàn)了一次提交數(shù)據(jù)修改田篇,那第一次查詢的結(jié)果和第二次查詢的結(jié)果產(chǎn)生不一樣的結(jié)果替废,這個(gè)隔離級(jí)別也支持所謂的’不可重復(fù)讀’- 可重復(fù)讀 repeatable read 可重復(fù)讀
Mysql使用當(dāng)前的隔離級(jí)別,該級(jí)別解決了不可重復(fù)讀的問題斯辰,同一個(gè)事物在并 發(fā)讀取事物時(shí)舶担,會(huì)看到同樣的結(jié)果,不過這會(huì)導(dǎo)致一個(gè)棘手的問題幻讀- Serializeble 可串行化彬呻,
該為最高的隔離級(jí)別衣陶,它通過強(qiáng)制事物排序,使之不可互相沖突闸氮,從而解決幻讀的問題剪况,簡(jiǎn)言之,serializeble是在每個(gè)讀取數(shù)據(jù)上加鎖蒲跨,在這個(gè)級(jí)別中可能導(dǎo)致Timeout與鎖競(jìng)爭(zhēng)Lock Contention ,實(shí)際項(xiàng)目中很少使用到這個(gè)級(jí)別译断,但如果用戶的應(yīng)用為了數(shù)據(jù)的穩(wěn)定性,需要強(qiáng)制減少并發(fā)的話或悲,也可以使用當(dāng)前的隔離級(jí)別孙咪。- 幻讀: 第一次讀取數(shù)據(jù)可能是一行堪唐,但第二次再次讀取數(shù)據(jù)的時(shí)候因?yàn)閯偤糜芯€程插入了數(shù)據(jù),這時(shí)候數(shù)據(jù)變成了兩行翎蹈,從而出現(xiàn)第一次和第二次讀取數(shù)據(jù)不一樣的情況淮菠。
7.mysql的鎖
按鎖的粒度可分為:表鎖、頁面鎖荤堪、行鎖合陵、記錄鎖、間隙鎖澄阳、臨鍵鎖
按鎖的屬性可分為:共享鎖拥知、排它鎖
按加鎖機(jī)制可分為:樂觀鎖、悲觀鎖
- innodb:的行鎖:分為排他鎖(x)和共享鎖(s),當(dāng)對(duì)數(shù)據(jù)進(jìn)行,insert,delete,update的時(shí)候innodb會(huì)自動(dòng)給數(shù)據(jù)加上行級(jí)的排他鎖碎赢,當(dāng)為select操作的時(shí)候低剔,是不加鎖的
- 共享鎖(又稱讀鎖、S鎖):
作用:防止其他事務(wù)修改當(dāng)前數(shù)據(jù)肮塞。
加鎖方式:
在select語句末尾加上lock in share mode關(guān)鍵字户侥。
# 對(duì)id=1的用戶加讀鎖
select * from user where id=1 lock in share mode;
- 排他鎖(又稱寫鎖、X鎖):
作用:防止其他事務(wù)讀取或者更新當(dāng)前數(shù)據(jù)峦嗤。
加鎖方式:
在select語句末尾加上for update關(guān)鍵字蕊唐。
# 對(duì)id=1的用戶加寫鎖
select * from user where id=1 for update;
悲觀鎖:
總是假設(shè)別人會(huì)修改當(dāng)前數(shù)據(jù),所以每次讀取的時(shí)候烁设,總是加鎖替梨。然后就是一個(gè)人干事情別人都干不了。
適用于寫多讀少的場(chǎng)景装黑。
# 加讀鎖
select * from user where id=1 lock in share mode;
# 加寫鎖
select * from user where id=1 for update;
樂觀鎖
總是假設(shè)別人不會(huì)修改當(dāng)前數(shù)據(jù)副瀑,所以每次讀取數(shù)據(jù)的時(shí)候都不會(huì)加鎖,只是在更新數(shù)據(jù)的時(shí)候通過version判斷別人是否修改過數(shù)據(jù)恋谭,Java的atomic包下的類就是使用樂觀鎖(CAS)實(shí)現(xiàn)的糠睡。
適用于讀多寫少的場(chǎng)景。
select id,name,age,version from user id=1;
update user set age=age+1 where id=1 and version=1;
8.mysql的優(yōu)化過程:
如何處理慢查詢問題:
1)開啟慢查詢?nèi)罩揪渭眨瑴?zhǔn)確定位到那個(gè)sql語句出現(xiàn)了問題
2)分析sql語句狈孔,看是否查詢了多余列的數(shù)據(jù),對(duì)語句進(jìn)行分析后重寫
3)分析sql的執(zhí)行計(jì)劃材义,然后獲取使用索引的情況均抽,之后修改語句或者修改索引,使得語句可以盡可能的命中索引其掂。
4)如果對(duì)表中的優(yōu)化已經(jīng)無法進(jìn)行油挥,可以考慮表中的數(shù)據(jù)是否太大,如果是的話可以進(jìn)行橫向或者縱向的分表。
9.索引的設(shè)計(jì)原則是什么?
1)適合索引的列是出現(xiàn)在where語句中的列
2)基數(shù)較小的表深寥,索引效果差攘乒,沒必要?jiǎng)?chuàng)建索引
3)在選擇索引的時(shí)候越短越好,沒必要用全部字段做索引
4)定義有外鍵的數(shù)據(jù)列一定要建立索引
5)更新頻率大的列惋鹅,沒必要?jiǎng)?chuàng)建索引
6)大文本持灰、大對(duì)象不要?jiǎng)?chuàng)建索引。