線程&&多線程的實現(xiàn)

一逼庞、什么是線程

線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位蛇更,它被包含在進(jìn)程之中,是進(jìn)程中的實際運(yùn)作單位。程序員可以通過它進(jìn)行多處理器編程派任,你可以使用多線程對運(yùn)算密集型任務(wù)提速砸逊。

一組并發(fā)線程運(yùn)行在一個進(jìn)程的上下文中,每個線程都有它自己獨立的線程上下文掌逛,例如:棧师逸、程序計數(shù)器、線程ID豆混、條件碼篓像、寄存器集合等,每個線程和其它的線程一起共享除此之外的進(jìn)程上下文的剩余部分皿伺。這里有一點要特別注意员辩,就是寄存器是從不共享的,而虛擬存儲器總是共享的鸵鸥。

線程和進(jìn)程的區(qū)別:

  • 線程是進(jìn)程的一部分奠滑,所以線程有的時候被稱為是輕權(quán)進(jìn)程或者輕量級進(jìn)程。
  • 一個沒有線程的進(jìn)程是可以被看作單線程的脂男,如果一個進(jìn)程內(nèi)擁有多個進(jìn)程养叛,進(jìn)程的執(zhí)行過程不是一條線(線程)的,而是多條線(線程)共同完成的宰翅。
  • 系統(tǒng)在運(yùn)行的時候會為每個進(jìn)程分配不同的內(nèi)存區(qū)域弃甥,但是不會為線程分配內(nèi)存(線程所使用的資源是它所屬的進(jìn)程的資源),線程組只能共享資源汁讼。那就是說淆攻,除了CPU之外(線程在運(yùn)行的時候要占用CPU資源),計算機(jī)內(nèi)部的軟硬件資源的分配與線程無關(guān)嘿架,線程只能共享它所屬進(jìn)程的資源瓶珊。
  • 與進(jìn)程的控制表PCB相似,線程也有自己的控制表TCB耸彪,但是TCB中所保存的線程狀態(tài)比PCB表中少多了伞芹。
  • 進(jìn)程是系統(tǒng)所有資源分配時候的一個基本單位,擁有一個完整的虛擬空間地址蝉娜,并不依賴線程而獨立存在唱较。

二、實現(xiàn)線程

JAVA語言對線程的支持主要體現(xiàn)在Thread類和Runnable接口上面召川,因此JAVA實現(xiàn)多線程有兩種方法:①實現(xiàn)Runnable接口 ②繼承Thread類南缓。

2.1 線程創(chuàng)建及啟動

①線程創(chuàng)建

Thread()
Thread(String name)
Thread(Runnable target)
Thread(Runnable target,String name)

②線程啟動

void start()

注意別把start()方法和run()方法搞混了。start()方法被用來啟動新創(chuàng)建的線程荧呐,而且start()內(nèi)部調(diào)用了run()方法汉形,這和直接調(diào)用run()方法的效果不一樣纸镊。

1) start()
用start方法來啟動線程,真正實現(xiàn)了多線程運(yùn)行概疆,這時無需等待run方法體代碼執(zhí)行完畢而直接繼續(xù)執(zhí)行下面的代碼逗威。通過調(diào)用Thread類的start()方法來啟動一個線程,這時此線程處于就緒(可運(yùn)行)狀態(tài)岔冀,并沒有運(yùn)行庵楷,一旦得到cpu時間片,就開始執(zhí)行run()方法楣颠,這里方法run()稱為線程體,它包含了要執(zhí)行的這個線程的內(nèi)容咐蚯,run()方法運(yùn)行結(jié)束童漩,此線程隨即終止。

2) run()
run()方法只是類的一個普通方法而已春锋,如果直接調(diào)用run()方法矫膨,程序中依然只有主線程這一個線程,其程序執(zhí)行路徑還是只有一條期奔,還是要順序執(zhí)行侧馅,還是要等待run方法體執(zhí)行完畢后才可繼續(xù)執(zhí)行下面的代碼,這樣就沒有達(dá)到寫線程的目的呐萌。

