JAVA多線程筆記

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)行芬膝、阻塞望门、終止。

  1. 新建狀態(tài)(New):新創(chuàng)建了一個(gè)線程對(duì)象锰霜。
  2. 就緒狀態(tài)(Runnable):線程對(duì)象創(chuàng)建后筹误,其他線程調(diào)用了該對(duì)象的start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中癣缅,變得可運(yùn)行厨剪,等待獲取CPU的使用權(quán)。
  3. 運(yùn)行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU友存,執(zhí)行程序代碼祷膳。
  4. 阻塞狀態(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ì)釋放持有的鎖)
  5. 結(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ì):

  1. 適合多個(gè)相同的程序代碼的線程去處理同一個(gè)資源

  2. 可以避免java中的單繼承的限制

  3. 增加程序的健壯性美莫,代碼可以被多個(gè)線程共享页眯,代碼和數(shù)據(jù)獨(dú)立

  4. 線程池只能放入實(shí)現(xiàn)Runable或callable類線程,不能直接放入繼承Thread的類

線程同步

synchronized關(guān)鍵字

synchronized關(guān)鍵字的作用域有三種:

  1. 在實(shí)例方法前面加上synchronized關(guān)鍵字厢呵,synchronized aMethod(){}可以防止多個(gè)線程同時(shí)訪問這個(gè)對(duì)象的synchronized方法窝撵。

  2. 在類方法前面加上synchronized關(guān)鍵字,synchronized static bMethod(){}可以防止多個(gè)線程同時(shí)訪問這個(gè)synchronized方法述吸。

  3. 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)的方法

  1. sleep(long millis): 在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行)

  2. join():指等待t線程終止帆卓。

join是Thread類的一個(gè)方法巨朦,啟動(dòng)線程后直接調(diào)用,即join()的作用是:“等待該線程終止”剑令,這里需要理解的就是該線程是指的主線程等待子線程的終止糊啡。也就是在子線程調(diào)用了join()方法后面的代碼,只有等到子線程結(jié)束了才能執(zhí)行吁津。

  1. 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)度程序再次選中。

  1. setPriority(): 更改線程的優(yōu)先級(jí)褥紫。

線程優(yōu)先級(jí)最大是10姜性,最小是1,默認(rèn)是5髓考。
MIN_PRIORITY = 1
NORM_PRIORITY = 5
MAX_PRIORITY = 10

  1. interrupt():讓出入阻塞的線程拋出一個(gè)中斷信號(hào)
    不要以為它是中斷某個(gè)線程污抬!它只是線線程發(fā)送一個(gè)中斷信號(hào),讓線程在無(wú)限等待時(shí)(如死鎖時(shí))能拋出绳军,從而結(jié)束線程印机,但是如果你try了這個(gè)異常,那么這個(gè)線程還是不會(huì)中斷的门驾!
    注意如果線程沒有阻塞射赛,那么是不會(huì)拋出InterruptedException的。

  2. setDaemon():指明某個(gè)線程是守護(hù)程序線程

守護(hù)程序線程作為在程序中創(chuàng)建的后臺(tái)線程奶是,如果程序中所有的非守護(hù)線程執(zhí)行完成楣责,那么程序就會(huì)推出竣灌。例如垃圾回收的線程等也是后臺(tái)線程。

  1. 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):

  1. 他們都是在多線程的環(huán)境下,都可以在程序的調(diào)用處阻塞指定的毫秒數(shù)凌蔬,并返回露懒。
  2. 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):

  1. Thread類的方法:sleep(),yield()等
    Object的方法:wait()和notify()等
  2. 每個(gè)對(duì)象都有一個(gè)鎖來控制同步訪問砾淌。Synchronized關(guān)鍵字可以和對(duì)象的鎖交互,來實(shí)現(xiàn)線程的同步谭网。
    sleep方法沒有釋放鎖汪厨,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法愉择。
  3. wait劫乱,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用织中,而sleep可以在任何地方使用
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市衷戈,隨后出現(xiàn)的幾起案子狭吼,更是在濱河造成了極大的恐慌,老刑警劉巖殖妇,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件刁笙,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拉一,警方通過查閱死者的電腦和手機(jī)采盒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔚润,“玉大人磅氨,你說我怎么就攤上這事〉站溃” “怎么了烦租?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)除盏。 經(jīng)常有香客問我叉橱,道長(zhǎng),這世上最難降的妖魔是什么者蠕? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任窃祝,我火速辦了婚禮,結(jié)果婚禮上踱侣,老公的妹妹穿的比我還像新娘粪小。我一直安慰自己,他們只是感情好抡句,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布探膊。 她就那樣靜靜地躺著,像睡著了一般待榔。 火紅的嫁衣襯著肌膚如雪逞壁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天锐锣,我揣著相機(jī)與錄音腌闯,去河邊找鬼。 笑死雕憔,一個(gè)胖子當(dāng)著我的面吹牛姿骏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播橘茉,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼工腋,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼姨丈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起擅腰,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蟋恬,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后趁冈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體歼争,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年渗勘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了沐绒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡旺坠,死狀恐怖乔遮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情取刃,我是刑警寧澤蹋肮,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布,位于F島的核電站璧疗,受9級(jí)特大地震影響坯辩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜崩侠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一漆魔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧却音,春花似錦改抡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)裸删。三九已至八拱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間涯塔,已是汗流浹背肌稻。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留匕荸,地道東北人爹谭。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像榛搔,于是被迫代替她去往敵國(guó)和親诺凡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子东揣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容