并發(fā)編程中的3個(gè)概念
1.原子性:操作中包含的步驟,要么都執(zhí)行成功,要么都失敗。
2.可見性:一個(gè)線程修改了變量的值朋魔,其他線程能夠立即看到修改的值。
3.有序性:JVM處理器在執(zhí)行指令時(shí)會(huì)重排序以優(yōu)化運(yùn)行效率卿操,但是會(huì)考慮數(shù)據(jù)依賴問題警检。但是如果多線程時(shí)重排序會(huì)造成程序執(zhí)行的正確性。所以多線程時(shí)要保證代碼執(zhí)行的有序性害淤。
原子性
例如:
x = 10; //語句1
y = x; //語句2
x++; //語句3
x = x + 1; //語句4
上面4個(gè)語句扇雕,哪些是原子性操作?
答:第一個(gè)是窥摄,其他的都不是洼裤。第一個(gè)x賦值為10,然后再同步到主存中溪王,是一個(gè)原子操作。
第二個(gè)首先要從主存中讀x的值值骇,然后再賦值給y莹菱,2個(gè)步驟各自是原子操作,但合在一起不是原子操作吱瘩。
同理x++,x=x+1,也不是原子操作道伟。
可以看出Java內(nèi)存模型中讀取、賦值操作為原子操作。那如何保證更大范圍的原子操作呢蜜徽?
那就使用到了synchronized和Lock祝懂,這兩個(gè)悲觀鎖可以保證同一時(shí)刻只有一個(gè)線程訪問被修飾的代碼,自然也就是原子操作了拘鞋。
可見性
經(jīng)過volatile修飾的共享變量砚蓬,可以將修改后的值立即更新到主存。如果普通共享變量盆色,值更新到主存的時(shí)機(jī)是不確定的灰蛙,所以沒有可見性。除此之外隔躲,synchronized和Lock在釋放鎖之前會(huì)更新主存摩梧,也能保證可見性。
有序性
Java內(nèi)存模型允許編譯器宣旱、處理器對(duì)指令重排序仅父,單線程無影響,多線程會(huì)影響浑吟,所以synchronized笙纤、Lock都能保證有序性。
添加volatile關(guān)鍵字修飾后买置,能夠保證一定的有序性粪糙,同時(shí)Java內(nèi)存模型先天有一些happens-before原則也可以保證有序性。
happens-before(先行發(fā)生行為)原則:
程序次序規(guī)則:一個(gè)線程內(nèi)忿项,按照書寫順序蓉冈,寫在前面的先執(zhí)行。事實(shí)上單線程程序指令依然會(huì)重排轩触,但是能夠保證結(jié)果的一致性寞酿。
鎖定規(guī)則:一個(gè)鎖必須釋放后,才能夠繼續(xù)加鎖脱柱。
volatile規(guī)則:寫操作發(fā)生在讀操作之前伐弹。一個(gè)線程寫,一個(gè)線程讀榨为,讀在寫后面惨好。
傳遞規(guī)則:A操作先于B操作,B操作先于C操作随闺,則A操作限于C操作日川。
線程啟動(dòng)規(guī)則:Thread的start()方法先行于線程內(nèi)的一切操作。
線程中斷規(guī)則:Thread的interrupt()方法先行于該線程代碼檢測(cè)到中斷事件的發(fā)生矩乐。
線程的終結(jié)規(guī)則:線程中所有的操作都先行于線程的終止檢測(cè)龄句。
對(duì)象的終結(jié)規(guī)則:一個(gè)對(duì)象的初始化方法先行于finalize()方法的開始回论。
相關(guān)題目
1.進(jìn)程和線程之間區(qū)別?
一個(gè)進(jìn)程是一個(gè)獨(dú)立的運(yùn)行環(huán)境,線程是進(jìn)程中的一個(gè)任務(wù)分歇,進(jìn)程中可以包含多個(gè)線程傀蓉,各個(gè)線程間可以共享進(jìn)程的資源。
2.多線程編程的好處职抡?
多線程用以提高程序的執(zhí)行效率葬燎,一些線程資源等待時(shí),cpu可以去處理其他線程繁调,減少了空閑狀態(tài)萨蚕。多個(gè)線程間還可以共享堆內(nèi)存,所以多線程程序比單線程程序更好蹄胰。
3.用戶線程和守護(hù)線程的區(qū)別岳遥?
默認(rèn)新建的thread是用戶線程,thread.setDaemon(true)可設(shè)置守護(hù)線程裕寨。平常處理邏輯的線程為用戶線程浩蓉,程序運(yùn)行時(shí)在后臺(tái)提供通用服務(wù)的線程設(shè)置為守護(hù)進(jìn)程(如jvm的垃圾回收線程)。守護(hù)線程創(chuàng)建的線程也是守護(hù)線程宾袜。
4.如何創(chuàng)建線程捻艳?
兩種方法:通過繼承Thread類或者實(shí)現(xiàn)Runable接口覆蓋或?qū)崿F(xiàn)其中的run()方法,然后再調(diào)用new thread再start()
5.線程的生命周期庆猫?
新建:當(dāng)new thread()時(shí)认轨,線程處理“新建”狀態(tài);
就緒&運(yùn)行:thread.start()之后月培,如何沒有分配cpu等資源為就緒嘁字,分配了cpu等資源為運(yùn)行狀態(tài);
阻塞:線程被掛起為阻塞狀態(tài)杉畜,如thread.join()纪蜒,thread.sleep(),等待用戶輸入等此叠,將線程放入到阻塞隊(duì)列中纯续,阻塞消除變?yōu)榫途w狀態(tài);
等待:當(dāng)前線程需要等待其他線程的一些特定動(dòng)作時(shí)(如某個(gè)對(duì)象被synchronized(o)加鎖灭袁;o.wait()時(shí)需要其他線程o.notify()猬错、o.notifyAll()喚醒);
超時(shí)等待:等待的基礎(chǔ)上茸歧,超時(shí)了兔魂;
終止:線程運(yùn)行完畢。
6.我們可以直接調(diào)用thread的run()方法嗎举娩?
可以的,直接調(diào)用和普通類就一樣了。如果要在一個(gè)線程中啟動(dòng)另一個(gè)線程的話铜涉,就需要thread.start()了
7.線程的優(yōu)先級(jí)智玻?
我們可以定義線程的優(yōu)先級(jí),1代表最低芙代,10代表最高吊奢,一般來說高優(yōu)先級(jí)線程會(huì)有具有優(yōu)先權(quán),但是它的實(shí)現(xiàn)是操作系統(tǒng)實(shí)現(xiàn)的纹烹,并不能一定保證高優(yōu)先級(jí)在低優(yōu)先級(jí)線程前執(zhí)行页滚。
8.什么是線程調(diào)度器Thread Schedule和時(shí)間分片time slicing?
線程調(diào)度器是一個(gè)操作系統(tǒng)服務(wù)铺呵,它負(fù)責(zé)為就緒態(tài)的線程分配CPU時(shí)間片裹驰,可以繼續(xù)線程優(yōu)先級(jí)或線程等待時(shí)間,盡量通過程序自己控制線程的調(diào)度片挂,而不是依賴于不同操作系統(tǒng)的默認(rèn)實(shí)現(xiàn)幻林。
9.什么是多線程中的上下文切換(context-switching)?
上下文切換時(shí)存儲(chǔ)和恢復(fù)CPU狀態(tài)的過程音念,它使得線程能夠從中斷點(diǎn)恢復(fù)執(zhí)行沪饺。
10.如何確保main()方法是程序最終結(jié)束的線程?
在main()方法中開啟別的線程時(shí)闷愤,使用別的線程對(duì)象.join()方法整葡,將其同步在main()線程里即可。
11.線程間如何通信讥脐?
線程間如果共享資源時(shí)遭居,可以采用object對(duì)象的wait(),notify(),notiyAll()等方法通過加鎖的方式進(jìn)行通信。
12.為什么Java中wait(),notify()t,notifyAll()方法被定義在Object類里攘烛?
因?yàn)镴ava并沒有可供所有對(duì)象使用的鎖魏滚、同步器,但是所有類都繼承Object類坟漱,這樣每個(gè)對(duì)象都擁有了鎖鼠次、監(jiān)視器可用。
13.為什么wait(),notify(),notifyAll()方法必須要在同步方法或者同步代碼塊中被調(diào)用芋齿?
因?yàn)閷?duì)象的這些方法要調(diào)用的話腥寇,該線程必須持有該對(duì)象的鎖,調(diào)用完方法后再釋放鎖以便讓其他線程可以得到這個(gè)鎖繼續(xù)操作觅捆,這樣就需要通過同步來實(shí)現(xiàn)赦役。
14.為什么thread的sleep和yield方法是靜態(tài)的?
這是一個(gè)調(diào)用對(duì)象的強(qiáng)制規(guī)范問題栅炒,因?yàn)閟leep和yield只能對(duì)運(yùn)行中的線程起作用掂摔,使用靜態(tài)讓這兩個(gè)方法只能強(qiáng)行獲取當(dāng)前線程對(duì)象并操作术羔,如果不指定靜態(tài)那么可以控制其他非運(yùn)行狀態(tài)的線程執(zhí)行該方法,顯然是不可以的乙漓。
15.如何確保線程安全级历?
這里線程安全指的是線程間共享資源時(shí)不會(huì)錯(cuò)亂。
1:通過synchronized來對(duì)對(duì)象加鎖實(shí)現(xiàn)線程安全叭披;
2:使用線程安全的集合如ConcurrentHashMap來存儲(chǔ)共享數(shù)據(jù)寥殖;
3:使用原子類如AtomicInteger存儲(chǔ)共享數(shù)據(jù);
4:使用鎖涩蜘;
5:使用volatile關(guān)鍵字修飾變量嚼贡,保證線程從堆內(nèi)存讀取數(shù)據(jù),而不是從線程cache讀取數(shù)據(jù)同诫。
16.volatile關(guān)鍵字的作用粤策?
volatile關(guān)鍵字用來修飾變量,被修飾的變量具有多線程下的可見性剩辟、有序性掐场、原子性(保證原子操作,原子操作的組合操作不能保證原子性)
17.同步塊和同步方法哪個(gè)更好贩猎?
同步塊往往對(duì)對(duì)象的鎖能精確到更細(xì)粒度熊户,所以使用同步塊較好。
18.如何創(chuàng)建守護(hù)線程吭服?
新建一個(gè)thread后嚷堡,thread.setDaemon(true)然后再調(diào)用thread.start()就可以了。
19.什么是ThreadLocal艇棕?
線程類中的普通全局變量蝌戒,在多線程的時(shí)候會(huì)有線程不安全的問題,可以使用TheadLocal變量沼琉,各個(gè)線程通過get()北苟、set()方法獲得各自線程的變量。
20.什么是Thread Group打瘪,為什么不建議使用它友鼻?
ThreadGroup這個(gè)類提供了關(guān)于線程組的信息,主要2個(gè)功能是獲取線程組中活躍線程列表闺骚、為線程設(shè)置線程外部異常處理(因?yàn)榫€程run()方法不能拋出異常彩扔,只能打日志)。Java1.5后可以通過setUncaughtExceptionHandler(UncaughtExceptionHandler eh) 來設(shè)置僻爽,ThreadGroup已經(jīng)過時(shí)虫碉,所以不建議使用。
21.什么是Thread Dump線程轉(zhuǎn)儲(chǔ)胸梆?
是一個(gè)JVM活動(dòng)線程的列表敦捧,可以用來分析系統(tǒng)瓶頸死鎖等须板,一般使用JDK自帶的jstack等工具來分析。
22.什么是死鎖DeadLock兢卵,如何分析和避免死鎖逼纸?
兩個(gè)以上的線程永遠(yuǎn)互相阻塞的情況叫做死鎖。
分析死鎖:通過分析Thread Dump查看阻塞的線程以及他們等待的資源济蝉,每個(gè)資源有id,可以根據(jù)資源的id查看哪些線程擁有了它的對(duì)象鎖菠发。
避免死鎖:避免嵌套鎖王滤,在需要的地方使用,避免無期限等待滓鸠。
23.什么是Java Timer類雁乡?
Java Timer類是一個(gè)工具類,可以安排一個(gè)線程在未來的某個(gè)時(shí)間點(diǎn)執(zhí)行糜俗,執(zhí)行一次或者周期執(zhí)行踱稍。TimerTask是一個(gè)實(shí)現(xiàn)了Runnable接口的抽象類,繼承它然后交給Timer對(duì)象安排即可悠抹。
package com.journaldev.threads;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class MyTimerTask extends TimerTask {
@Override
public void run() {
System.out.println("Timer task started at:"+new Date());
completeTask();
System.out.println("Timer task finished at:"+new Date());
}
private void completeTask() {
try {
//assuming it takes 20 secs to complete the task
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String args[]){
TimerTask timerTask = new MyTimerTask();
//running timer task as daemon thread
Timer timer = new Timer(true);
timer.scheduleAtFixedRate(timerTask, 0, 10*1000);
System.out.println("TimerTask started");
//cancel after sometime
try {
Thread.sleep(120000);
} catch (InterruptedException e) {
e.printStackTrace();
}
timer.cancel();
System.out.println("TimerTask cancelled");
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
24.什么是線程池珠月?
線程池用于管理多個(gè)工作線程。Executors是線程池的工廠類可以方便的創(chuàng)建線程池對(duì)象楔敌。
1. newCachedThreadPool 創(chuàng)建一個(gè)可緩存線程池啤挎,如果線程池長(zhǎng)度超過處理需要,可靈活回收空閑線程卵凑,若無可回收庆聘,則新建線程。
2. newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池勺卢,可控制線程最大并發(fā)數(shù)伙判,超出的線程會(huì)在隊(duì)列中等待。
3. newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池黑忱,支持定時(shí)及周期性任務(wù)執(zhí)行宴抚。
4. newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來執(zhí)行任務(wù)杨何,保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行酱塔。
25.?
26.危虱?
27.羊娃?
28.?