總結(jié):調(diào)用start方法方可啟動線程馁痴,而run方法只是thread的一個普通方法調(diào)用,還是在主線程里執(zhí)行肺孤。

2.2 獲取線程的引用

static Thread currentThread():返回當(dāng)前運(yùn)行的線程引用

2.3 停止線程的方法
??如果我們想停止線程罗晕,不應(yīng)該用stop(),因為stop()使得線程戛然而止赠堵,完成了什么工作小渊,哪些工作還沒有做,都不知道茫叭,且清理工作也沒有做酬屉,所以stop()不是正確的停止線程方法,所以也被棄用了揍愁。
??之后Java API的設(shè)計者就沒有提供一個兼容且線程安全的方法來停止一個線程呐萨。當(dāng)run()或者call()方法執(zhí)行完的時候線程會自動結(jié)束,如果要手動結(jié)束一個線程吗垮,你可以用volatile布爾變量在線程執(zhí)行中設(shè)置狀態(tài)標(biāo)識來退出run()方法的循環(huán)或者是取消任務(wù)來中斷線程垛吗。

//volatile是可見性的關(guān)鍵,保證了線程正確讀取變量的值
volatile boolean keepRunning = true;

public void run(){
    while(keepRunning){
       //讓線程"發(fā)動5連擊"
       for(int i=0;i<5;i++){
         System.out.println(Thread.currentThread().getName() +"正在運(yùn)行");
         //讓出處理器時間烁登,使得所有線程能同時再去競爭CPU資源從而獲取運(yùn)行的機(jī)會
         Thread.yield();
       }
    }
    //完成最后一次業(yè)務(wù)后跳出while循環(huán)后怯屉,之后進(jìn)行一些清理工作
}

線程終止后蔚舀,其生命周期結(jié)束了,即進(jìn)入死亡態(tài)锨络,終止后的線程不能再被調(diào)度執(zhí)行赌躺。那么線程退出run()方法,是否代表該線程結(jié)束羡儿?還是釋放完內(nèi)存之后礼患?答案就是:執(zhí)行完run之后所有語句=該線程結(jié)束≠臨時內(nèi)存被回收。簡單來說掠归,就是線程結(jié)束就是run方法執(zhí)行結(jié)束之后缅叠,線程進(jìn)入死亡態(tài),他占用的那部分就有可能被回收虏冻,但不一定是立即被回收肤粱。

2.4 測試線程狀態(tài)的方法
??可以通過Thread 中的isAlive() 方法來獲取線程是否處于活動狀態(tài)。線程由start() 方法啟動后厨相,直到其被終止之間的任何時刻领曼,都處于'Alive'狀態(tài)。

2.5 線程的暫停和恢復(fù)
??有幾種方法可以暫停一個線程的執(zhí)行蛮穿,在適當(dāng)?shù)臅r候再恢復(fù)其執(zhí)行庶骄。

1.sleep() 方法
  當(dāng)前線程睡眠(停止執(zhí)行)若干毫秒,線程由運(yùn)行中狀態(tài)進(jìn)入不可運(yùn)行狀態(tài)践磅,停止執(zhí)行時間到后線程進(jìn)入可運(yùn)行狀態(tài)单刁。

static void sleep(long millis)
static void sleep(long millis,int nanos)
try{
    Thread.sleep(500);
}catch(InterruptedException e){
   e.printStackTrace();
}

線程休眠要點
①線程休眠總是暫停當(dāng)前線程
②在被喚醒并開始執(zhí)行前,線程休眠的實際時間取決于系統(tǒng)計時器和調(diào)度器府适。對比較清閑的系統(tǒng)來說,實際休眠的時間十分接近于指定的休眠時間,但對于繁忙的系統(tǒng),兩者之間的差距就較大窜骄。
③線程休眠并不會釋放當(dāng)前線程已經(jīng)獲取的任何鎖
④線程休眠并不會丟失當(dāng)前線程已經(jīng)獲取的任何監(jiān)視器虐骑。
⑤其他線程可以中斷當(dāng)前進(jìn)程的休眠,但會拋出InterruptedException異常颠黎。

