Java多線程創(chuàng)建和常用方法

本文主要介紹線程的定義恶导,創(chuàng)建浆竭,使用,停止惨寿,狀態(tài)圖和常用方法邦泄。
主要用于概念掃盲和梳理。
多進(jìn)程是指操作系統(tǒng)能同時(shí)運(yùn)行多個(gè)任務(wù)(程序)裂垦。
多線程是指在同一程序中有多個(gè)順序流在執(zhí)行顺囊。

先說并發(fā)

1. 為什么要用到并發(fā)

一直以來,硬件的發(fā)展極其迅速蕉拢,也有一個(gè)很著名的"摩爾定律"特碳,可能會奇怪明明討論的是并發(fā)編程為什么會扯到了硬件的發(fā)展诚亚,這其中的關(guān)系應(yīng)該是多核CPU的發(fā)展為并發(fā)編程提供的硬件基礎(chǔ)。摩爾定律并不是一種自然法則或者是物理定律午乓,它只是基于認(rèn)為觀測數(shù)據(jù)后站宗,對未來的一種預(yù)測。
按照所預(yù)測的速度益愈,我們的計(jì)算能力會按照指數(shù)級別的速度增長梢灭,不久以后會擁有超強(qiáng)的計(jì)算能力,正是在暢想未來的時(shí)候蒸其,2004年敏释,Intel宣布4GHz芯片的計(jì)劃推遲到2005年,然后在2004年秋季摸袁,Intel宣布徹底取消4GHz的計(jì)劃钥顽,也就是說摩爾定律的有效性超過了半個(gè)世紀(jì)戛然而止

但是靠汁,聰明的硬件工程師并沒有停止研發(fā)的腳步蜂大,他們?yōu)榱诉M(jìn)一步提升計(jì)算速度,而不是再追求單獨(dú)的計(jì)算單元膀曾,而是將多個(gè)計(jì)算單元整合到了一起,也就是形成了多核CPU阳啥。短短十幾年的時(shí)間添谊,家用型CPU,比如Intel i7就可以達(dá)到4核心甚至8核心。而專業(yè)服務(wù)器則通巢斐伲可以達(dá)到幾個(gè)獨(dú)立的CPU斩狱,每一個(gè)CPU甚至擁有多達(dá)8個(gè)以上的內(nèi)核。因此扎瓶,摩爾定律似乎在CPU核心擴(kuò)展上繼續(xù)得到體驗(yàn)所踊。因此,多核的CPU的背景下概荷,催生了并發(fā)編程的趨勢秕岛,通過并發(fā)編程的形式可以將多核CPU的計(jì)算能力發(fā)揮到極致,性能得到提升误证。
頂級計(jì)算機(jī)科學(xué)家Donald Ervin Knuth如此評價(jià)這種情況:在我看來继薛,這種現(xiàn)象(并發(fā))或多或少是由于硬件設(shè)計(jì)者無計(jì)可施了導(dǎo)致的,他們將摩爾定律的責(zé)任推給了軟件開發(fā)者愈捅。
另外遏考,在特殊的業(yè)務(wù)場景下先天的就適合于并發(fā)編程。比如在圖像處理領(lǐng)域蓝谨,一張1024X768像素的圖片灌具,包含達(dá)到78萬6千多個(gè)像素青团。即時(shí)將所有的像素遍歷一邊都需要很長的時(shí)間,面對如此復(fù)雜的計(jì)算量就需要充分利用多核的計(jì)算的能力咖楣。又比如當(dāng)我們在網(wǎng)上購物時(shí)督笆,為了提升響應(yīng)速度,需要拆分截歉,減庫存胖腾,生成訂單等等這些操作,就可以進(jìn)行拆分利用多線程的技術(shù)完成瘪松。面對復(fù)雜業(yè)務(wù)模型咸作,并行程序會比串行程序更適應(yīng)業(yè)務(wù)需求,而并發(fā)編程更能吻合這種業(yè)務(wù)拆分 宵睦。

2. 并發(fā)編程缺點(diǎn)

  • 頻繁的上下文切換
    時(shí)間片是CPU分配給各個(gè)線程的時(shí)間记罚,因?yàn)闀r(shí)間非常短,所以CPU不斷通過切換線程壳嚎,讓我們覺得多個(gè)線程是同時(shí)執(zhí)行的桐智,時(shí)間片一般是幾十毫秒。而每次切換時(shí)烟馅,需要保存當(dāng)前的狀態(tài)起來说庭,以便能夠進(jìn)行恢復(fù)先前狀態(tài),而這個(gè)切換時(shí)非常損耗性能郑趁,過于頻繁反而無法發(fā)揮出多線程編程的優(yōu)勢刊驴。

