多線程

概述

操作系統(tǒng)幾乎都支持多任務(wù)操作蜒谤,例如:可以聽歌山宾,可以看電影,可以聊天鳍徽。 聽歌塌碌,看電影,聊天就是一個個的任務(wù)旬盯,每個運行中的任務(wù)都是一個進程。就聽歌而言翎猛,可以唱胖翰,可以顯示歌詞,這便是一個個的線程切厘,一個進程中包含了多個順序執(zhí)行流萨咳,每個順序執(zhí)行流就是一個線程。

進程的定義

進程執(zhí)行的程序(程序是靜態(tài)的疫稿,進程是動態(tài)的)培他,或者是一個任務(wù)。 一個進程可以包含一個或多個線程遗座。

線程的定義

線程就是程序中單獨順序的流控制舀凛。線程本身不能運行,它只能用于程序中途蒋。而多線程指的是單個程序中可以同時運行多個不同的線程執(zhí)行不同的小任務(wù)猛遍。 線程是不擁有系統(tǒng)資源的,只能使用分配給程序的資源和環(huán)境。

進程可以看成是一個工廠懊烤,而線程可以看成是工廠中的工人梯醒,為了完成工廠的任務(wù),每個工人都要去完成自己所負責(zé)的小任務(wù)腌紧, 從而使進程完成它的任務(wù)茸习。 多進程允許多個任務(wù)同時運行,多線程是一個任務(wù)分成不同部分運行壁肋。

進程和線程的區(qū)別

  • 多個進程的內(nèi)部數(shù)據(jù)和狀態(tài)都是完全獨立的号胚,不會互相影響。 而多線程是共享一塊內(nèi)存空間和一組系統(tǒng)資源墩划,有可能互相影響涕刚。
  • 線程本身的數(shù)據(jù)通常只有寄存器數(shù)據(jù),以及一個程序執(zhí)行使用過的堆棧乙帮,所以線程的切換比進程切換的負擔要小杜漠。
  • 一個進程由多個線程組成,線程是進程的進行單元察净。當進程被初始化后驾茴,主線程就被創(chuàng)建了。一個線程必須有一個父進程氢卡。

一個程序運行后至少有一個進程锈至,一個進程里可以包含多個線程,但至少要包含一個線程译秦。

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

JAVA中使用Threadk類來代表線程峡捡,所有的線程對象都是Thread類或者子類的實例。每個線程的作用是完成一定的任務(wù)筑悴,實際上就是進行一段程序流们拙,JAVA使用線程執(zhí)行體來代表這段程序流。一個線程不能被重現(xiàn)啟動在它執(zhí)行完成之后阁吝。

線程創(chuàng)建的方式:

  • 繼承Thread類砚婆,然后重寫run方法
  • 實現(xiàn)Runable接口 ,實現(xiàn)其run方法
  • 使用Callable和Future創(chuàng)建線程

將希望線程執(zhí)行的代碼放到run方法中突勇,然后使用start方法來啟動線程装盯,start方法首先為線程的執(zhí)行準備好系統(tǒng)資源,在去調(diào)用run方法甲馋。

下面看一下各個線程的實現(xiàn)代碼

通過繼承Thread類實現(xiàn)

public class ThreadTest {
    public static void main(String[] args) {
        Thread1 t = new Thread1() ;
        t.start();
    }
}

class Thread1 extends  Thread{
    @Override
    public void run(){
        for (int i = 0 ; i < 100 ; i++) {
            System.out.println("hello thread " + i );
        }
    }
}

通過實現(xiàn)Runable接口實現(xiàn):

public class Thread2Test {
    public static void main(String[] args) {
        Thread2 t = new Thread2();
        Thread tt = new Thread(t) ;
        tt.start();
    }
}

class Thread2 implements  Runnable{

    @Override
    public void run() {
        for (int i = 0 ; i < 100 ; i++){
            System.out.println("hello world " + i);
        }
    }
}

//通過靜態(tài)內(nèi)部類的方式創(chuàng)建多線程
public class StaticTest {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0 ; i <20 ; i++) {
                    System.out.println("hello  world " + i );
                }
            }
        }) ;
        t.start();
        for (int i = 0 ; i < 20  ; i++){
            System.out.println("main " + i );
        }
    }
}

通過上面兩種線程啟動方式的比較埂奈,是否會有這樣的疑問,繼承Thread為什么要重寫run方法定躏,通過實現(xiàn)Runable接口的類挥转,為什么要作為Thread類的構(gòu)造參數(shù)海蔽,才能啟動線程,取源碼中尋找一下答案绑谣。