2.join()
  join()的作用是:“等待該線程終止”文判。這里需要理解的就是該線程是指的主線程等待子線程的終止竞惋,也就是在子線程調(diào)用了join()方法后面的代碼浑厚,只有等到子線程結(jié)束了才能執(zhí)行。

void join()
void join(long millis)
void join(long millis,int nanos)
//加入join是為了讓Stage線程最后停止钠导,如果不加有可能Stage線程結(jié)束,actor線程還未停止
//好比導(dǎo)演喊停,演員還在演
public class Stage extends Thread(){
    public void run(){
          ActorRunnable actorTask = new ActorRunnable();
          Thread actor = new Thread(actorTask1,"演員1")窗宇;
          //當(dāng)前是Stage和actor兩個線程同時競爭CPU資源
          actor.start();
          try{
               actor.join();
          }catch(InterruptedException e){
               e.printStackTrace();
          }
    }
}

3)yield()
  yield()使得當(dāng)前運(yùn)行線程釋放處理器資源蝇完,但并不意味著退出和暫停,只是傻咖,告訴線程調(diào)度如果有人需要朋魔,可以先拿去,我過會再執(zhí)行卿操,沒人需要警检,我繼續(xù)執(zhí)行。但是它的這種主動退出沒有任何保障害淤,就是在當(dāng)前進(jìn)入可運(yùn)行狀態(tài)時扇雕,還是有可能被JVM選中再回到運(yùn)行狀態(tài)的。

static void yield()
Thread.yield( ):使當(dāng)前線程從執(zhí)行狀態(tài)(運(yùn)行狀態(tài))變?yōu)榭蓤?zhí)行態(tài)(就緒狀態(tài))窥摄。

2.6 使用線程代碼示例

public class ActorRunnable implements Runnable{
    volatile boolean keepRunning = true;
    public void run(){
       while(keepRunning){
       //讓線程"發(fā)動5連擊"
       for(int i=0;i<5;i++){
         System.out.println(Thread.currentThread().getName() +"正在運(yùn)行");
         //讓出處理器時間镶奉,使得所有線程能同時再去競爭CPU資源從而獲取運(yùn)行的機(jī)會
         Thread.yield();
       }
    }
}

public class Stage extends Thread(){
    public void run(){
          //演員1的任務(wù)
          ActorRunnable actorTask1 = new ActorRunnable();
          //演員2的任務(wù)
          ActorRunnable actorTask2 = new ActorRunnable();
          //Thread中構(gòu)造方法里面的runnable對象用于提供線程業(yè)務(wù)接口run方法
          Thread actor1 = new Thread(actorTask1,"演員1");
          Thread actor2 = new Thread(actorTask2,"演員2")崭放;
          //actor1哨苛、actor2兩個線程由本線程啟動
          actor1.start();
          actor2.start();
          //讓舞臺線程暫時休眠。因為在本實例中是actor1币砂、actor2和stage三個線程共同競爭CPU資源的
          //由于當(dāng)stage競爭到了cpu時就會終止另外兩個線程建峭,所以在stage線程中調(diào)用了sleep方法來給actor1和actor2方法提供最少50ms的運(yùn)行時間。
          try{
               Thread.sleep(500);
          }catch(InterruptedException e){
               e.printStackTrace();
          }
          //只有當(dāng)Thread.yield()的時候决摧,選中的是stage線程迹缀,才能執(zhí)行到下面停止actor的方法。
          //但是actor1停止了并不意味著actor2也會停止蜜徽,因為執(zhí)行完actor1.keepRunning = false后祝懂,可能stage線程的cpu資源被搶走了
          actor1.keepRunning = false;          
          actor2.keepRunning = false;
    }
}

public void main(String[] args){
     new stage().start();
}

三、Thread VS Runnable

我們剛接觸的時候可能會迷糊繼承Thread類和實現(xiàn)Runnable接口實現(xiàn)多線程拘鞋,其實在接觸后我們會發(fā)現(xiàn)這完全是兩個不同的實現(xiàn)多線程砚蓬。