通常減少上下文切換可以采用無鎖并發(fā)編程,CAS算法寡润,使用最少的線程和使用協(xié)程捆憎。

  1. 無鎖并發(fā)編程:可以參照concurrentHashMap鎖分段的思想,不同的線程處理不同段的數(shù)據(jù)梭纹,這樣在多線程競爭的條件下躲惰,可以減少上下文切換的時(shí)間。
  2. CAS算法变抽,利用Atomic下使用CAS算法來更新數(shù)據(jù)础拨,使用了樂觀鎖,可以有效的減少一部分不必要的鎖競爭帶來的上下文切換
  3. 使用最少線程:避免創(chuàng)建不需要的線程绍载,比如任務(wù)很少太伊,但是創(chuàng)建了很多的線程,這樣會造成大量的線程都處于等待狀態(tài)
  4. 協(xié)程:在單線程里實(shí)現(xiàn)多任務(wù)的調(diào)度逛钻,并在單線程里維持多個(gè)任務(wù)間的切換
  • 線程安全

3. 一些概念

3.1 同步與異步

同步和異步通常用來形容一次方法調(diào)用僚焦。同步方法調(diào)用一開始,調(diào)用者必須等待被調(diào)用的方法結(jié)束后曙痘,調(diào)用者后面的代碼才能執(zhí)行芳悲。而異步調(diào)用立肘,指的是,調(diào)用者不用管被調(diào)用方法是否完成名扛,都會繼續(xù)執(zhí)行后面的代碼谅年,當(dāng)被調(diào)用的方法完成后會通知調(diào)用者。
比如肮韧,在超時(shí)購物融蹂,如果一件物品沒了,你得等倉庫人員跟你調(diào)貨弄企,直到倉庫人員跟你把貨物送過來超燃,你才能繼續(xù)去收銀臺付款,這就類似同步調(diào)用拘领。而異步調(diào)用了意乓,就像網(wǎng)購,你在網(wǎng)上付款下單后约素,什么事就不用管了届良,該干嘛就干嘛去了,當(dāng)貨物到達(dá)后你收到通知去取就好圣猎。

3.2 并發(fā)與并行

并發(fā)和并行是十分容易混淆的概念士葫。并發(fā)指的是多個(gè)任務(wù)交替進(jìn)行,而并行則是指真正意義上的“同時(shí)進(jìn)行”送悔。實(shí)際上慢显,如果系統(tǒng)內(nèi)只有一個(gè)CPU,而使用多線程時(shí)放祟,那么真實(shí)系統(tǒng)環(huán)境下不能并行鳍怨,只能通過切換時(shí)間片的方式交替進(jìn)行呻右,而成為并發(fā)執(zhí)行任務(wù)跪妥。
真正的并行也只能出現(xiàn)在擁有多個(gè)CPU的系統(tǒng)中。

3.3 阻塞和非阻塞

阻塞和非阻塞通常用來形容多線程間的相互影響声滥,比如一個(gè)線程占有了臨界區(qū)資源眉撵,那么其他線程需要這個(gè)資源就必須進(jìn)行等待該資源的釋放,會導(dǎo)致等待的線程掛起落塑,這種情況就是阻塞纽疟,而非阻塞就恰好相反,它強(qiáng)調(diào)沒有一個(gè)線程可以阻塞其他線程憾赁,所有的線程都會嘗試地往前運(yùn)行污朽。

3.4 臨界區(qū)

臨界區(qū)用來表示一種公共資源或者說是共享數(shù)據(jù),可以被多個(gè)線程使用龙考。但是每個(gè)線程使用時(shí)蟆肆,一旦臨界區(qū)資源被一個(gè)線程占有矾睦,那么其他線程必須等待。

進(jìn)程與線程

進(jìn)程--資源分配的最小單位

  • 每個(gè)進(jìn)程都有獨(dú)立的代碼和數(shù)據(jù)空間(進(jìn)程上下文)
  • 一個(gè)進(jìn)程包含多個(gè)線程
  • 進(jìn)程間的切換會有較大的開銷

線程--cpu調(diào)度的最小單位

  • 同一類線程共享代碼和數(shù)據(jù)空間炎功,
  • 每個(gè)線程有獨(dú)立的運(yùn)行棧和程序計(jì)數(shù)器(PC)
  • 線程切換開銷小

