多線程-Java瘋狂講義筆記

線程與進(jìn)程

1.進(jìn)程是處于運(yùn)行過程中的程序哀托,并且具有一定的獨(dú)立功能,其是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位,它可以擁有自己獨(dú)立的資源界拦,每一個(gè)都擁有自己的私有的地址空間。進(jìn)程有獨(dú)立性梗劫、動(dòng)態(tài)性寞奸、并發(fā)性特征。
2.線程是進(jìn)程的組成部分在跳,線程可以擁有自己的堆棧枪萄、自己的程序計(jì)數(shù)器和自己的局部變量,但不用有系統(tǒng)資源猫妙,它與父進(jìn)程的其他線程共享該進(jìn)程所擁有的全部資源瓷翻。

線程的創(chuàng)建和啟動(dòng)

繼承Thread類創(chuàng)建線程類

1.定義Thread的子類,并重寫該類的run()方法割坠,run()代表該線程的任務(wù)實(shí)體
2.創(chuàng)建子類實(shí)例
3.調(diào)用子類實(shí)例的start()方法來啟動(dòng)該線程齐帚。

public class ThreadTest1 extends Thread{
    private int i;
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"-"+i);
        }
    }
    public static void main(String[] args){
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
            if (i==20){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                new ThreadTest1().start();
                new ThreadTest1().start();
            }
        }
    }
}

注:Thread.currentThread()是Thread的靜態(tài)方法,返回當(dāng)前執(zhí)行的線程對象彼哼;getName()是實(shí)例对妄,獲取調(diào)用該方法的線程對象的名稱。

實(shí)現(xiàn)Runnable接口創(chuàng)建線程類

1.定義Runnable接口的實(shí)現(xiàn)類敢朱,重寫該接口的run()方法剪菱,該方法也是線程的執(zhí)行體摩瞎。
2.創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例,并以此實(shí)例為tagret創(chuàng)建一個(gè)Thread對象孝常,該Thread才是真正的線程對象旗们。
3.調(diào)用線程對象的start()方法啟動(dòng)該線程。

public class ThreadTest1 implements Runnable{
    private int i;
    @Override
    public void run() {
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
        }
    }
    public static void main(String[] args){
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
            if (i==20){
                ThreadTest1 rn = new ThreadTest1();
                Thread st1 = new Thread(rn,"新線程1");
                Thread st2 = new Thread(rn,"新線程2");
                st1.start();
                st2.start();
            }
        }

    }
}

注:采用Runnable接口方式創(chuàng)建的多個(gè)線程可以共享線程類的實(shí)例變量构灸,這是因?yàn)槌绦蛩鶆?chuàng)建的Runnable對象只是線程的target上渴,而多個(gè)線程可以共享一個(gè)target,所以多個(gè)線程可以共享同一個(gè)線程類(target)喜颁。

使用Callable和Future創(chuàng)建線程

1.創(chuàng)建Callable接口的實(shí)現(xiàn)類稠氮,并實(shí)現(xiàn)call()方法,再創(chuàng)建Callable實(shí)現(xiàn)類的實(shí)例半开。
2.使用FutureTask類來包裝Callable對象括袒,該FutureTask對象封裝該Callable對象的call()方法的返回值。
3.使用FutrueTask對象作為Thread對象的taget創(chuàng)建并啟動(dòng)新線程稿茉。
4.調(diào)用FutureTask對象的get()方法來獲得子線程執(zhí)行結(jié)束后的返回值锹锰。

public class CallableTest{
    public static void main(String[] args){
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int i = 0;
                for (; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName()+"-"+i);
                }
                return i;
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
            if (i==20){
                new Thread(futureTask).start();
                new Thread(futureTask).start();
            }
        }
    }
}

注:
1.call()方法可以有返回值,且能拋出異常漓库。
2.FutureTask實(shí)現(xiàn)了Future接口恃慧,并實(shí)現(xiàn)了Runnable接口。
3.FutureTask的幾個(gè)方法能夠控制它關(guān)聯(lián)的Callable任務(wù)渺蒿。

線程的生命周期

