JAVA多線程筆記
線程的基本概念
進(jìn)程:每個(gè)進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(進(jìn)程上下文)蹋嵌,進(jìn)程間的切換會(huì)有較大的開銷暇屋,一個(gè)進(jìn)程包含1–n個(gè)線程加匈。(進(jìn)程是資源分配的最小單位)
線程:同一類線程共享代碼和數(shù)據(jù)空間炫隶,每個(gè)線程有獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(PC)序愚,線程切換開銷小。(線程是cpu調(diào)度的最小單位)
線程的狀態(tài)
線程和進(jìn)程一樣分為五個(gè)階段:創(chuàng)建等限、就緒、運(yùn)行芬膝、阻塞望门、終止。
- 新建狀態(tài)(New):新創(chuàng)建了一個(gè)線程對(duì)象锰霜。
- 就緒狀態(tài)(Runnable):線程對(duì)象創(chuàng)建后筹误,其他線程調(diào)用了該對(duì)象的start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中癣缅,變得可運(yùn)行厨剪,等待獲取CPU的使用權(quán)。
- 運(yùn)行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU友存,執(zhí)行程序代碼祷膳。
- 阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因?yàn)槟撤N原因放棄CPU使用權(quán),暫時(shí)停止運(yùn)行屡立。直到線程進(jìn)入就緒狀態(tài)直晨,才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)。阻塞的情況分三種:
(一)膨俐、等待阻塞:運(yùn)行的線程執(zhí)行wait()方法勇皇,JVM會(huì)把該線程放入等待池中。(wait會(huì)釋放持有的鎖)
(二)焚刺、同步阻塞:運(yùn)行的線程在獲取對(duì)象的同步鎖時(shí)敛摘,若該同步鎖被別的線程占用,則JVM會(huì)把該線程放入鎖池中乳愉。
(三)兄淫、其他阻塞:運(yùn)行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請(qǐng)求時(shí)蔓姚,JVM會(huì)把該線程置為阻塞狀態(tài)拖叙。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)赂乐、或者I/O處理完畢時(shí)薯鳍,線程重新轉(zhuǎn)入就緒狀態(tài)。(注意,sleep是不會(huì)釋放持有的鎖) - 結(jié)束狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期挖滤。
Java中多線程的實(shí)現(xiàn)
在java中要想實(shí)現(xiàn)多線程崩溪,有兩種手段,一種是繼承Thread類斩松,另外一種是實(shí)現(xiàn)接口.(Runable接口和Callable接口伶唯,并與Future、線程池結(jié)合使用)惧盹。在Java中乳幸,推薦使用線程池方式實(shí)現(xiàn)多線程。
繼承Thread實(shí)現(xiàn)
以下是一個(gè)簡(jiǎn)單的例子
class MyThread extends Thread {
@Override
public void run() {
//TO DO sth.
}
}
開啟線程钧椰,調(diào)用線程對(duì)象的start方法即可粹断,注意不是調(diào)用run方法,調(diào)用run方法也行嫡霞,但是就不是新開線程的方式運(yùn)行了瓶埋,而是普通的方法調(diào)用。真正是調(diào)用start() 方法诊沪。
public static void main(String[] args)
{
new MyThread().start();
}
實(shí)現(xiàn)java.lang.Runnable接口
先說一下java.lang.Runnable吧养筒,它是一個(gè)接口,在它里面只聲明了一個(gè)run()方法:
public interface Runnable {
public abstract void run();
}
采用Runnable也是非常常見的一種端姚,我們只需要重寫run方法即可晕粪。下面也來看個(gè)實(shí)例。
class MyThread implements Runnable {
@Override
public void run() {
//TO DO sth.
}
}
在啟動(dòng)的多線程的時(shí)候渐裸,需要先通過Thread類的構(gòu)造方法Thread(Runnable target) 構(gòu)造出對(duì)象兵多,然后調(diào)用Thread對(duì)象的start()方法來運(yùn)行多線程代碼。
public static void main(String[] args) {
new Thread(new MyThread()).start();
}
實(shí)現(xiàn)java.util.concurrent.Callable接口
Callable位于java.util.concurrent包下橄仆,它也是一個(gè)接口剩膘,在它里面也只聲明了一個(gè)方法,只不過這個(gè)方法叫做call():
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
那么怎么使用Callable呢盆顾?一般情況下是配合ExecutorService來使用的怠褐,也可以結(jié)合Thread使用。由于需要返回和取消您宪,因此還需要一個(gè)參數(shù)來傳遞狀態(tài)奈懒。
Future
Future就是對(duì)于具體的Runnable或者Callable任務(wù)的執(zhí)行結(jié)果進(jìn)行取消、查詢是否完成宪巨、獲取結(jié)果磷杏。必要時(shí)可以通過get方法獲取執(zhí)行結(jié)果,該方法會(huì)阻塞直到任務(wù)返回結(jié)果捏卓。
Future類位于java.util.concurrent包下极祸,它是一個(gè)接口:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
因?yàn)镕uture只是一個(gè)接口,所以是無(wú)法直接用來創(chuàng)建對(duì)象使用的,因此就有了下面的FutureTask遥金。
FutureTask
FutureTask實(shí)現(xiàn)了RunnableFuture接口浴捆,我們先來看一下FutureTask的實(shí)現(xiàn):
public class FutureTask<V> implements RunnableFuture<V>
而RunnableFuture又實(shí)現(xiàn)了Runnable, Future接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
出RunnableFuture繼承了Runnable接口和Future接口,而FutureTask實(shí)現(xiàn)了RunnableFuture接口稿械。所以它既可以作為Runnable被線程執(zhí)行选泻,又可以作為Future得到Callable的返回值。
所以實(shí)現(xiàn)方式
class MyThread implements Callable {
@Override
public Object call() throws Exception { System.out.println(Thread.currentThread().getName());
return 1;
}
}
調(diào)用可以方式1:使用線程池
public void testCallable1() throws ExecutionException, InterruptedException {
ExecutorService pool = Executors.newSingleThreadExecutor();
MyThread task = new MyThread();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
pool.submit(futureTask);
System.out.println(futureTask.get());
}
調(diào)用方式2:使用線程對(duì)象
MyThread task = new MyThread();
FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
Thread thread = new Thread(futureTask).start();
System.out.println(futureTask.get());
實(shí)現(xiàn)線程方式的比較
實(shí)現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢(shì):
適合多個(gè)相同的程序代碼的線程去處理同一個(gè)資源
可以避免java中的單繼承的限制
增加程序的健壯性美莫,代碼可以被多個(gè)線程共享页眯,代碼和數(shù)據(jù)獨(dú)立
線程池只能放入實(shí)現(xiàn)Runable或callable類線程,不能直接放入繼承Thread的類
線程同步
synchronized關(guān)鍵字
synchronized關(guān)鍵字的作用域有三種:
在實(shí)例方法前面加上synchronized關(guān)鍵字厢呵,synchronized aMethod(){}可以防止多個(gè)線程同時(shí)訪問這個(gè)對(duì)象的synchronized方法窝撵。
在類方法前面加上synchronized關(guān)鍵字,synchronized static bMethod(){}可以防止多個(gè)線程同時(shí)訪問這個(gè)synchronized方法述吸。
synchronized關(guān)鍵字還可以用于方法中的某個(gè)區(qū)塊中,表示只對(duì)這個(gè)區(qū)塊的資源實(shí)行互斥訪問锣笨。
值得注意的是蝌矛,synchronized關(guān)鍵字是不能繼承的,也就是說错英,基類的方法synchronized f(){} 在繼承類中并不自動(dòng)是synchronized f(){}入撒,而是變成了f(){}。繼承類需要你顯式的指定它的某個(gè)方法為synchronized方法椭岩。
并發(fā)包的lock工具
在JavaSE5.0中新增了一個(gè)java.util.concurrent包來支持同步茅逮。
ReentrantLock
ReentrantLock類是可重入、互斥判哥、實(shí)現(xiàn)了Lock接口的鎖献雅, 它與使用synchronized方法和快具有相同的基本行為和語(yǔ)義,并且擴(kuò)展了其能力
ReenreantLock類的常用方法有:
ReentrantLock() : 創(chuàng)建一個(gè)ReentrantLock實(shí)例
lock() : 獲得鎖
unlock() : 釋放鎖
ReentrantLock lock = new ReentrantLock();
lock.lock();
int syncNum = 1;
try {
syncNum++ ;
} finally {
lock.unlock();
}
注:ReentrantLock()還有一個(gè)可以創(chuàng)建公平鎖的構(gòu)造方法塌计,但由于會(huì)大幅度降低程序運(yùn)行效率挺身,不推薦使用 。
其他的一些方法實(shí)現(xiàn)同步
(1)原子變量實(shí)現(xiàn)線程同步
在java的util.concurrent.atomic包中提供了創(chuàng)建了原子類型變量的工具類锌仅,使用該類可以簡(jiǎn)化線程同步章钾。
(2)使用volatile關(guān)鍵字
(3)使用ThreadLocal避免并發(fā)問題
(4)util.concurrent下實(shí)現(xiàn)了很多線程安全的集合類,可以使用
同步可能產(chǎn)生的問題
線程僵死
如果一個(gè)線程等待一個(gè)永遠(yuǎn)不會(huì)釋放的鎖热芹,那么線程就會(huì)一直無(wú)法運(yùn)行贱傀。
死鎖
只要您擁有多個(gè)進(jìn)程或者線程,而且它們要爭(zhēng)用對(duì)多個(gè)鎖的獨(dú)占訪問伊脓,那么就有可能發(fā)生死鎖府寒。如果有一組進(jìn)程或線程,其中每個(gè)都在等待一個(gè)只有其它進(jìn)程或線程才可以執(zhí)行的操作,那么就稱它們被死鎖了椰棘。
性能問題
同步由于需要獲得鎖才能運(yùn)行纺棺,如果多個(gè)線程競(jìng)爭(zhēng),那么就會(huì)導(dǎo)致性能下降邪狞。但是需要保證線程同步的正確性祷蝌,再進(jìn)行鎖的優(yōu)化。
線程相關(guān)的方法
sleep(long millis): 在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行)
join():指等待t線程終止帆卓。
join是Thread類的一個(gè)方法巨朦,啟動(dòng)線程后直接調(diào)用,即join()的作用是:“等待該線程終止”剑令,這里需要理解的就是該線程是指的主線程等待子線程的終止糊啡。也就是在子線程調(diào)用了join()方法后面的代碼,只有等到子線程結(jié)束了才能執(zhí)行吁津。
- yield():暫停當(dāng)前正在執(zhí)行的線程對(duì)象棚蓄,并執(zhí)行其他線程。
Thread.yield()方法作用是:暫停當(dāng)前正在執(zhí)行的線程對(duì)象碍脏,并執(zhí)行其他線程梭依。
yield()應(yīng)該做的是讓當(dāng)前運(yùn)行線程回到可運(yùn)行狀態(tài),以允許具有相同優(yōu)先級(jí)的其他線程獲得運(yùn)行機(jī)會(huì)典尾。因此役拴,使用yield()的目的是讓相同優(yōu)先級(jí)的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。但是钾埂,實(shí)際中無(wú)法保證yield()達(dá)到讓步目的河闰,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中。
- setPriority(): 更改線程的優(yōu)先級(jí)褥紫。
線程優(yōu)先級(jí)最大是10姜性,最小是1,默認(rèn)是5髓考。
MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10
interrupt():讓出入阻塞的線程拋出一個(gè)中斷信號(hào)
不要以為它是中斷某個(gè)線程污抬!它只是線線程發(fā)送一個(gè)中斷信號(hào),讓線程在無(wú)限等待時(shí)(如死鎖時(shí))能拋出绳军,從而結(jié)束線程印机,但是如果你try了這個(gè)異常,那么這個(gè)線程還是不會(huì)中斷的门驾!
注意如果線程沒有阻塞射赛,那么是不會(huì)拋出InterruptedException的。setDaemon():指明某個(gè)線程是守護(hù)程序線程
守護(hù)程序線程作為在程序中創(chuàng)建的后臺(tái)線程奶是,如果程序中所有的非守護(hù)線程執(zhí)行完成楣责,那么程序就會(huì)推出竣灌。例如垃圾回收的線程等也是后臺(tái)線程。
- Obj.wait()秆麸,與Obj.notify()
Obj.wait()初嘹,與Obj.notify()必須要與synchronized(Obj)一起使用,也就是wait,與notify是針對(duì)已經(jīng)獲取了Obj鎖進(jìn)行操作沮趣。
從語(yǔ)法角度來說就是Obj.wait(),Obj.notify必須在synchronized(Obj){…}語(yǔ)句塊內(nèi)屯烦。從功能上來說wait就是說線程在獲取對(duì)象鎖后,主動(dòng)釋放對(duì)象鎖房铭,同時(shí)本線程休眠驻龟。直到有其它線程調(diào)用對(duì)象的notify()喚醒該線程,才能繼續(xù)獲取對(duì)象鎖缸匪,并繼續(xù)執(zhí)行翁狐。
wait和sleep區(qū)別
共同點(diǎn):
- 他們都是在多線程的環(huán)境下,都可以在程序的調(diào)用處阻塞指定的毫秒數(shù)凌蔬,并返回露懒。
- wait()和sleep()都可以通過interrupt()方法 打斷線程的暫停狀態(tài) ,從而使線程立刻拋出InterruptedException砂心。
如果線程A希望立即結(jié)束線程B懈词,則可以對(duì)線程B對(duì)應(yīng)的Thread實(shí)例調(diào)用interrupt方法。如果此刻線程B正在wait/sleep /join计贰,則線程B會(huì)立刻拋出InterruptedException钦睡,在catch() {} 中直接return即可安全地結(jié)束線程蒂窒。
需要注意的是躁倒,InterruptedException是線程自己從內(nèi)部拋出的,并不是interrupt()方法拋出的洒琢。對(duì)某一線程調(diào)用 interrupt()時(shí)秧秉,如果該線程正在執(zhí)行普通的代碼,那么該線程根本就不會(huì)拋出InterruptedException衰抑。但是象迎,一旦該線程進(jìn)入到 wait()/sleep()/join()后,就會(huì)立刻拋出InterruptedException 呛踊。
不同點(diǎn):
- Thread類的方法:sleep(),yield()等
Object的方法:wait()和notify()等 - 每個(gè)對(duì)象都有一個(gè)鎖來控制同步訪問砾淌。Synchronized關(guān)鍵字可以和對(duì)象的鎖交互,來實(shí)現(xiàn)線程的同步谭网。
sleep方法沒有釋放鎖汪厨,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法愉择。 - wait劫乱,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用织中,而sleep可以在任何地方使用