其他

  • 多進(jìn)程是指操作系統(tǒng)能同時(shí)運(yùn)行多個(gè)任務(wù)(程序)枚冗。
  • 多線程是指在同一程序中有多個(gè)順序流在執(zhí)行。
  • 線程和進(jìn)程一樣分為五個(gè)階段:創(chuàng)建蛇损、就緒赁温、運(yùn)行、阻塞淤齐、終止股囊。

線程創(chuàng)建、使用床玻、停止

創(chuàng)建線程

  • 在Java中要想實(shí)現(xiàn)多線程毁涉,有三種手段,一種是繼承Thread類锈死,另外一種是實(shí)現(xiàn)Runnable接口贫堰,三是實(shí)現(xiàn)Callable 接口。
    實(shí)現(xiàn) Runnable 和 Callable 接口的類只能當(dāng)做一個(gè)可以在線程中運(yùn)行的任務(wù)待牵,不是真正意義上的線程其屏,因此最后還需要通過 Thread 來調(diào)用∮Ц茫可以說任務(wù)是通過線程驅(qū)動從而執(zhí)行的偎行。

1. 繼承Thread 類,覆蓋run方法(推薦使用Runable)

  • run()方法是多線程程序的一個(gè)約定贰拿,所有的多線程代碼都在run方法里面蛤袒。
        class PrimeThread extends Thread {
         long minPrime;
         PrimeThread(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }
  • 我們復(fù)寫的是run()方法,但是執(zhí)行的卻是start()方法膨更。
  • start()方法的調(diào)用后并不是立即執(zhí)行多線程代碼妙真,而是使得該線程變?yōu)榭蛇\(yùn)行態(tài)(Runnable),什么時(shí)候運(yùn)行是由操作系統(tǒng)決定的荚守。
     PrimeThread p = new PrimeThread(143);
     p.start();

2. 實(shí)現(xiàn)Runnable接口珍德,覆蓋run方法

     class PrimeRun implements Runnable {
         long minPrime;
         PrimeRun(long minPrime) {
             this.minPrime = minPrime;
         }

         public void run() {
             // compute primes larger than minPrime
              . . .
         }
     }

在啟動的多線程的時(shí)候,需要先通過Thread類的構(gòu)造方法Thread(Runnable target)構(gòu)造出對象矗漾,然后調(diào)用Thread對象的start()方法來運(yùn)行多線程代碼锈候。

     PrimeRun p = new PrimeRun(143);
     new Thread(p).start();

    new Thread(new PrimeRun(143)).start();

實(shí)際上所有的多線程代碼都是通過運(yùn)行Thread的start()方法來運(yùn)行的。因此敞贡,不管是擴(kuò)展Thread類還是實(shí)現(xiàn)Runnable接口來實(shí)現(xiàn)多線程泵琳,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進(jìn)行多線程編程的基礎(chǔ)

3. 實(shí)現(xiàn) Callable 接口

與 Runnable 相比获列,Callable 可以有返回值琳钉,返回值通過FutureTask 進(jìn)行封裝

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        // ...
    }
    public static void main(String[]  args) {
        MyCallable mc = new MyCallable();
        FutureTask<Integer> ft = new FutureTask<>(mc);
        Thread thread = new Thread(ft);
        thread.start();
        System.out.println(ft.get());
    }
}

Thread和Runnable的區(qū)別

實(shí)現(xiàn)Runnable接口比繼承Thread類所具有的優(yōu)勢:

  1. 適合多個(gè)相同的程序代碼的線程去處理同一個(gè)資源
  2. 可以避免java中的單繼承的限制
  3. 增加程序的健壯性蛛倦,代碼可以被多個(gè)線程共享歌懒,代碼和數(shù)據(jù)獨(dú)立
  4. 線程池只能放入實(shí)現(xiàn)Runable或callable類線程,不能直接放入繼承Thread的類

使用線程

方法

類別 方法簽名 簡介
構(gòu)造方法 thread() -
- thread(String name) -
- thread(Runnable target) -
- thread(Runnable target,String name) -
常用方法 void start() 啟動線程
- static void sleep(long millis) 休眠
- static void sleep(long millis,int nanos) 休眠
- void join() 使其他線程等待當(dāng)前線程終止
- void join(long millis) 使其他線程等待當(dāng)前線程終止
- void join(long millis,int nanos) 使其他線程等待當(dāng)前線程終止
- static void yield( ) 當(dāng)前運(yùn)行線程釋放處理器資源
獲取線程引用 static Thread currendThread() 返回當(dāng)前運(yùn)行的線程引用

