1 簡(jiǎn)述
1.1 線程機(jī)制
Java使用的是搶占式的線程機(jī)制魂迄,調(diào)度機(jī)制周期性地切換上下文,切換線程惋耙,從而為每個(gè)線程提供時(shí)間片捣炬⌒懿看似同時(shí)執(zhí)行,其實(shí)是不停地切換湿酸。在這種機(jī)制下婿屹,一個(gè)線程的阻塞不會(huì)導(dǎo)致整個(gè)進(jìn)程阻塞。
優(yōu)先級(jí)較低的線程僅僅是執(zhí)行的頻率較低推溃,不會(huì)得不到執(zhí)行昂利。
要實(shí)現(xiàn)線程行為,你必須顯示地將一個(gè)任務(wù)(Runnable)附著到線程(Thread)上铁坎。Thread類只是驅(qū)動(dòng)賦予它的任務(wù)Runnable蜂奸。
1.2 阻塞(se第四聲)
程序中一個(gè)任務(wù)因?yàn)樵摮绦蚩刂品秶獾囊蛩兀l件通常是IO),而不能繼續(xù)執(zhí)行厢呵,這個(gè)任務(wù)線程被阻塞了窝撵。
2 Executor 線程池
用new Thread()的方式不利于性能優(yōu)化,所以采用線程池替代襟铭,達(dá)到線程的復(fù)用碌奉。首選CachedThreadPool回收舊線程停止創(chuàng)建新線程。
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Runnable() {
@Override
public void run() {
}
});
Runnable設(shè)返回值寒砖,需要實(shí)現(xiàn)Callable接口call()方法赐劣,并且必須使用ExecutorService.submit()方法調(diào)用。
使用 Future.get() 獲取返回值哩都。
例如:
public class CallableAndFuture {
static class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
return "Hello world";
}
}
static class MyThread2 implements Runnable {
@Override
public void run() {
}
}
public static void main(String[] args) {
ExecutorService threadPool = Executors.newSingleThreadExecutor();
Future<String> future = threadPool.submit(new MyThread());
try {
System.out.println(future.get());
} catch (Exception e) {
} finally {
threadPool.shutdown();
}
}
}
3 線程安全
3.1 實(shí)質(zhì)
線程安全其實(shí)就是解決共享資源競(jìng)爭(zhēng)的問(wèn)題魁兼。
解決方法是:當(dāng)一個(gè)資源被一個(gè)任務(wù)使用時(shí),加鎖漠嵌。
解鎖的時(shí)候咐汞,下一個(gè)要使用資源的任務(wù)并沒(méi)有按照排隊(duì)的方式按次序來(lái)獲取,而是通過(guò)競(jìng)爭(zhēng)獲取資源儒鹿』海可以通過(guò)yield()和setPriority()來(lái)給線程調(diào)度器提供建議,這效果取決于具體平臺(tái)和JVM實(shí)現(xiàn)约炎。
3.2 synchronized加鎖
對(duì)于某個(gè)特定對(duì)象植阴,其所有的synchronized方法共享同一個(gè)鎖。
一個(gè)任務(wù)可以多次獲得對(duì)象的鎖圾浅。
判斷是否應(yīng)該加鎖:如果你正在寫一個(gè)變量掠手,這個(gè)變量接下來(lái)將被另一個(gè)線程讀取;你正在讀一個(gè)上一次已經(jīng)被另一個(gè)線程寫過(guò)的變量狸捕。以上兩種情況都必須使用同步喷鸽。并且,讀寫線程都必須用相同的監(jiān)視器鎖同步府寒。
3.3 使用顯式的Lock對(duì)象加鎖
除了synchronized方式加鎖之外魁衙,還可以用Lock對(duì)象:
private Lock lock = new ReentrantLock();//重入鎖
......
lock.lock();
try{
......
return ...;//在try中return確保unlock()不會(huì)過(guò)早發(fā)生
}finally{
lock.unlock();
}
和synchronized的區(qū)別:tryLock()
- 可以嘗試著獲取鎖报腔,最終獲取失敗。
- 可以嘗試著獲取鎖一段時(shí)間剖淀,然后放棄獲取纯蛾。
如果其他的線程已經(jīng)獲取了這個(gè)鎖,你可以決定離開(kāi)纵隔,去執(zhí)行其他一些事翻诉,而不是等待直至這個(gè)鎖釋放。提供了比Synchronized更細(xì)粒度的控制力捌刮。(例如可以釋放當(dāng)前鎖之前碰煌,捕獲下一節(jié)點(diǎn)的鎖)
4 volatile
volatile關(guān)鍵字主要提供2種作用:
- 可見(jiàn)性:當(dāng)使用volatile關(guān)鍵字去修飾變量的時(shí)候,所有線程都會(huì)直接讀取該變量并且不緩存它绅作。這就確保了線程讀取到的變量是同內(nèi)存中是一致的
- 禁止指令重排序:應(yīng)用在標(biāo)志位
5線程本地存儲(chǔ)
防止共享資源產(chǎn)生沖突的第二種方式是根除變量的共享芦圾,使用ThreadLocal。
6 終結(jié)任務(wù)
線程的各個(gè)狀態(tài)
停止線程主要使用標(biāo)志位俄认,在run()方法中加以判斷个少,是否要執(zhí)行。
- 自己定義一個(gè)volatile boolean的標(biāo)志位
- 使用interrupt()方法眯杏,但是這個(gè)方法也是打個(gè)停止的標(biāo)志夜焦,并不是真正的停止線程,還是要在run()方法中調(diào)用Thread.interrupted()方法判斷岂贩。
7 線程之間的協(xié)作
yield()方法:調(diào)用的線程放棄cpu時(shí)間讓給別的線程茫经。
join()方法:調(diào)用的線程先執(zhí)行。
7.1 wait()
wait()是object的方法萎津,它會(huì)釋放鎖卸伞,而sleep()和yield()不會(huì)釋放。
因此在該對(duì)象(未鎖定的)中的其他synchronized方法可以在wait()期間被調(diào)用锉屈。
只能在同步控制方法或者同步控制塊里調(diào)用wait()瞪慧、notify()、notifyAll()部念,因?yàn)橐僮鲗?duì)象的鎖。
wait()必須用一個(gè)檢查感興趣的條件的while循環(huán)包圍氨菇,本質(zhì)是檢查感興趣的條件儡炼,并在條件不滿足的情況下返回到wait()方法中。
Thread1:
synchronized(sharedMonitor){
<setup condition for Thread2>
sharedMonitor.notify();
}
//錯(cuò)誤的使用:時(shí)機(jī)太晚查蓉,錯(cuò)失信號(hào)乌询,盲目進(jìn)入wait(),產(chǎn)生死鎖豌研。
Thread2:
while(someConditon){
synchronized(sharedMonitor){
sharedMonitor.wait();
}
}
//正確的使用:防止在someCondition變量上產(chǎn)生競(jìng)爭(zhēng)條件
Thread2:
synchronized(sharedMonitor){
while(someConditon){
sharedMonitor.wait();
}
}
7.2 notify()和notifyAll()
notify():在眾多等待同一個(gè)鎖的線程中妹田,只有一個(gè)會(huì)被喚醒去獲取鎖唬党。
notifyAll():在眾多等待同一個(gè)鎖的線程中,全部會(huì)被喚醒去共同競(jìng)爭(zhēng)鎖鬼佣。
兩個(gè)同樣最終只有一個(gè)線程能獲取到鎖驶拱。
可能有多個(gè)線程在某單個(gè)對(duì)象上處于wait狀態(tài),因此調(diào)用notifyAll()比notify()更安全晶衷。
只有一個(gè)線程實(shí)際處于wait()狀態(tài)蓝纲,這時(shí)可以用notify()代替notifyAll()優(yōu)化;
如果有多個(gè)線程在等待不同條件晌纫,使用notify()就不能知道是否喚醒了恰當(dāng)?shù)娜蝿?wù)税迷。
8 死鎖
某個(gè)線程在等待另一個(gè)線程,而后者又在等待別的線程锹漱,這樣一直下去箭养,直到這個(gè)鏈上的線程又在等待第一個(gè)線程釋放鎖。這得到了一個(gè)狀態(tài):線程之間相互等待的連續(xù)循環(huán)哥牍,沒(méi)有哪個(gè)線程能繼續(xù)毕泌。這被稱為死鎖
死鎖的4個(gè)條件:
- 互斥條件(資源的特點(diǎn)):線程使用的資源中至少有一個(gè)是不能共享的。
- 占用請(qǐng)求條件(單個(gè)線程的特點(diǎn)):至少有一個(gè)線程砂心,它持有一個(gè)資源懈词,并且等待獲取另一個(gè)當(dāng)前被別的線程持有的資源。
- 不可剝奪條件(單個(gè)線程的特點(diǎn)):已持有的資源不能被別的線程搶占辩诞,而它(持有資源的線程)自己也不會(huì)主動(dòng)去搶別的線程的資源坎弯。
- 循環(huán)等待條件(多個(gè)線程形成的特點(diǎn)):必須有循環(huán)等待。
解決:讓一方先放棄資源作出讓步译暂。