  • Runnable方式可以避免Thread方式由于java單繼承特性帶來的缺陷
  • Runnable的代碼可以被多個線程(Thread實例)共享,適合于多個線程處理同一資源的情況盆色;而Thread方式是多個線程分別完成自己的任務(wù)灰蛙。

第二個是什么意思呢祟剔?我們用代碼來說明。假設(shè)到年關(guān)了摩梧,現(xiàn)在還剩下五張火車票物延,那現(xiàn)在有三個窗口去賣這五張火車票,我們用三個線程去模擬這三個窗口去同時賣這五張火車票仅父。

用Thread實現(xiàn)賣票系統(tǒng)
class MyThread extends Thread{
   private int ticketsCont = 5; //5張火車票
   private String name ; //窗口叛薯,即線程名字

   public MyThread(String name){
       this.name = name;
   }
 
   public void run(){
      while(ticketCont>0){
        ticketCont--;
        System.out.println(name+"賣掉了一張票,剩余票數(shù)為:"+ticketCont);
      }
   }
}

public class TicketThread{
  public void main(String[] args){
    //創(chuàng)建三個線程笙纤,模擬三個窗口賣票
    MyThread mt1 = new MyThread("窗口1");
    MyThread mt2 = new MyThread("窗口2");
    MyThread mt3 = new MyThread("窗口3");

    //啟動三個線程耗溜,也即是開始賣票
    mt1.start();
    mt2.start();
    mt3.start();
  }
}

運(yùn)行結(jié)果:總共有5張票,但是卻賣了15張票


用Runnable實現(xiàn)賣票系統(tǒng)
class MyThread implements Runnable{
   private int ticketsCont = 5; //5張火車票

   public void run(){
      while(ticketCont>0){
        ticketCont--;
        System.out.println(Thread.currentThread().getName()+"賣掉了一張票省容,剩余票數(shù)為:"+ticketCont);
      }
   }
}

public class TicketThread{
  public void main(String[] args){
    MyThread mt = new MyThread();
    //創(chuàng)建三個線程抖拴,模擬三個窗口賣票
    Thread th1 = new Thread(mt,"窗口2");
    Thread th2 = new Thread(mt,"窗口2");
    Thread th3 = new Thread(mt,"窗口3");

    //啟動三個線程,也即是開始賣票
    th1.start();
    th2.start();
    th3.start();
  }
}

運(yùn)行結(jié)果:正常賣出五張票



??在Runnable模擬的方法中腥椒,因為MyThread實現(xiàn)Runnable接口的阿宅。MyThread mt = new MyThread();之后我們把mt傳給三個線程對象,也就是說三個線程傳遞的對象是同一個Runnable對象笼蛛,所以三個線程對象用的都是同一個Runnable對象里面的代碼洒放,自然而然資源也是共享的,所以它們?nèi)齻€加在一起總共賣了五張火車票伐弹。
??但是為什么打印出來剩余票數(shù)是4、1榨为、3惨好、0、2呢随闺?因為線程的執(zhí)行時間是隨機(jī)的日川。首先線程1啟動,獲取到CPU資源矩乐,然后轉(zhuǎn)到run方法去執(zhí)行龄句。執(zhí)行完之后又把CPU資源讓出來,票數(shù)剩余4張散罕。這個時候主線程又繼續(xù)執(zhí)行分歇,然后線程1沒有獲取了CPU資源,就在等待欧漱,線程2獲取到了CPU資源就去執(zhí)行run方法职抡,剩余票數(shù)變成3張......

四、守護(hù)線程

JAVA線程有兩類:
①用戶線程

  • 運(yùn)行在前臺误甚,執(zhí)行具體的任務(wù)
  • 程序的主線程缚甩、連接網(wǎng)絡(luò)的子線程等都是用戶線程