public
class Thread implements Runnable {  //Thread類也實現(xiàn)了Runable接口
 //無參構(gòu)造方法
 public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
 //參數(shù)為Runable類型的構(gòu)造方法
 public Thread(Runnable target) {
      init(null, target, "Thread-" + nextThreadNum(), 0);
 }
 
 //線程默認的名字是Thread-number 党窜,通過下面的代碼實現(xiàn), threadInitNumber靜態(tài)成員變量,被所有的線程對象共享
 private static int threadInitNumber;
 private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }
 public Thread(String name) {
        init(null, null, name, 0);  //指定線程的名字
    }
 //... 還有其他的構(gòu)造方法借宵,不再羅列

 //私有初始化方法
 private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
        init(g, target, name, stackSize, null);
    }

 public synchronized void start() {
  
        if (threadStatus != 0)
         
        group.add(this);

        boolean started = false;
        try {
            start0();   //start0 為一個native方法幌衣,調(diào)用c來分配系統(tǒng)資源
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
 //run方法,如果target為空壤玫,則什么都不做豁护,否則執(zhí)行target的run方法。
 public void run() {
        if (target != null) {
            target.run();
        }
    }
 //調(diào)用getName方法可以返回線程的名字
 public final String getName() {
        return new String(name, true);
    }

 //由于主要介紹多線程欲间,Thread的其他方法不再過多的羅列楚里,可以自行查閱源碼文件
}

當使用第一種方式來生成線程對象的時候,必須要重寫run方法猎贴,因為Thread類中的run方法班缎,當target為空時,并不做任何的操作她渴。第二方式來生成線程對象時达址,需要實現(xiàn)run方法,然后使用new Thread(new myThread())來生成線程對象趁耗。target不為空沉唠,就會調(diào)用target類的run 方法 。

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

public class Thread3Test {
    public static void main(String[] args) {
        ThirdThread t3 = new ThirdThread() ;
        FutureTask<Integer> futureTask = new FutureTask<Integer>(t3);

        ThirdThread1 t4 = new ThirdThread1() ;
        FutureTask<Integer>  futureTask1 = new FutureTask<Integer>(t4);

        Thread t = new Thread(futureTask);
        Thread t1 = new Thread(futureTask1);
        t.start();
        t1.start();
        try {
            System.out.println(futureTask.get());
            System.out.println(futureTask1.get());
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class ThirdThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int i = 0 ;
        for ( ; i <5 ; i++){
            System.out.println("hello " + i );
        }
        return i ;
    }
}
class ThirdThread1 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int i = 0 ;
        for ( ; i <5 ; i++){
            System.out.println("world " + i );
        }
        return i ;
    }
}

//運行結(jié)果 苛败,多線程的運行結(jié)果是不可預(yù)測的满葛,可能回和我的運行結(jié)果不同
hello 0
world 0
hello 1
world 1
hello 2
world 2
world 3
world 4
hello 3
hello 4
5
5
main

為了說明效果,特寫了兩個類罢屈,啟動了兩個線程嘀韧,從結(jié)果看,多線程的效果確實是產(chǎn)生了儡遮。但是通過上面的代碼發(fā)現(xiàn),使用了FutureTask對實現(xiàn)了Callable接口的類暗赶,進行了包裝鄙币,然后將FutureTask實例傳入了Thread類的構(gòu)造方法中,這又是為啥蹂随? Thread的構(gòu)造方法只能接受Runable的類型十嘿,Call接口并沒有繼承Runable接口,無法作為參數(shù)傳入Thread構(gòu)造方法中岳锁,所以使用了FutureTask來進行包裝绩衷,因為FutureTask實現(xiàn)了Runable接口,并實現(xiàn)了run方法,在該方法中調(diào)用了Callable接口的call方法咳燕,并將call方法的返回值賦值給了result變量勿决。調(diào)用get()方法便能獲取返回值。

三種創(chuàng)建方式總結(jié):

通過繼承Thread類招盲,實現(xiàn)Runable接口和Callable接口都可以創(chuàng)建多線程低缩,繼承Thread類,并重寫run方法 曹货,可以直接通過調(diào)用start()方法實現(xiàn)多線程咆繁,實現(xiàn)Runable接口,則需要借助Thread類實現(xiàn)顶籽,將Runable實例玩般,傳入Thread的構(gòu)造方法中,在調(diào)用start()方法實現(xiàn)多線程礼饱。實現(xiàn)Callable接口坏为,則需要使用FutureTask來包裝,并將FutureTask實例傳入Thread的構(gòu)造方法中實現(xiàn)多線程慨仿。實現(xiàn)Runable和Callable接口的不同之處是久脯,Callable接口中的call方法可以返回值,并可以拋出異常镰吆。

線程的生命周期