注意事項(xiàng)

  • main方法其實(shí)也是一個(gè)線程溯壶。在java中所以的線程都是同時(shí)啟動的及皂,至于什么時(shí)候,哪個(gè)先執(zhí)行且改,完全看誰先得到CPU的資源验烧。
  • 在java中,每次程序運(yùn)行至少啟動2個(gè)線程又跛。一個(gè)是main線程碍拆,一個(gè)是垃圾收集線程。

停止線程

錯(cuò)誤方法

  • stop()方法慨蓝。
    它會使線程戛然而止感混,我們不知道它完成了什么工作,沒有完成什么工作礼烈,因此是錯(cuò)誤的弧满。
  • interrupt()方法。
    不要使用interrupt()方法此熬,因?yàn)?strong>在線程run()中調(diào)用sleep(),join(),yeild()方法時(shí)庭呜,中斷狀態(tài)會被清除,isinterrupe=false

如何正確的停止線程

使用退出標(biāo)志犀忱。

volatile boolean keepRunning = true;

代碼示例

public class ActorThread extends Thread {//繼承Thread創(chuàng)建線程
    // 覆蓋run------------------------------------------------------
    public void run() {
        System.out.println(getName() + "是一個(gè)演員募谎!");
        int count = 0;
        boolean keepRunning = true;
        while (keepRunning) {
            System.out.println(getName() + "登臺演出:" + (++count));
            //標(biāo)注法停止線程-----------------------------------------------
            if (count == 15) {
                keepRunning = false;
            }
            //測試sleep()-----------------------
            if (count % 5 == 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(getName() + "的演出結(jié)束了!");
    }

    public static void main(String[] args) {
        Thread actor = new ActorThread();
        actor.setName("Mr. Thread");
        actor.start();
        Thread actressThread = new Thread(new Actress(), "Ms. Runnable");
        actressThread.start();
    }

}

class Actress implements Runnable {//實(shí)現(xiàn)Runnable接口創(chuàng)建線程
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "是一個(gè)演員阴汇!");
        int count = 0;
        boolean keepRunning = true;
        while (keepRunning) {
            System.out.println(Thread.currentThread().getName() + "登臺演出:" + (++count));
            if (count == 15) {
                keepRunning = false;
            }
            if (count % 5 == 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(Thread.currentThread().getName() + "的演出結(jié)束了数冬!");
    }
}

輸出

測試sleep方法與停止線程

線程狀態(tài)轉(zhuǎn)換

線程狀態(tài)轉(zhuǎn)換
  1. 新建狀態(tài)(New):新創(chuàng)建了一個(gè)線程對象。
  2. 就緒狀態(tài)(Runnable):線程對象創(chuàng)建后鲫寄,其他線程調(diào)用了該對象的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ī)會轉(zhuǎn)到運(yùn)行狀態(tài)府阀。阻塞的情況分三種:
    • 等待阻塞:運(yùn)行的線程執(zhí)行wait()方法,JVM會把該線程放入等待池中芽突。(wait會釋放持有的鎖)
    • 同步阻塞:運(yùn)行的線程在獲取對象的同步鎖時(shí)试浙,若該同步鎖被別的線程占用,則JVM會把該線程放入鎖池中寞蚌。
    • 其他阻塞:運(yùn)行的線程執(zhí)行sleep()或join()方法田巴,或者發(fā)出了I/O請求時(shí),JVM會把該線程置為阻塞狀態(tài)挟秤。當(dāng)sleep()狀態(tài)超時(shí)壹哺、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí)艘刚,線程重新轉(zhuǎn)入就緒狀態(tài)管宵。(sleep是不會釋放持有的鎖)
  5. 死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期攀甚。

常用函數(shù)說明

sleep(long millis):線程暫停執(zhí)行

在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠箩朴。
sleep() 可能會拋出 InterruptedException。因?yàn)楫惓2荒芸缇€程傳播回 main() 中秋度,因此必須在本地進(jìn)行處理隧饼。線程中拋出的其它異常也同樣需要在本地進(jìn)行處理。

join(long millis):指等待t線程一段時(shí)間(或終止)

  • join是Thread類的一個(gè)方法静陈,啟動線程后直接調(diào)用燕雁,即join()的作用是:“等待該線程(終止)”。
  • 目的
    在很多情況下鲸拥,主線程生成并起動了子線程拐格,如果子線程里要進(jìn)行大量的耗時(shí)的運(yùn)算,主線程往往將于子線程之前結(jié)束刑赶,但是如果主線程處理完其他的事務(wù)后捏浊,需要用到子線程的處理結(jié)果,也就是主線程需要等待子線程執(zhí)行完成之后再結(jié)束撞叨,這個(gè)時(shí)候就要用到j(luò)oin()方法了金踪。