在線程的生命周期中痢士,它要經(jīng)過新建、就緒茂装、運(yùn)行怠蹂、阻塞和死亡5種狀態(tài)。
1.新建:當(dāng)程序使用new關(guān)鍵字創(chuàng)建了一個(gè)線程之后少态,該線程就處于新建狀態(tài)城侧,此時(shí)它和其他Java對象一樣。
2.就緒:程序調(diào)用線程的start()方法之后彼妻。
注:啟動(dòng)線程要用start()嫌佑,而不是run(),直接調(diào)用run()的話程序只是把線程當(dāng)成一個(gè)普通對象侨歉,而不是線程對象屋摇,對應(yīng)的run()方法也只是一個(gè)普通方法,而不是線程執(zhí)行體幽邓。
3.運(yùn)行:如果就緒的線程獲得了CPU炮温,run()方法開始執(zhí)行。
4.阻塞:
(1)線程調(diào)用sleep()方法主動(dòng)放棄占用的處理器資源
(2)線程調(diào)用了一個(gè)阻塞式IO方法牵舵,在該方法返回之前線程阻塞柒啤。
(3)線程視圖獲得一個(gè)同步監(jiān)視器倦挂,但該同步監(jiān)視器正被其他線程所持有。
(4)線程在等待某個(gè)通知白修。
(5)程序調(diào)用了線程的suspend()方法將該線程掛起妒峦,容易導(dǎo)致死鎖重斑。
5.死亡:
(1)run()或call()執(zhí)行完成兵睛,線程正常結(jié)束。
(2)線程拋出一個(gè)未捕獲的Exception或Error窥浪。
(3)調(diào)用stop()方法來結(jié)束線程祖很,容易導(dǎo)致死鎖。

線程生命周期.jpg

控制線程

join線程

當(dāng)某個(gè)程序執(zhí)行流中調(diào)用其他線程的join()方法時(shí)漾脂,調(diào)用線程將被阻塞假颇,直到被join()方法加入的join線程執(zhí)行完為止。