線程要經(jīng)過:新建帘撰、就緒、運行万皿、阻塞摧找、死亡五種狀態(tài)。

生命周期的轉(zhuǎn)化如下:


線程的生命周期.png

線程同步

在多線程的環(huán)境中牢硅,可能會有兩個甚至多個的線程試圖同時訪問一個有限的資源蹬耘。對于這種潛在的資源沖突進行預(yù)防。解決的方法便是在資源上為其加鎖减余,對象加鎖之后综苔,其他線程便不能再訪問加鎖的資源。

線程安全的問題位岔,比較突出的便是銀行賬戶的問題如筛,好多書中都是拿這個在說明多線程對有限資源的競爭問題。

為了解決對這種有限資源的競爭抒抬,java提供了synchronized關(guān)鍵字來解決這個問題杨刨。 synchronized可以修飾方法,被synchronized修飾的方法稱為同步方法擦剑。

java中的每個對象都有一個鎖(lock)或者叫做監(jiān)視器(monitor),當訪問某個對象的synchronized方法時妖胀,表示將該對象上鎖芥颈,此時其他的任何線程都無法在去訪問該synchronized方法了,直到之前的那個線程執(zhí)行完畢后(或者拋出異常)赚抡,將該對象的鎖釋放掉爬坑,其他線程才有可能再去訪問synchronized方法。

對于同步方法而言怕品,無須顯式的指定同步監(jiān)視器妇垢,同步方法的同步監(jiān)視器是this,也就是對象本身肉康。線程在開始進行同步方法或者同步代碼塊之前闯估,必須先獲得對同步監(jiān)視器的鎖定。所以任意時刻只能有一個線程獲得對對象的訪問操作吼和。

synchronized方法是一種粗粒度的并發(fā)控制涨薪,synchronized塊是一種細粒度的并發(fā)控制,范圍更小一點炫乓。

Java5之后刚夺,java提供了一種功能更加強大的線程同步機制,顯示的定義同步鎖對象來實現(xiàn)同步末捣,在這種機制下侠姑,同步鎖使用Lock對象充當。

代碼如下:

class LockTest{
    private final ReentrantLock lock = new ReentrantLock();
    public void method(){
        lock.lock();
        try{
            ....
        }finally{
            lock.unlock() ;
        }
    }
}

使用Lock與使用同步方法有點相似箩做,使用Lock時時顯式使用Lock對象作為同步鎖莽红,同步方法是隱式使用當前對象作為同步監(jiān)視器。

死鎖

當兩個線程相互等待對象釋放同步監(jiān)視器時就會發(fā)生死鎖邦邦,一旦發(fā)生死鎖安吁,程序不會出現(xiàn)任何異常和任何提示,所以應(yīng)該避免線程之間相互等待對方釋放資源的情況出現(xiàn)燃辖。

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

傳統(tǒng)的線程通信鬼店,借助了Object類中的wait() ,notify(),notifyAll()三個方法,這三個方法并不屬于Thread類黔龟。

如果使用sychronized來保證同步妇智,通信則依靠下面的方法:

wait():導(dǎo)致當前線程阻塞,知道其他線程調(diào)用該同步監(jiān)視器的notify()或者notifyAll()方法氏身,該線程才會被喚醒巍棱,喚醒后并不會立即執(zhí)行,而是等待處理器的調(diào)度观谦。
notify():喚醒此同步監(jiān)視器中等待的單個線程拉盾。
notifyAll():喚醒同步監(jiān)視器中等待的所有線程桨菜。

wait()和notify() 或 wait()和notifyAll()是成對出現(xiàn)的豁状,因為他們依靠的都是同一個同步監(jiān)視器捉偏。

使用Condition控制線程通信

如果程序沒有使用synchronized來保證同步,而是使用了Lock對象來保證同步泻红,則系統(tǒng)中就不存在隱式的同步監(jiān)視器夭禽,也就不能使用wait(),notify(),notifyAll()方法進行通信了。 Java提供了Condition來進行線程之間的通信谊路。

Condition提供了3個方法來進行線程通信:
await(): 導(dǎo)致當前線程等待讹躯,知道其他線程調(diào)用signal()或者signalAll()方法
signal(): 喚醒此Lock對象上的單個線程。
signalAll():喚醒此Lock對象上的所有線程缠劝。

線程池

創(chuàng)建一個新的線程成本還是比較高的潮梯,因為它涉及了程序與操作系統(tǒng)之間的交互,所以使用線程池可以很好的提高性能惨恭,尤其是程序的線程的生命周期很短秉馏,需要頻繁的創(chuàng)建線程的時候。