yield():暫停當(dāng)前正在執(zhí)行的線程對象,并執(zhí)行其他線程牵敷。

  • yield()應(yīng)該做的是讓當(dāng)前運(yùn)行線程回到可運(yùn)行狀態(tài)胡岔,以允許具有相同優(yōu)先級的其他線程獲得運(yùn)行機(jī)會。
  • 目的
    使用yield()的目的是讓相同優(yōu)先級的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行枷餐。但是靶瘸,實(shí)際中無法保證yield()達(dá)到讓步目的,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中。

wait()

本小節(jié)轉(zhuǎn)載自使用wait/notify/notifyAll實(shí)現(xiàn)線程間通信的幾點(diǎn)重要說明

用于線程間通信

在Java中怨咪,可以通過配合調(diào)用Object對象的wait()方法和notify()方法或notifyAll()方法來實(shí)現(xiàn)線程間的通信屋剑。

語義

在線程中調(diào)用wait()方法,將阻塞等待其他線程的通知(其他線程調(diào)用notify()方法或notifyAll()方法)诗眨,在線程中調(diào)用notify()方法或notifyAll()方法唉匾,將通知其他線程從wait()方法處返回。

Object是所有類的超類匠楚,它有5個(gè)方法組成了等待/通知機(jī)制的核心:notify()肄鸽、notifyAll()、wait()油啤、wait(long)和wait(long典徘,int)。在Java中益咬,所有的類都從Object繼承而來逮诲,因此,所有的類都擁有這些共有方法可供使用幽告。而且梅鹦,由于他們都被聲明為final,因此在子類中不能覆寫任何一個(gè)方法冗锁。

使用中需要注意

  1. wait() --- "我去等待了"
 public final void wait()  throws InterruptedException,IllegalMonitorStateException 
  • 該方法用來將當(dāng)前線程置入休眠狀態(tài)齐唆,直到接到通知或被中斷為止。在調(diào)用wait()之前冻河,線程必須要獲得該對象的對象級別鎖箍邮,即只能在同步方法或同步塊中調(diào)用wait()方法。
  • 進(jìn)入wait()方法后叨叙,當(dāng)前線程釋放鎖锭弊。在從wait()返回前,線程與其他線程競爭重新獲得鎖擂错。如果調(diào)用wait()時(shí)味滞,沒有持有適當(dāng)?shù)逆i,則拋出IllegalMonitorStateException钮呀,它是RuntimeException的一個(gè)子類剑鞍,因此,不需要try-catch結(jié)構(gòu)爽醋。
  1. notify()---"大家來爭我"
public final native void notify() throws IllegalMonitorStateException
  • 該方法也要在同步方法或同步塊中調(diào)用蚁署,即在調(diào)用前,線程也必須要獲得該對象的對象級別鎖子房,的如果調(diào)用notify()時(shí)沒有持有適當(dāng)?shù)逆i形用,也會拋出IllegalMonitorStateException
  • 該方法用來通知那些可能等待該對象的對象鎖的其他線程。如果有多個(gè)線程等待证杭,則線程規(guī)劃器任意挑選出其中一個(gè)wait()狀態(tài)的線程來發(fā)出通知田度,并使它等待獲取該對象的對象鎖(notify后,當(dāng)前線程不會馬上釋放該對象鎖解愤,wait所在的線程并不能馬上獲取該對象鎖镇饺,要等到程序退出synchronized代碼塊后,當(dāng)前線程才會釋放鎖送讲,wait所在的線程也才可以獲取該對象鎖)奸笤,但不驚動其他同樣在等待被該對象notify的線程們。當(dāng)?shù)谝粋€(gè)獲得了該對象鎖的wait線程運(yùn)行完畢以后哼鬓,它會釋放掉該對象鎖监右,此時(shí)如果該對象沒有再次使用notify語句,則即便該對象已經(jīng)空閑异希,其他wait狀態(tài)等待的線程由于沒有得到該對象的通知健盒,會繼續(xù)阻塞在wait狀態(tài),直到這個(gè)對象發(fā)出一個(gè)notify或notifyAll称簿。這里需要注意:它們等待的是被notify或notifyAll扣癣,而不是鎖。這與下面的notifyAll()方法執(zhí)行后的情況不同憨降。
  1. notifyAll()
     public final native void notifyAll() throws IllegalMonitorStateException