public class ThreadTest extends Thread{
    private int i;
    public ThreadTest(String name){
        super(name);
    }
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"-"+i);
        }
    }
    public static void main(String[] args){
        new ThreadTest("普通線程").start();
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
            if (i==20){
                ThreadTest th = new ThreadTest("join線程");
                th.start();
                try {
                    th.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

可以看到由于在主線程中調(diào)用了"join線程"的join()方法骨稿,所以主線程從i==20開始阻塞笨鸡,必須等待"join線程"執(zhí)行完成之后才能繼續(xù)執(zhí)行,而"普通線程"則不受影響坦冠,與"join線程"并發(fā)執(zhí)行形耗。還有join(long mills)比較常用,表示等待被join的線程的時(shí)間最長為mills毫秒辙浑,若超過則不再等待激涤。

后臺(tái)線程

在后臺(tái)運(yùn)行,為其他的線程提供服務(wù)的線程被稱為“后臺(tái)線程”判呕,其基本特征是如果所有前臺(tái)線程都死亡倦踢,后臺(tái)線程會(huì)自動(dòng)死亡。
調(diào)用Thread對象的setDaemon(true)方法可將指定線程設(shè)置成后臺(tái)線程侠草,還可以調(diào)用isDaemon()用于判斷指定線程是否為后臺(tái)線程辱挥。前臺(tái)線程創(chuàng)建的子線程默認(rèn)是前臺(tái)線程,后臺(tái)線程創(chuàng)建的子線程默認(rèn)是后臺(tái)線程边涕。

線程睡眠:sleep

調(diào)用Thread.sleep(long mills)方法可以使當(dāng)前線程暫停mills毫秒般贼,并進(jìn)入阻塞狀態(tài)。

線程讓步:yield

與sleep相似奥吩,yield方法也是靜態(tài)方法哼蛆,也可以讓當(dāng)前線程暫停,但不會(huì)阻塞該線程霞赫,二十將線程轉(zhuǎn)入就緒狀態(tài)腮介。即就算當(dāng)前線程調(diào)用了yield方法,它也有可能立即獲得CPU而得到執(zhí)行端衰,具體取決于系統(tǒng)調(diào)度策略和線程優(yōu)先級叠洗。

調(diào)用線程對象的setPriority(int)可以設(shè)置線程的優(yōu)先級甘改,常用的有Priority.MIN_PRIORITY、Priority.MAX_PRIORITY灭抑、Priority.NORM_PRIORITY三個(gè)值(分別對應(yīng)Java線程優(yōu)先級數(shù)值的0十艾、10、5)腾节。

線程同步

線程同步是為了解決線程安全問題的忘嫉,典型的線程安全問題如多個(gè)線程同時(shí)對銀行同一個(gè)賬戶進(jìn)行取錢操作。
線程同步一共有三種基本方式:同步代碼塊案腺、同步方法庆冕、同步鎖。

同步代碼塊

synchronized(obj){
  ...
  //此處為同步代碼塊
}

上述代碼synchronized括號中的obj是同步監(jiān)視器劈榨,意思是線程開始執(zhí)行之前必須先獲得同步監(jiān)視器的鎖定访递,如賬戶可以寫成:

synchronized(account){
  ...
  //此處為同步代碼塊
}

同步方法

同步方法的同步監(jiān)視器是this,即調(diào)用該方法的對象同辣,如Account類的取錢方法draw():

public synchronized void draw(double drawAmount){
  ...
  //取錢代碼
}

注:synchronized關(guān)鍵字可以修飾方法拷姿、代碼快,但不能修飾構(gòu)造器旱函、成員變量等响巢。

同步鎖

通過顯式定義同步鎖對象來實(shí)現(xiàn)同步的機(jī)制,同步鎖由Lock對象充當(dāng)陡舅。在實(shí)現(xiàn)線程安全的控制中抵乓,比較常用的是ReentrantLock,靶衍,其常見的使用形式為:

class X{
  //定義鎖對象
  private final ReentrantLock lock = new ReentrantLock ();
  //...
 
 //定義需要保證線程安全的方法
  public void m(){
    lock.lock();
    try{
      //需要保證線程安全的代碼
    }
    finally{
      lock.unlock();
    }
  }
}

死鎖

當(dāng)兩個(gè)線程相互等待對象釋放同步監(jiān)視器時(shí)就會(huì)發(fā)生死鎖灾炭,該現(xiàn)象不會(huì)發(fā)生異常,也不會(huì)給提示颅眶,只是所有線程處于阻塞狀態(tài)蜈出。

線程通信

傳統(tǒng)的線程通信

傳統(tǒng)的線程通信主要是通過Object類提供的wait()、notify()涛酗、notifyAll()這三個(gè)方法來實(shí)現(xiàn)铡原,這三個(gè)方法必須由同步監(jiān)視器對象來調(diào)用:同步方法中直接調(diào)用;同步代碼塊中同步監(jiān)視器是synchronized括號里的對象商叹,必須由該對象調(diào)用
wait():導(dǎo)致當(dāng)前線程等待燕刻,知道其他線程調(diào)用該同步監(jiān)視器的notify()方法或notifyAll()方法來喚醒該線程。
notify():隨機(jī)喚醒在此同步監(jiān)視器上等待的單個(gè)線程剖笙。
notifyAll():喚醒在此同步監(jiān)視器上等待的所有線程卵洗。

使用Condition控制線程通信

主要針對使用Lock對象實(shí)現(xiàn)線程同步的代碼。Condition被綁定在Lock對象上弥咪,可以通過Lock對象的newCondition()方法獲取Condition對象过蹂。該對象也有三個(gè)方法對應(yīng)上述方法:
await():導(dǎo)致當(dāng)前線程等待十绑,直到其他線程調(diào)用該Condition的signal()方法或signalAll()方法來喚醒該線程。
signal():隨機(jī)喚醒在此Lock對象上等待的單個(gè)線程酷勺。
signalAll():喚醒在此Lock對象上等待的所有線程本橙。

使用阻塞隊(duì)列(BlockingQueue)控制線程通信

BlockingQueue是一個(gè)接口,其也是Queue的接口脆诉,但其并不是用來當(dāng)作容器甚亭。其有個(gè)特征:當(dāng)生產(chǎn)者線程試圖向BlockingQueue中放入元素時(shí),如果該隊(duì)列已滿库说,則該線程被阻塞狂鞋;當(dāng)消費(fèi)者線程試圖從BlockingQueue中取出元素時(shí)片择,如果該隊(duì)列已空潜的,則該線程阻塞。由此控制線程的通信字管。對應(yīng)的控制線程的方法是:
put():執(zhí)行時(shí)如果隊(duì)列已滿則阻塞調(diào)用線程啰挪。
take():執(zhí)行時(shí)如果隊(duì)列為空則阻塞調(diào)用線程。

線程相關(guān)類

ThreadLocal類

ThreadLocal就是為每一個(gè)使用該變量的線程都提供一個(gè)變量值的副本而存在的嘲叔,使每一個(gè)線程都可以獨(dú)立地改變自己的副本亡呵,而不會(huì)和其他線程的副本沖突。其提供三個(gè)方法:
T get():返回當(dāng)前線程局部變量副本的值硫戈。
void remove:刪除當(dāng)前線程的局部變量副本的值锰什。
void set(T value):設(shè)置當(dāng)前線程布局變量副本的值。
其使用方法與類的普通變量相似:

public class Account {
    private ThreadLocal<String> name = new ThreadLocal<>();
    public Account(String str){
        name.set(str);
    }

    public String getName() {
        return name.get();
    }

    public void setName(String name) {
        this.name.set(name);
    }
}

同步和ThreadLocal用途的區(qū)別

1.同步機(jī)制是為了同步多個(gè)線程對相同資源的并發(fā)訪問丁逝,是多個(gè)線程之間進(jìn)行通信的有效方法
2.ThreadLocal是為了隔離多個(gè)線程的數(shù)據(jù)共享汁胆,從根本上避免多個(gè)線程之間對共享資源的競爭,也就不需要對多個(gè)線程進(jìn)行同步了霜幼。

包裝線程不安全的集合

程序中有多個(gè)線程訪問集合類嫩码,可以使用Collections提供的類方法把這些集合包裝成線程安全的集合。
1.<T>Collection<T> synchronizedCollection(Collection<T> c)
2.static <T>List<T> synchronizedList(List<T> list)
3.static <K,V>(Sorted)Map<K,V> synchronized((Sorted)Map<K,V> map)
2.static <T>(Sorted)Set<T> synchronizedSet((Sorted)Set<T> set)

HashMap m = Collections.synchronizedMap(new HashMap());

線程安全的集合類

Java5開始罪既,java.util.concurrent包下提供了大量支持高效并發(fā)訪問的集合接口和實(shí)現(xiàn)類铸题。
其主要分為兩類:
1.以Concurrent開的集合類,如ConcurrentHashMap琢感、ConcurrentSkipListMap丢间、ConcurrentSkipListSet、ConcurrentLinkedQueue和ConcurrentLinkedDeque等驹针。
2.以CopyOnWrite開頭的集合類烘挫,如CopyOnWriteArrayList、CopyOnWriteArraySet等牌捷。

Concurrent開頭的集合類代表了支持并發(fā)訪問的集合墙牌,它們可以支持多個(gè)線程并發(fā)寫入訪問涡驮,這些寫入線程的所有操作都是線程安全的,但讀取操作不必鎖定喜滨。
當(dāng)線程對CopyOnWriteArrayList集合執(zhí)行讀取操作時(shí)捉捅,線程將會(huì)直接讀取集合本身,無須枷鎖和阻塞虽风,當(dāng)線程對ConpyOnWriteArrayList集合執(zhí)行寫入操作時(shí)棒口,該集合會(huì)在底層賦值一份新的數(shù)組,對新的數(shù)組執(zhí)行寫入操作辜膝。所以CopyOnWriteArrayList適合用再讀取操作遠(yuǎn)大于寫入操作的場景无牵,如緩存。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末厂抖,一起剝皮案震驚了整個(gè)濱河市茎毁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌忱辅,老刑警劉巖七蜘,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異墙懂,居然都是意外死亡橡卤,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門损搬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碧库,“玉大人,你說我怎么就攤上這事巧勤∏痘遥” “怎么了?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵踢关,是天一觀的道長伞鲫。 經(jīng)常有香客問我,道長签舞,這世上最難降的妖魔是什么秕脓? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮儒搭,結(jié)果婚禮上吠架,老公的妹妹穿的比我還像新娘。我一直安慰自己搂鲫,他們只是感情好傍药,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般拐辽。 火紅的嫁衣襯著肌膚如雪拣挪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天俱诸,我揣著相機(jī)與錄音菠劝,去河邊找鬼。 笑死睁搭,一個(gè)胖子當(dāng)著我的面吹牛赶诊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播园骆,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼舔痪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锌唾?” 一聲冷哼從身側(cè)響起锄码,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鸠珠,沒想到半個(gè)月后巍耗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秋麸,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡渐排,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了灸蟆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驯耻。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖炒考,靈堂內(nèi)的尸體忽然破棺而出可缚,到底是詐尸還是另有隱情,我是刑警寧澤斋枢,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布帘靡,位于F島的核電站,受9級特大地震影響瓤帚,放射性物質(zhì)發(fā)生泄漏描姚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一戈次、第九天 我趴在偏房一處隱蔽的房頂上張望轩勘。 院中可真熱鬧,春花似錦怯邪、人聲如沸绊寻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽澄步。三九已至冰蘑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間村缸,已是汗流浹背懂缕。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留王凑,地道東北人搪柑。 一個(gè)月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像索烹,于是被迫代替她去往敵國和親工碾。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

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