一、線程概述
進程是處于運行過程中的程序呵燕,并且具有一定的獨立功能憾筏,進程是系統(tǒng)進行資源分配和調(diào)度的一個獨立單位钦奋。
進程的三個特征:
- 獨立性
- 動態(tài)性
- 并發(fā)性
線程可以擁有自己的堆棧、程序計數(shù)器袒炉、局部變量旁理,但不擁有系統(tǒng)資源,它與父進程的其它線程共享該進程所擁有的全部資源我磁。
一個線程可以創(chuàng)建和撤銷另一個線程孽文,同一個進程中的多個線程可以并發(fā)執(zhí)行驻襟。
二、線程的創(chuàng)建和啟動
Java使用Thread
類創(chuàng)建線程芋哭,所有的線程對象都必須是Thread
類或其子類的實例沉衣。
繼承Thread創(chuàng)建線程
- 定義Thread類創(chuàng)建線程類,并重寫
run()
方法减牺,表示線程需要完成的任務(wù)豌习,run()
方法為線程執(zhí)行體。 - 創(chuàng)建Thread子類的實例拔疚,即創(chuàng)建了線程實例肥隆。
- 調(diào)用線程對象的start()方法啟動該線程。
public class Test extends Thread {
private int i ;
@Override
public void run(){
for(;i<100;i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) {
new Test().start();
new Test().start();
}
}
以上程序有3個進程稚失,包括主線程栋艳。main方法的方法體代表主線程的線程執(zhí)行體。
- Thread.currentThread()
返回正在執(zhí)行的線程對象
- getName()
返回調(diào)用該方法的線程的名字墩虹。
程序可以通過setName(String name)方法為線程設(shè)置名字嘱巾,通過getName()方法返回名字。默認(rèn)主線程的名字為
main
诫钓,用戶啟動的多個線程的名字依次為Thread-0
旬昭,Thread-1
...
使用繼承Thread類的方法創(chuàng)建的線程類,多個線程之間無法共享線程類的實例變量菌湃。
實現(xiàn)Runnable接口
- 定義Runnable接口的實現(xiàn)類问拘,并重寫run()方法
- 創(chuàng)建Runnable實現(xiàn)類的實例,并以此實例作為Thread的target創(chuàng)建Thread對象惧所。
- 調(diào)用run()方法啟動線程
//實現(xiàn)Runnable接口的類的實例
Test t = new Test();
new Thread(t);
new Thread(t, "name");
Runnable對象僅僅作為Thread對象的target骤坐,Runnable實現(xiàn)類里面的run()方法僅作為線程執(zhí)行體。實際的線程對象依然是Thread的實例下愈,該Thread線程負(fù)責(zé)執(zhí)行其target的run()方法纽绍。
Runnable接口是函數(shù)式接口,Callable接口也是函數(shù)式接口
使用Callable和Future
Java5開始提供Callable
接口势似,提供了一個call()
方法可以作為線程執(zhí)行體拌夏,但比run()
方法更強大:
- call()方法可以有返回值
- 可以聲明拋出異常
Callable對象不能直接作為Thread的target。
java5提供了Future接口代表Callable接口里call()方法的返回值履因。并為Future接口提供了FutureTask實現(xiàn)類障簿。該實現(xiàn)類實現(xiàn)了Runnable接口,可以作為Thread的target栅迄。
Future接口定義的公共方法控制它關(guān)聯(lián)的Callable任務(wù):
- boolean cancel(boolean mayInterruptIfRunning)
- V get()
- V get(long timeout, TimeUnit unit)
- boolean isCancelled()
- boolean isDone()
Callable接口有泛型限制站故,泛型形參與call()方法的返回值類型相同。而且Callable接口是函數(shù)式接口毅舆。
創(chuàng)建并啟動有返回值的接口:
- 創(chuàng)建Callable接口的實現(xiàn)類西篓,并實現(xiàn)call()方法愈腾,再創(chuàng)建Callable實現(xiàn)類的實例。
- 使用FutureTask類包裝Callable對象岂津,封裝了Callable對象的call()方法的返回值顶滩。
- 使用FutureTask對象作為Thread的target創(chuàng)建并啟動新線程。
- 調(diào)用FutureTask對象的get()方法獲得子線程執(zhí)行結(jié)束后的返回值
import java.util.concurrent.FutureTask;
public class Test{
public static void main(String[] args) {
Test test = new Test();
FutureTask<Integer> task = new FutureTask<>( ()->{
int i=0;
for(;i<100;i++){
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
});
new Thread(task).start();
try{
System.out.println(task.get());
}
catch(Exception e){
e.printStackTrace();
}
}
}
創(chuàng)建線程的三種方式對比
可以將Callable
和Runnable
歸并為一類寸爆。
使用實現(xiàn)這兩個接口創(chuàng)建多線程的優(yōu)缺點:
- 還可以繼承其他類
- 多個線程可以共享同一個target對象
- 編程稍微復(fù)雜,如果需要訪問當(dāng)前線程盐欺,則要使用
Thread.currentThread()
方法
使用Thread
方式:
- 不能繼承其他父類
- 編寫簡單
推薦使用
Runnable
和Callable
方式創(chuàng)建多線程
三赁豆、線程的生命周期
新建、就緒冗美、運行魔种、阻塞、死亡
新建和就緒狀態(tài)
new之后就處于新建狀態(tài)粉洼。分配內(nèi)存节预,初始化成員變量的值。
start之后處于就緒狀態(tài)属韧。創(chuàng)建方法調(diào)用棧和程序計數(shù)器安拟。
運行和阻塞狀態(tài)
處于就緒狀態(tài)的進程獲得了cpu就處于運行狀態(tài)。
所有現(xiàn)代的桌面和服務(wù)器操作系統(tǒng)都采用搶占式調(diào)度策略宵喂。(系統(tǒng)分配給線程一段時間)
協(xié)作式調(diào)度策略(線程調(diào)用它的sleep或者yield方法才會停止)
以下情況由就緒變?yōu)樽枞?/p>
- 調(diào)用sleep方法
- 調(diào)用了一個阻塞式io
- 試圖獲得一個同步監(jiān)視器糠赦,但該監(jiān)視器正在被別的線程使用。
- 線程在等待某個通知
- 程序調(diào)用了線程的suspend()方法將線程掛起锅棕。
從阻塞變?yōu)榫途w:
- 調(diào)用sleep()方法的線程經(jīng)過了指定時間
- 線程調(diào)用的阻塞式io方法已經(jīng)返回
- 線程成功獲得了試圖取得的同步監(jiān)視器
- 線程正在等待某個通知時拙泽,其他線程發(fā)出了一個通知
- 處于掛起狀態(tài)的線程被嗲用了resume()恢復(fù)方法
死亡
- run()或者call()方法執(zhí)行完畢
- 線程拋出一個未捕獲的異常或者錯誤
- 直接調(diào)用該線程的stop方法(容易死鎖)
當(dāng)主線程結(jié)束時裸燎,其他線程不受任何影響顾瞻,并不會隨之結(jié)束。一旦子線程啟動起來德绿,他就擁有和主線程一樣的地位荷荤。
調(diào)用線程的isAlive()方法可以返回線程的狀態(tài)。新建和死亡狀態(tài)返回false脆炎。
對于已經(jīng)運行并死亡的線程調(diào)用
start()
方法會引起IllegalThreadException
四梅猿、控制線程
join線程
Thread提供了讓一個線程等待另一個線程完成的方法--join方法。當(dāng)某個程序執(zhí)行流中調(diào)用其他線程的join方法時秒裕,調(diào)用線程將被阻塞袱蚓,直到被join()方法加入的join線程執(zhí)行完為止。
join()方法通常由使用線程的程序調(diào)用几蜻,以將大問題劃分成小問題,每個小問題分配一個線程。當(dāng)所有小問題處理完之后漂辐,再調(diào)用主線程進一步操作歧蕉。
public class Test extends Thread{
//提供帶參構(gòu)造器
public Test(String name){
super(name);
}
@Override
public void run(){
for(int i=0;i<100;i++){
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) throws Exception{
new Test("新線程").start();
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"--"+i);
if(i==10){
Test t = new Test("又一個新線程");
t.start();
//main線程調(diào)用t線程的join方法,main必須等到t執(zhí)行完成之后才能繼續(xù)執(zhí)行
t.join();
}
}
}
}
join方法的三種重載形式摆霉。
- join()
等待被join的線程執(zhí)行完成。
- join(long millis)
等待被join的線程的時間最長為millis毫秒
join(long millis, int nanos)
后臺線程
"Daemon Thread",也稱為"守護線程"或者"精靈線程"蹬敲。
jvm的垃圾回收線程就是后臺線程。
如果所有的前臺線程都已經(jīng)死亡莺戒,后臺線程會自動死亡伴嗡。
調(diào)用Thread對象的setDaemon(true)
方法即可。
isDaemon()
方法判斷指點線程是否是后臺線程从铲。
前臺線程創(chuàng)建的子線程默認(rèn)是前臺線程瘪校,后臺線程創(chuàng)建的子線程默認(rèn)是后臺線程。
setDaemon
必須在start
之前調(diào)用名段。
線程睡眠
讓當(dāng)前線程進入阻塞狀態(tài)阱扬。
- static void sleep(long millis)
- static void sleep(long millis, int nanos)
線程讓步
yield()
方法讓當(dāng)前線程暫停,進入就緒狀態(tài)伸辟。
該方法只是讓當(dāng)前線程暫停一下麻惶,讓系統(tǒng)的線程調(diào)度器重新調(diào)度一次。只有優(yōu)先級與當(dāng)前線程相同或者更改的處于就緒狀態(tài)的線程才會獲得執(zhí)行的機會信夫。
public class Test extends Thread{
//提供帶參構(gòu)造器
public Test(String name){
super(name);
}
@Override
public void run(){
for(int i=0;i<100;i++){
System.out.println(getName()+" "+i);
if (i==20){
Thread.yield();
}
}
}
public static void main(String[] args) {
Test t1 = new Test("高級");
//t1.setPriority(Thread.MAX_PRIORITY);
Test t2 = new Test("低級");
//t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
在多CPU并行的環(huán)境下用踩,yield()方法的功能有時候并不明顯。
sleep和yield方法的區(qū)別:
- sleep()不理會其他線程的優(yōu)先級
- sleep()將線程轉(zhuǎn)入阻塞狀態(tài)忙迁,而yield()轉(zhuǎn)讓就緒狀態(tài)
- sleep()方法聲明拋出了
InterruptedException
脐彩,yield()方法沒有聲明拋出異常。 - sleep()方法有更好的可移植性姊扔,不建議使用yield()方法控制并發(fā)線程的執(zhí)行惠奸。
改變線程的優(yōu)先級
每個線程默認(rèn)的優(yōu)先級與創(chuàng)建它的父線程的優(yōu)先級相同。main線程具有普通優(yōu)先級恰梢。
setPriority(int newPriority),getPriority()佛南。
參數(shù)范圍為1~10.
Thread類有三個靜態(tài)常量:
- MAX_PRIORITY(10)
- MIN_PRIORITY(1)
- NORM_PRIORITY(5)
不同系統(tǒng)的優(yōu)先級不相同,不能很好的和Java的10個優(yōu)先級對應(yīng)嵌言,盡量避免直接指定優(yōu)先級的數(shù)值嗅回。
五、線程同步
同步代碼塊
Java的多線程支持引入了同步監(jiān)視器解決這個問題摧茴,使用同步監(jiān)視器的通用方法是同步代碼塊绵载。格式為:
synchronized(obj){
//同步代碼塊
}
obj是同步監(jiān)視器。線程開始執(zhí)行同步代碼塊之前,必須先獲得對同步監(jiān)視器的鎖定娃豹。
當(dāng)同步代碼塊執(zhí)行完成后焚虱,該線程釋放同步監(jiān)視器的鎖定。
允許使用任何對象作為同步監(jiān)視器懂版。通常推薦可能被并發(fā)訪問的共享資源充當(dāng)同步監(jiān)視器鹃栽。
同步方法
synchronized修飾方法。
對于修飾的實例方法躯畴,無需顯示指定同步監(jiān)視器民鼓,同步方法的同步監(jiān)視器是this
,也就是調(diào)用該方法的對象蓬抄。
通過同步方法可以方便的實現(xiàn)線程安全的類摹察,線程安全的類有如下特征:
該類的對象可以被多個線程安全的訪問
每個線程調(diào)用該對象的任意方法之后都將得到正確的結(jié)果
每個線程調(diào)用該對象的任意方法之后,該對象狀態(tài)依然保持合理狀態(tài)倡鲸。
只對會改變競爭資源的方法進行同步。
如果可變類有兩種運行環(huán)境:單線程環(huán)境和多線程環(huán)境黄娘,則應(yīng)該為該可變類提供兩種版本峭状。線程安全版本和線程不安全版本。
釋放同步監(jiān)視器的鎖定
程序無法顯示釋放逼争,線程會在如下幾種情況釋放對監(jiān)視器的鎖定:
- 當(dāng)前線程的同步方法优床、同步代碼塊執(zhí)行結(jié)束。
- 同步方法誓焦、同步代碼塊遇到return胆敞、break
- 出現(xiàn)error或exception
- 程序執(zhí)行了同步監(jiān)視器對象的wait()方法
如下情況,線程不會釋放同步監(jiān)視器:
- 程序調(diào)用Thread.sleep()杂伟、Thread.yield()方法
- 其他線程調(diào)用該線程的suspend()方法將該線程掛起
同步鎖
Java5開始移层。Lock對象。
Lock赫粥、ReadWriteLock是Java5提供的兩個根接口观话,分別有ReentrantLock和ReentrantReadWriteLock實現(xiàn)類。
Java8新增了StampedLock類越平,在大多數(shù)場景中可以代替ReentrantReadWriteLock频蛔。
ReentrantReadWriteLock為讀寫操作提供了三種鎖模式:Writing、ReadingOptimistic秦叛、Reading晦溪。
import java.util.concurrent.locks.ReentrantLock;
class Test{
private final ReentrantLock lock = new ReentrantLock();
public void m(){
//加鎖
lock.lock();
try{
//代碼塊
}
finally{
lock.unlock();
}
}
}
lock提供了同步方法和同步代碼塊沒有的其他功能。包括用于非塊結(jié)構(gòu)的tryLock()方法挣跋,試圖獲取可中斷鎖的lockInterruptibly()方法三圆,還有獲取超時失效鎖的try(long, TimeUnit)方法。
死鎖
Thread類的suspend方法很容易造成死鎖,Java不再推薦使用該方法嫌术。
六哀澈、線程通信
傳統(tǒng)的線程通信
wait()、notify()度气、notifyAll()
- 對于使用synchronized修飾的方法割按,該類的默認(rèn)實例(this)就是同步監(jiān)視器,可以在同步方法中直接調(diào)用這三個方法磷籍。
- 對于使用synchronized修飾的同步代碼塊适荣,同步監(jiān)視器是synchronized后括號里的對象。必須使用該對象調(diào)用這三個方法院领。
使用condition控制線程通信
Lock對線保證同步時弛矛,可以使用Condition類來保持協(xié)調(diào)。
Condition實例被綁定在一個Lock對象上比然。要獲得特定Lock實例的Condition實例丈氓,調(diào)用Lock對象的newCondition()方法即可。
Condition類提供了如下三個方法:
- await()
- signal()
- signalAll()
使用阻塞隊列(BlockingQueue)控制線程通信
Queue的子接口强法,主要用途作為線程同步的工具万俗。BlockingQueue具有一個特征:當(dāng)生產(chǎn)者線程試圖向BlockingQueue中放入元素時,如果該隊列已滿饮怯,則該線程被阻塞闰歪;當(dāng)消費者從中取出元素時,如果該隊列已空蓖墅,則該線程被阻塞库倘。
提供的支持阻塞的方法:
- put(E e)
- take()
隊列的方法:
- 在隊列尾部放入元素:add(E e)、offer(E e)论矾、put(E e)教翩,隊列已滿,這三個方法分別會拋出異常贪壳、返回false迂曲、阻塞隊列
- 在頭部刪除并返回刪除的元素,remove()寥袭、poll()路捧、take(),隊列已空传黄,這三個方法分別會拋出異常杰扫、返回false、阻塞隊列
- 頭部取出但不刪除元素膘掰,element()章姓、peek()佳遣,隊列已空,這兩個方法會拋出異常凡伊、返回false
表頭 | 拋出異常 | 不同返回值 | 阻塞線程 | 指定超時時間 |
---|---|---|---|---|
隊尾插入元素 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
隊頭刪除元素 | remove() | poll() | take() | poll(time,unit) |
獲取零渐、不刪除 | element() | peek() | 無 | 無 |

七、線程池
Java8改進的線程池
Java5開始內(nèi)建支持線程池系忙,新增Executors工廠類產(chǎn)生線程池诵盼,靜態(tài)方法:
- newCachedThreadPool()
創(chuàng)建一個具有緩存功能的線程池
- newFIxedThreadPoll(int nThreads)
創(chuàng)建一個可重用的、固定線程數(shù)量的線程池
- newSingleThreadExecutor()
創(chuàng)建一個只有單線程的線程池
- newScheduledThreadPoll(int corePoolSize)
指定延遲后執(zhí)行線程任務(wù)
- newSingleThreadScheduledExecutor()
指定延遲
- ExecutorService newWorkStealingPool(int parallelism)
創(chuàng)建持有足夠線程的線程池來支持給定的并行級別银还,該方法還會使用多個隊列來減少競爭风宁。
- ExecutorService newWorkStealingPool()
相當(dāng)于前一個方法的簡化版,如果有4個cpu蛹疯,相當(dāng)于參數(shù)為4
Java8新增的ForkJoinPoll方法
八戒财、線程相關(guān)類
ThreadLocal
提供了三個public方法:
- T get()
- void remove()
- void set(T value)
它將需要并發(fā)訪問的資源復(fù)制多份,每個線程擁有一個資源捺弦,每個線程都擁有自己的資源副本饮寞。
不能代替同步機制。同步機制是為了同步多個線程對相同資源的并發(fā)訪問列吼,是多個線程之間對共享資源的競爭幽崩;而ThreadLocal是為了隔離多個線程的數(shù)據(jù)共享,從根本上避免多個線程之間對共享資源的競爭冈欢,不需要對多個線程同步。
通常建議:多個線程需要共享資源盈简,以達到線程的通信功能凑耻,使用同步機制;僅僅需要隔離多個線程之間的共享沖突柠贤,使用ThreadLocal
包裝線程不安全的類
Colletions提供的類方法:
- static <T> Collection<T> synchronizedCollection(Collection<T> c)
- static <T> List<T> synchronizedList(List<T> list)
- static <K,V> Map<K,V> synchronizedMap(Map<K,V> map)
- static <T> Set<T> synchronizedSet(Set<T> s)
- static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
- static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> m)
線程安全的集合類
java.util.concurrent
包下面提供了大量支持高效并發(fā)訪問的集合接口和實現(xiàn)類香浩。