Java5提供了內(nèi)建的線程池支持脱羡,新增了一個Executors工廠類來產(chǎn)生線程萝究。

  • newCachedThreadPool():創(chuàng)建一個具有緩存功能的線程池,系統(tǒng)根據(jù)需要創(chuàng)建線程锉罐,這些線程將會被緩存在線程池中 帆竹。
  • newFixedThreadPool(int nThreads): 創(chuàng)建一個可重用,固定線程數(shù)的線程池脓规。
  • newSingleThreadPool():創(chuàng)建一個只有單線程的線程池

上面三個方法返回ExecutorService對象代表一個線程池栽连,可以執(zhí)行Runnable對象或者Callable對象所代表的線程

下面這兩個方法返回一個ScheduledExecutorService線程池,是ExecutorService的子類抖拦,可以在指定的延遲后執(zhí)行任務(wù)升酣。

  • newScheduledThreadPool(int corePoolSize): 創(chuàng)建具有指定線程數(shù)的線程池,它可以在指定延遲后執(zhí)行線程任務(wù)态罪,corePoolSize指定池中保存的線程數(shù)噩茄,即使線程是空閑的也被保存在線程池中。
  • newSingleThreadScheduledExecutor():創(chuàng)建只有一個線程的線程池复颈,它可以在指定延遲后執(zhí)行線程任務(wù)绩聘。

線程池的簡單使用:

public class MyThread extends Thread{
    public void run(){
        for(itn i = 0 ; i <10 ; i++){
            System.out.println("hello world" + i) ;
        }
    }
}

public class Test{
    public static void main(String[] args){
        MyThread t = new MyThread();
        ExecutorService pool = Exectors.newFixedThreadPool(6);
        pool.submit(t) ;
        pool.shutdown();
    }
}


少年聽雨歌樓上,紅燭昏羅帳耗啦。  
壯年聽雨客舟中凿菩,江闊云低,斷雁叫西風(fēng)帜讲。
感謝支持衅谷!
                                        ---起個名忒難

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市似将,隨后出現(xiàn)的幾起案子获黔,更是在濱河造成了極大的恐慌蚀苛,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,222評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件玷氏,死亡現(xiàn)場離奇詭異堵未,居然都是意外死亡,警方通過查閱死者的電腦和手機盏触,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評論 3 385
  • 文/潘曉璐 我一進店門渗蟹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赞辩,你說我怎么就攤上這事雌芽。” “怎么了辨嗽?”我有些...
    開封第一講書人閱讀 157,720評論 0 348
  • 文/不壞的土叔 我叫張陵膘怕,是天一觀的道長。 經(jīng)常有香客問我召庞,道長岛心,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,568評論 1 284
  • 正文 為了忘掉前任篮灼,我火速辦了婚禮忘古,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘诅诱。我一直安慰自己髓堪,他們只是感情好,可當我...
    茶點故事閱讀 65,696評論 6 386
  • 文/花漫 我一把揭開白布娘荡。 她就那樣靜靜地躺著干旁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪炮沐。 梳的紋絲不亂的頭發(fā)上争群,一...
    開封第一講書人閱讀 49,879評論 1 290
  • 那天,我揣著相機與錄音大年,去河邊找鬼换薄。 笑死,一個胖子當著我的面吹牛翔试,可吹牛的內(nèi)容都是我干的轻要。 我是一名探鬼主播,決...
    沈念sama閱讀 39,028評論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼垦缅,長吁一口氣:“原來是場噩夢啊……” “哼冲泥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,773評論 0 268
  • 序言:老撾萬榮一對情侶失蹤凡恍,失蹤者是張志新(化名)和其女友劉穎幸冻,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咳焚,經(jīng)...
    沈念sama閱讀 44,220評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,550評論 2 327
  • 正文 我和宋清朗相戀三年庞溜,在試婚紗的時候發(fā)現(xiàn)自己被綠了革半。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,697評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡流码,死狀恐怖又官,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情漫试,我是刑警寧澤六敬,帶...
    沈念sama閱讀 34,360評論 4 332
  • 正文 年R本政府宣布,位于F島的核電站驾荣,受9級特大地震影響外构,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜播掷,卻給世界環(huán)境...
    茶點故事閱讀 40,002評論 3 315
  • 文/蒙蒙 一审编、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧歧匈,春花似錦垒酬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至斟冕,卻和暖如春口糕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背磕蛇。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評論 1 266
  • 我被黑心中介騙來泰國打工走净, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孤里。 一個月前我還...
    沈念sama閱讀 46,433評論 2 360
  • 正文 我出身青樓伏伯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捌袜。 傳聞我的和親對象是個殘疾皇子说搅,可洞房花燭夜當晚...
    茶點故事閱讀 43,587評論 2 350

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