②守護(hù)線程

  • 運(yùn)行在后臺谱净,為其他前臺線程服務(wù)
  • 一旦所有用戶線程都運(yùn)行結(jié)束,守護(hù)線程會隨JVM一起結(jié)束工作
  • 最常見的守護(hù)線程就是垃圾回收線程擅威、數(shù)據(jù)庫連接池中的監(jiān)測線程壕探、JVM虛擬機(jī)啟動后的監(jiān)測線程。

如何設(shè)置守護(hù)線程郊丛?可以通過調(diào)用Thread類的setDaemon(true)方法來設(shè)置當(dāng)前的線程為守護(hù)線程李请。要注意的是,setDaemon(true)必須在start()方法之前調(diào)用宾袜,否則會拋出IllegalThreadStateException異常

public class DeamonThredemo{
  public static void main(String[] args){
    //這里的主線程就是main
    System.out.println("進(jìn)入主線程"+Thread.currentThread().getName());
    //deamonThread實現(xiàn)了Runnable接口
    DeamonThread deamonThread = new DeamonThread();
    Thread thread = new Thread(deamonThread);
    //將thread設(shè)置為守護(hù)線程
    thread.setDaemon(true)
    //啟動守護(hù)線程
    thread.start();
    //主線程工作捻艳。在主線程工作的同時守護(hù)線程會一直工作,直到主線程退出
    System.out.println("程序退出了主線程"+Thread.currentThread().getName());
  }
}

但不是所有的任務(wù)都可以分配給守護(hù)線程來運(yùn)行庆猫,比如讀寫操作或者計算邏輯為什么呢认轨?
??一旦所有的用戶線程都退出運(yùn)行了,守護(hù)線程也覺得自己的存在沒有必要了月培,因為它沒有守護(hù)的對象了嘁字,這時守護(hù)線程也會結(jié)束掉工作。如果我在守護(hù)線程里面做了一些讀寫操作杉畜,而當(dāng)這個讀寫操作做到一半的時候纪蜒,所有的用戶線程都退出來了,這個時候守護(hù)線程也沒有存在的必要了此叠,他也會結(jié)束掉自己纯续。但是這個時候讀寫操作還沒進(jìn)行完,守護(hù)線程就退出來了灭袁,那程序就崩潰了猬错。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市茸歧,隨后出現(xiàn)的幾起案子倦炒,更是在濱河造成了極大的恐慌,老刑警劉巖软瞎,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逢唤,死亡現(xiàn)場離奇詭異,居然都是意外死亡涤浇,警方通過查閱死者的電腦和手機(jī)鳖藕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來只锭,“玉大人吊奢,你說我怎么就攤上這事。” “怎么了页滚?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵召边,是天一觀的道長。 經(jīng)常有香客問我裹驰,道長隧熙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任幻林,我火速辦了婚禮贞盯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沪饺。我一直安慰自己躏敢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布整葡。 她就那樣靜靜地躺著件余,像睡著了一般。 火紅的嫁衣襯著肌膚如雪遭居。 梳的紋絲不亂的頭發(fā)上啼器,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機(jī)與錄音俱萍,去河邊找鬼端壳。 笑死,一個胖子當(dāng)著我的面吹牛枪蘑,可吹牛的內(nèi)容都是我干的损谦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼岳颇,長吁一口氣:“原來是場噩夢啊……” “哼照捡!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赦役,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤麻敌,失蹤者是張志新(化名)和其女友劉穎栅炒,沒想到半個月后掂摔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡赢赊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年乙漓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片释移。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡叭披,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出玩讳,到底是詐尸還是另有隱情涩蜘,我是刑警寧澤嚼贡,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站同诫,受9級特大地震影響粤策,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜误窖,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一叮盘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧霹俺,春花似錦柔吼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至艇棕,卻和暖如春蝌戒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沼琉。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工北苟, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人打瘪。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓友鼻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親闺骚。 傳聞我的和親對象是個殘疾皇子彩扔,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,955評論 2 355

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