對于Thread對象來說,當(dāng)你調(diào)用的是start(),線程會被放到等待隊列,等待CPU調(diào)度,不一定馬上執(zhí)行;無需等待run()方法執(zhí)行完畢,可以直接執(zhí)行下面的代碼;
而調(diào)用的是run()的話,就是當(dāng)做普通的方法調(diào)用,程序還是要順序執(zhí)行的;
新建線程的幾種方式:
實現(xiàn)Runnable接口;里面實現(xiàn)run()方法;
然后把這個實現(xiàn)了Runnable接口的類就新建為一個Thread t = new Thread(new (實現(xiàn)Runnable接口的類)),調(diào)用start()方法即可開始一個線程了。記住,start()只是開啟,然后就會返回,繼續(xù)執(zhí)行start()下面的語句了。。
線程執(zhí)行器:我們可以通過不同的線程執(zhí)行器來實現(xiàn)多線程的執(zhí)行,有以下幾種執(zhí)行器:
ExecutorService exec = Executors.newCachedThreadPool();
ExecutorService exec = Executors.newFixedThreadPool(5);
ExecutorService exec = Executors.newSingleThreadExecutor();
我們可以對比一下這三者的區(qū)別:第一個執(zhí)行會為每一個任務(wù)都創(chuàng)建一個線程,
而第二個則是可以一次性指定要分配多少線程,而第三個則是屬于單線程,會一個線程一個線程的依次執(zhí)行;
休眠:會使得任務(wù)中斷一段時間,相當(dāng)于變相的阻塞了,可以給其他線程制造機會去執(zhí)行;
但是我們不能通過sleep()來試圖控制線程的順序執(zhí)行,而是要考慮用同步控制來實現(xiàn);
讓步:通過使用yield()方法來給線程調(diào)度機制一個暗示:你的工作已經(jīng)完成的差不多了,可以讓別的線程使用CPU了,其功能上跟sleep()其實是差不多的。
后臺線程:指在程序運行的時候在后臺提供一種通用服務(wù)的線程,并且這種線程并不屬于程序總共不可或缺的部分,當(dāng)所有非后臺線程結(jié)束時,程序終止;由后臺線程創(chuàng)建的線程也是后臺線程;
在線程調(diào)用start()之前,調(diào)用setDaemon(true);
實現(xiàn)多線程的另一種方式:通過繼承Thread的方式來實現(xiàn):而且run()方法是放在構(gòu)造函數(shù)里面的,也就是說,當(dāng)初始化一個線程的時候,就自動的開啟了線程,記得run()方法里面一般都是一個while()循環(huán);
加入一個線程:一個線程可以在其他線程之上調(diào)用join()方法,如果某個線程在另一個線程t上調(diào)用t.join();此線程將被掛起,知道目標(biāo)線程t結(jié)束才恢復(fù);
join()方法,你在一個線程中join()了一個線程進來,你就要等待這個線程結(jié)束了,才可以把自己這個線程給結(jié)束掉;join()的底層實現(xiàn)是wait()方法;
同步:Synchronzied;可以用在方法上,也可以用到類上面;
顯式地使用lock對象,先用Lock lock = new ReentrantLock();建出一個鎖對象出來,然后在方法里面,先調(diào)用lock.lock();然后try語句里面是方法體,最后記得要在finally里面加上lock.unlock();這樣就就相當(dāng)于解鎖了。
區(qū)別:可以看到synchronized和 lock相比起來,lock似乎要加上一些try/catch語句才可以,但是,這也是好處之一,比起synchronized,可以多出來處理的過程,讓用戶出現(xiàn)錯誤的可能性降低;
使用原子類也可以實現(xiàn)資源共享的問題,但是原子類一般很少在常規(guī)編程中用到,用于性能調(diào)優(yōu),然后AtomicInteger,AtomicLong等原子類,使用這些的時候,不需要用到synchronized和lock,但是原子類很少用到,所以我們還是用synchronized和lock。
線程的狀態(tài):新建;就緒;阻塞;死亡;
導(dǎo)致阻塞的幾個原因:1,通過調(diào)用sleep()使任務(wù)進入休眠狀態(tài),
2,通過調(diào)用wait()使線程掛起,知道線程得到notify()或notifyAll()消息,
3,任務(wù)再等待某個輸入/輸出完成;
4,任務(wù)視圖在某個對象上調(diào)用其同步控制方法,但是對象鎖不可用,因為另一個任務(wù)已經(jīng)獲取了這個鎖;
中斷:這是一個大學(xué)問呀。。一般的話,我們中斷都是用interrupted(),但是,我們現(xiàn)在說了,用Executor執(zhí)行器可以更好地執(zhí)行了,所以我們?nèi)缛绾卧趫?zhí)行器中中斷線程呢?這也很好辦,用Executor的shutdownNow(),但是,這又是一個問題了,這只是用來中斷所有的線程的,但是我們是想要中斷某一個線層那該怎么辦呢?這就用到了返回式了,通過submit()來啟動任務(wù)的時候,我們就能夠得到返回的類型Future<?>通過這個去調(diào)用calcel()來中斷某個線程。具體等一下碼,現(xiàn)在還要討論的還有一個問題,中斷的線程是否有一些是無法中斷的,判定如下:如果是在sleep()中的線程,那么顯然是可以中斷的,但是對于正在讀取I/O的線程和正在試圖獲取鎖的線程,我們是無法中斷的,而中斷線程就相當(dāng)于拋出了一個異常,方便我們關(guān)閉掉資源。
線程之間的協(xié)作:當(dāng)線程同時運行多個任務(wù)時,我們可以用鎖來同步兩個任務(wù)的行為,同時也可以用wait()和notifyAll()來實現(xiàn)對線程的控制;
wait()就是一種掛起的狀態(tài),當(dāng)你掛起了之后,鎖將被釋放,把空出來的線程,給別人執(zhí)行,而等到被調(diào)用notify()喚醒之后,又會重新獲得之前wait()鎖,如果這個時候鎖正在被使用的話,就要陷入等待了。
wait()跟sleep()之間的區(qū)別:1,在synchronized中,wait()期間對象鎖是釋放的;而sleep()鎖是不會釋放的;
2,可以通過notify(),notifyAll(),或者令時間到期,從wait()中恢復(fù)執(zhí)行;
喚醒的區(qū)別:notify()方法保證的喚醒是指喚醒的是恰當(dāng)?shù)娜蝿?wù),另外,為了使用notify(),你必須等待相同的條件,而對于notifyAll()來說,是否所有的線程都會被喚醒呢?只有當(dāng)notifyAll()因某個特定鎖被調(diào)用時,只有等待這個鎖的任務(wù)才能被喚醒;
除了wait()和notify(),我們還可以顯式地使用lock和Condition對象;使用互斥并允許任務(wù)掛起的基本類是Condition;可以通過在Condition上調(diào)用await來掛起一個任務(wù),通過signal()來通知這個任務(wù),喚醒這個任務(wù),或者調(diào)用signalAll()來喚醒所有在這個Condition上被其自身掛起的任務(wù);與使用notify()相比,signlAll()是更安全的方式;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
lock.unlock();
condition.await();
但是我們要知道顯式的lock對象,相比起wait(),notify()來說,更加復(fù)雜,所以還是建議用回原來的那個wait(),Lock和Condition只有在更加困難的多線程問題才是必需的;
當(dāng)某一個任務(wù)在等待另一個任務(wù)的鎖釋放,而下一個任務(wù)又在等待上一個鎖的釋放,在這樣呈鏈?zhǔn)降难h(huán)里面,直到這個鏈條上的任務(wù)又在等待第一個任務(wù)釋放鎖,得到了一個任務(wù)之間的相互等待的連續(xù)循環(huán);稱為死鎖;
哲學(xué)家就餐問題;經(jīng)典的死鎖問題;
發(fā)生死鎖的四個滿足條件:
1,互斥條件,任務(wù)使用的資源中國至少有一個是不能共享的。
2,至少有一個任務(wù)它必須持有一個資源把那個正在等待獲取另一個被別的任務(wù)持有的資源;
3,資源不能被任務(wù)搶占;
4,必須要有等待循環(huán);
防止死鎖的最容易的方式是破壞第四個條件。