該方法與notify()方法的工作方式相同父虑,重要的一點(diǎn)差異是:
notifyAll使所有原來在該對象上wait的線程統(tǒng)統(tǒng)退出wait的狀態(tài)(即全部被喚醒,不再等待notify或notifyAll授药,但由于此時(shí)還沒有獲取到該對象鎖士嚎,因此還不能繼續(xù)往下執(zhí)行),變成等待獲取該對象上的鎖悔叽,一旦該對象鎖被釋放(notifyAll線程退出調(diào)用了notifyAll的synchronized代碼塊的時(shí)候)航邢,他們就會去競爭。如果其中一個(gè)線程獲得了該對象鎖骄蝇,它就會繼續(xù)往下執(zhí)行膳殷,在它退出synchronized代碼塊,釋放鎖后九火,其他的已經(jīng)被喚醒的線程將會繼續(xù)競爭獲取該鎖赚窃,一直進(jìn)行下去,直到所有被喚醒的線程都執(zhí)行完畢岔激。

  1. wait(long)和wait(long,int)
    顯然勒极,這兩個(gè)方法是設(shè)置等待超時(shí)時(shí)間的,后者在超值時(shí)間上加上ns虑鼎,精度也難以達(dá)到辱匿,因此键痛,該方法很少使用。對于前者匾七,如果在等待線程接到通知或被中斷之前絮短,已經(jīng)超過了指定的毫秒數(shù),則它通過競爭重新獲得鎖昨忆,并從wait(long)返回丁频。另外,需要知道邑贴,如果設(shè)置了超時(shí)時(shí)間席里,當(dāng)wait()返回時(shí),我們不能確定它是因?yàn)榻拥搅送ㄖ€是因?yàn)槌瑫r(shí)而返回的拢驾,因?yàn)閣ait()方法不會返回任何相關(guān)的信息奖磁。但一般可以通過設(shè)置標(biāo)志位來判斷,在notify之前改變標(biāo)志位的值繁疤,在wait()方法后讀取該標(biāo)志位的值來判斷署穗,當(dāng)然為了保證notify不被遺漏,我們還需要另外一個(gè)標(biāo)志位來循環(huán)判斷是否調(diào)用wait()方法嵌洼。
  2. 深入理解:
    如果線程調(diào)用了對象的wait()方法案疲,那么線程便會處于該對象的
    等待池中,等待池中的線程不會去競爭該對象的鎖麻养。
    當(dāng)有線程調(diào)用了對象的notifyAll()方法(喚醒所有wait線程)或notify()方法(只隨機(jī)喚醒一個(gè)wait線程)褐啡,被喚醒的的線程便會進(jìn)入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖鳖昌。
    優(yōu)先級高的線程競爭到對象鎖的概率大备畦,假若某線程沒有競爭到該對象鎖,它還會留在鎖池中许昨,唯有線程再次調(diào)用wait()方法懂盐,它才會重新回到等待池中。而競爭到對象鎖的線程則繼續(xù)往下執(zhí)行糕档,直到執(zhí)行完了synchronized代碼塊莉恼,它會釋放掉該對象鎖,這時(shí)鎖池中的線程會繼續(xù)競爭該對象鎖速那。

wait和sleep區(qū)別

共同點(diǎn):

  1. 他們都是在多線程的環(huán)境下俐银,都可以在程序的調(diào)用處阻塞指定的毫秒數(shù),并返回
  2. wait()和sleep()都可以通過interrupt()方法 打斷線程的暫停狀態(tài) 端仰,從而使線程立刻拋出InterruptedException捶惜。 (不建議使用該方法)
  • 如果線程A希望立即結(jié)束線程B,則可以對線程B對應(yīng)的Thread實(shí)例調(diào)用interrupt方法荔烧。如果此刻線程B正在wait/sleep /join吱七,則線程B會立刻拋出InterruptedException汽久,在catch() {} 中直接return即可安全地結(jié)束線程。
  • 需要注意的是踊餐,InterruptedException是線程自己從內(nèi)部拋出的景醇,并不是interrupt()方法拋出的。對某一線程調(diào)用 interrupt()時(shí)市袖,如果該線程正在執(zhí)行普通的代碼啡直,那么該線程根本就不會拋出InterruptedException烁涌。但是苍碟,一旦該線程進(jìn)入到 wait()/sleep()/join()后,就會立刻拋出InterruptedException 撮执。

不同點(diǎn):

  1. 所屬的類
    Thread類的方法:sleep(),yield()等
    Object的方法:wait()和notify()等
  2. 是否釋放鎖
    每個(gè)對象都有一個(gè)鎖來控制同步訪問微峰。Synchronized關(guān)鍵字可以和對象的鎖交互,來實(shí)現(xiàn)線程的同步抒钱。
    sleep方法沒有釋放鎖蜓肆,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法谋币。
  3. 使用限制
    wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用 线定。

sleep()和yield()的區(qū)別

  • sleep 方法使當(dāng)前運(yùn)行中的線程睡眼一段時(shí)間罗岖,進(jìn)入不可運(yùn)行狀態(tài),這段時(shí)間的長短是由程序設(shè)定的诅蝶,yield 方法使當(dāng)前線程讓出 CPU 占有權(quán)退个,但讓出的時(shí)間是不可設(shè)定的。實(shí)際上调炬,yield()方法對應(yīng)了如下操作:先檢測當(dāng)前是否有相同優(yōu)先級的線程處于同可運(yùn)行狀態(tài)语盈,如有,則把 CPU 的占有權(quán)交給此線程缰泡,否則刀荒,繼續(xù)運(yùn)行原來的線程。所以yield()方法稱為“退讓”棘钞,它把運(yùn)行機(jī)會讓給了同等優(yōu)先級的其他線程
  • 另外照棋,sleep 方法允許較低優(yōu)先級的線程獲得運(yùn)行機(jī)會,但 yield() 方法執(zhí)行時(shí)武翎,當(dāng)前線程仍處在可運(yùn)行狀態(tài)烈炭,所以,不可能讓出較低優(yōu)先級的線程些時(shí)獲得 CPU 占有權(quán)宝恶。在一個(gè)運(yùn)行系統(tǒng)中符隙,如果較高優(yōu)先級的線程沒有調(diào)用 sleep 方法趴捅,又沒有受到 I\O 阻塞,那么霹疫,較低優(yōu)先級線程只能等待所有較高優(yōu)先級的線程運(yùn)行結(jié)束拱绑,才有機(jī)會運(yùn)行。

三線程打印ABC

要求

java實(shí)現(xiàn)三個(gè)線程A B C:A線程打印10次A丽蝎,B線程打印10次B,C線程打印10次C猎拨,要求線程同時(shí)運(yùn)行,交替打印10次ABC屠阻。

方法一.使用synchronized() + wait(),nitify()

  • 題意
    問題為三線程間的同步喚醒操作红省,主要的目的就是ThreadA->ThreadB->ThreadC→ThreadA……循環(huán)執(zhí)行三個(gè)線程。為了控制線程執(zhí)行的順序国觉,那么就必須要確定喚醒吧恃、等待的順序,所以每一個(gè)線程必須同時(shí)持有兩個(gè)對象鎖麻诀,才能繼續(xù)執(zhí)行痕寓。一個(gè)對象鎖是prev,就是前一個(gè)線程所持有的對象鎖蝇闭。還有一個(gè)就是自身對象鎖呻率。
  • 思路
    主要的思想就是,為了控制執(zhí)行的順序呻引,必須要先持有prev鎖礼仗,也就是前一個(gè)線程要釋放自身對象鎖,再去申請自身對象鎖苞七,兩者兼?zhèn)鋾r(shí)打印字母藐守,之后首先調(diào)用self.notifyAll()釋放自身對象鎖,喚醒下一個(gè)等待線程蹂风,再調(diào)用prev.wait()釋放prev對象鎖卢厂,終止當(dāng)前線程,等待循環(huán)結(jié)束后再次被喚醒惠啄。

程序運(yùn)行的主要過程就是
A線程最先運(yùn)行慎恒,持有C,A對象鎖,后釋放A,C鎖撵渡,喚醒B融柬;
線程B等待A鎖,再申請B鎖趋距,后打印B粒氧,再釋放B,A鎖,喚醒C节腐;
線程C等待B鎖外盯,再申請C鎖摘盆,后打印C,再釋放C,B鎖饱苟,喚醒A……

  • 注意
    為了避免JVM啟動ThreadA孩擂、ThreadB、ThreadC三個(gè)線程順序的不確定性箱熬。需要讓A,B,C三個(gè)線程以確定的順序啟動类垦,中間加一段sleep()確保前一個(gè)線程已啟動

代碼實(shí)現(xiàn)

public class ABC {
    public static class ThreadPrinter implements Runnable {
        private String name;
        private Object prev;//前一個(gè)對象的鎖
        private Object self;//自己的鎖

        private ThreadPrinter(String name, Object prev, Object self) {
            this.name = name;
            this.prev = prev;
            this.self = self;
        }

        @Override
        public void run() {
            int count = 10;
            while (count > 0) {// 多線程并發(fā)城须,不能用if蚤认,必須用循環(huán)測試等待條件,避免虛假喚醒
                synchronized (prev) { // 先獲取 prev 鎖
                    synchronized (self) {// 再獲取 self 鎖
                        System.out.print(name);
                        count--;
                        self.notifyAll();// 先釋放 self酿傍,喚醒其他線程競爭self鎖
                    }
                    try {
                        prev.wait(); // 再釋放 prev烙懦,休眠等待喚醒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Object a = new Object();
        Object b = new Object();
        Object c = new Object();
        ThreadPrinter pa = new ThreadPrinter("A", c, a);
        ThreadPrinter pb = new ThreadPrinter("B", a, b);
        ThreadPrinter pc = new ThreadPrinter("C", b, c);

        new Thread(pa).start();
        Thread.sleep(10);
        new Thread(pb).start();
        Thread.sleep(10);
        new Thread(pc).start();
        Thread.sleep(10);
    }
}

輸出

打印結(jié)果

方法二. 使用Lock

代碼實(shí)現(xiàn)

import java.util.concurrent.locks.*;

public class ABC_Lock {
    private static Lock lock = new ReentrantLock();//通過Lock鎖來保證線程的訪問的互斥
    private static int state = 0;//控制ABC的打印語句是否執(zhí)行驱入,獲取Lock之后的判斷
 
    static class ThreadA extends Thread {
        @Override
        public void run() {
            int count = 10;
            while(count > 0) {
                try {
                    lock.lock();
                    while (state % 3 == 0) {//多線程并發(fā)赤炒,不能用if,必須用循環(huán)測試等待條件亏较,避免虛假喚醒
                        System.out.print("A");
                        state++;
                        count--;
                    }
                } finally {
                    lock.unlock();// lock()和unlock()操作結(jié)合try/catch使用
                }
            }
        }
    }
 
    static class ThreadB extends Thread {
        @Override
        public void run() {
            int count = 10;
            while(count > 0) {
                try {
                    lock.lock();
                    while (state % 3 == 1) {//多線程并發(fā)莺褒,不能用if,必須用循環(huán)測試等待條件雪情,避免虛假喚醒
                        System.out.print("B");
                        state++;
                        count--;
                    }
                } finally {
                    lock.unlock();// lock()和unlock()操作結(jié)合try/catch使用
                }
            }
        }
    }
 
    static class ThreadC extends Thread {
        @Override
        public void run() {
            int count = 10;
            while(count > 0){
                try {
                    lock.lock();
                    while (state % 3 == 2) {//多線程并發(fā)遵岩,不能用if,必須用循環(huán)測試等待條件巡通,避免虛假喚醒
                        System.out.print("C");
                        state++;
                        count--;
                    }
                } finally {
                    lock.unlock();// lock()和unlock()操作結(jié)合try/catch使用
                }
            }
        }
    }
 
    public static void main(String[] args) {
        new ThreadA().start();
        new ThreadB().start();
        new ThreadC().start();
    }
}

輸出

打印結(jié)果

參考文章
并發(fā)編程的優(yōu)缺點(diǎn)
java多線程入門學(xué)習(xí)(一)
Java多線程學(xué)習(xí)(吐血超詳細(xì)總結(jié))
oracle官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尘执,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子宴凉,更是在濱河造成了極大的恐慌誊锭,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件弥锄,死亡現(xiàn)場離奇詭異丧靡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)籽暇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門温治,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人戒悠,你說我怎么就攤上這事熬荆。” “怎么了绸狐?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵卤恳,是天一觀的道長捏顺。 經(jīng)常有香客問我,道長纬黎,這世上最難降的妖魔是什么幅骄? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮本今,結(jié)果婚禮上拆座,老公的妹妹穿的比我還像新娘。我一直安慰自己冠息,他們只是感情好挪凑,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著逛艰,像睡著了一般躏碳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上散怖,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天菇绵,我揣著相機(jī)與錄音,去河邊找鬼镇眷。 笑死咬最,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的欠动。 我是一名探鬼主播永乌,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼具伍!你這毒婦竟也來了翅雏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤人芽,失蹤者是張志新(化名)和其女友劉穎望几,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體啼肩,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡橄妆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祈坠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片害碾。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖赦拘,靈堂內(nèi)的尸體忽然破棺而出慌随,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布阁猜,位于F島的核電站丸逸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏剃袍。R本人自食惡果不足惜黄刚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望民效。 院中可真熱鬧憔维,春花似錦、人聲如沸畏邢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舒萎。三九已至程储,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間臂寝,已是汗流浹背章鲤。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留交煞,地道東北人咏窿。 一個(gè)月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓斟或,卻偏偏與公主長得像素征,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子萝挤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

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