Java基礎(chǔ)知識(shí)復(fù)習(xí)筆記(3)--線程基礎(chǔ)

一饲齐、線程概念

1. 操作系統(tǒng)中的線程
現(xiàn)在的操作系統(tǒng)是多任務(wù)操作系統(tǒng)爽待,多線程是實(shí)現(xiàn)多任務(wù)的一種方式,在操作系統(tǒng)中遏考,每一個(gè)進(jìn)程都有操作系統(tǒng)分配給它的獨(dú)立內(nèi)存空間慈鸠,線程是進(jìn)程中的一個(gè)執(zhí)行流程,一個(gè)進(jìn)程中可以啟動(dòng)多個(gè)線程灌具。線程總是屬于某一個(gè)進(jìn)程青团,進(jìn)程中的多線程共享進(jìn)程的內(nèi)存譬巫。
2. Java中的線程
在Java中,線程指兩件不同的事情:一是java.lang.Thread類的一個(gè)實(shí)例壶冒,二是線程的執(zhí)行缕题。

  • 使用java.lang.Thread類或者java.lang.Runnable接口編寫代碼來定義、實(shí)例化和啟動(dòng)新線程胖腾。
  • 一個(gè)Thread類的實(shí)例只是一個(gè)對(duì)象烟零,像Java中的任何其他對(duì)象一樣,具有變量和方法咸作,生死與堆上锨阿。
  • Java中每個(gè)線程都有一個(gè)自己的調(diào)用棧,即使不在程序中創(chuàng)建任何新線程记罚,線程也在后臺(tái)運(yùn)行著墅诡。一旦創(chuàng)建一個(gè)新的線程,就產(chǎn)生一個(gè)新的調(diào)用棧桐智。

線程棧:
Java為了實(shí)現(xiàn)平臺(tái)無關(guān)性, 必須解決不同操作系統(tǒng)中進(jìn)程末早,線程的差異,因此Java建立了一套自己的進(jìn)程與線程機(jī)制说庭。 這套機(jī)制與windows系統(tǒng)的頗為相似然磷,但是底層實(shí)現(xiàn)確實(shí)根據(jù)不同平臺(tái)的機(jī)制進(jìn)行實(shí)現(xiàn)。
線程棧存儲(chǔ)的信息是指某時(shí)刻線程中方法調(diào)度的信息刊驴,當(dāng)前調(diào)用的方法總是位于棧頂姿搜。 當(dāng)某個(gè)方法被調(diào)用時(shí),此方法的相關(guān)信息壓入棧頂捆憎。

  • 一個(gè)Java應(yīng)用總是從main()方法開始運(yùn)行舅柜,main()方法運(yùn)行在一個(gè)線程內(nèi),它被稱為主線程躲惰。
  • 線程總體分兩類:用戶線程和守候線程致份。當(dāng)所有用戶線程執(zhí)行完畢的時(shí)候,JVM自動(dòng)關(guān)閉礁扮。但是守候線程卻不獨(dú)立于JVM知举,守候線程一般是由操作系統(tǒng)或者用戶自己創(chuàng)建的。

守護(hù)線程的作用是為其他線程的運(yùn)行提供服務(wù)太伊,比如說GC線程雇锡。其實(shí)用戶線程和守護(hù)線程本質(zhì)上來說去沒啥區(qū)別的,唯一的區(qū)別之處就在虛擬機(jī)的離開:如果用戶線程全部撤離僚焦,那么守護(hù)線程也就沒啥線程好服務(wù)的了锰提,所以虛擬機(jī)也就退出了。

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

1. 線程的創(chuàng)建
Java創(chuàng)建線程有兩種方法立肘,
第一種:繼承java.lang.Thread類边坤,重寫run方法;
第二種谅年,實(shí)現(xiàn)java.lang.Runnable接口茧痒,實(shí)現(xiàn)run方法。

兩種生成線程對(duì)象的區(qū)別:
1.兩種方法均需執(zhí)行線程的start方法為線程分配必須的系統(tǒng)資源融蹂、調(diào)度線程運(yùn)行并執(zhí)行線程的run方法旺订。
2.在具體應(yīng)用中,采用哪種方法來構(gòu)造線程體要視情況而定超燃。通常区拳,當(dāng)一個(gè)線程已繼承了另一個(gè)類時(shí),就應(yīng)該用第二種方法來構(gòu)造,即實(shí)現(xiàn)Runnable接口。

2. 線程的實(shí)例化
第一種方法的創(chuàng)建的線程屡贺,直接new該線程類即可。

//Thread的構(gòu)造函數(shù)
public Thread( );
public Thread(Runnable target);
public Thread(String name);
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, Runnable target);
public Thread(ThreadGroup group, String name);
public Thread(ThreadGroup group, Runnable target, String name);
public Thread(ThreadGroup group, Runnable target, String name, long stackSize);

第二種方法創(chuàng)建的線程笆凌,需要調(diào)用Thread的構(gòu)造方法進(jìn)行實(shí)例化。

//實(shí)現(xiàn)runnable接口的類士葫,調(diào)用Thread的構(gòu)造方法進(jìn)行實(shí)例化
Thread(Runnable target) 
Thread(Runnable target, String name) 
Thread(ThreadGroup group, Runnable target) 
Thread(ThreadGroup group, Runnable target, String name) 
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

3. 啟動(dòng)線程

  • 在線程的Thread對(duì)象上調(diào)用start()方法來啟動(dòng)線程菩颖,可不是run()喲。
  • run()方法沒有任何特別之處为障。它只是新線程知道調(diào)用的方法名稱(和簽名)。因此放祟,在Runnable上或者Thread上調(diào)用run方法是合法的鳍怨,但并不啟動(dòng)新的線程。
  • 在調(diào)用start()方法之前:線程處于新狀態(tài)中跪妥,新狀態(tài)指有一個(gè)Thread對(duì)象鞋喇,但還沒有一個(gè)真正的線程。
  • 而在調(diào)用start()方法后眉撵,才會(huì)啟動(dòng)執(zhí)行線程侦香,該線程從新狀態(tài)變?yōu)榭蛇\(yùn)行狀態(tài),當(dāng)線程獲得執(zhí)行機(jī)會(huì)后纽疟,再運(yùn)行run()方法罐韩。

4. 創(chuàng)建線程例子
通過繼承Thread創(chuàng)建線程

package tym.ThreadBase.create;
/** *Created by TyiMan on 2016/5/14. */
public class MyThread extends Thread {   
  public MyThread() {       
    super();    
  }    
  public MyThread(String name) {       
    super(name);    
  }    
  public void run() {        
    for (int i = 0; i < 3; i++)                    
      System.out.println(Thread.currentThread().getName() + " " + i);    
  }
}

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

package tym.ThreadBase.create;
/** * Created by TyiMan on 2016/5/14. */
public class MyRunnable implements Runnable {    
  @Override    
  public void run() {        
    for(int i = 0;i<3;i++)        
      System.out.println(Thread.currentThread().getName()+" "+i);     
  }
}

主函數(shù)實(shí)例化線程并且運(yùn)行

package tym.ThreadBase.create;
/** * Created by TyiMan on 2016/5/14. */
public class Main {    
  public static void main(String[]args){       
    Thread thread1 = new MyThread();        
    Thread thread2 = new Thread(new MyRunnable());        
    Thread thread3 = new MyThread("MyThread");        
    Thread thread4 = new Thread(new MyRunnable(),"MyRunnable");       

    thread1.start();        
    thread2.start();       
    thread3.start();        
    thread4.start();    
  }
}

執(zhí)行結(jié)果:

//執(zhí)行結(jié)果是按照順序的,如果循環(huán)次數(shù)更多污朽,他們的輸出就會(huì)這么有序了
Thread-0 0
Thread-0 1
Thread-0 2
Thread-1 0
Thread-1 1
Thread-1 2
MyThread 0
MyThread 1
MyThread 2
MyRunnable 0
MyRunnable 1
MyRunnable 2

一些常見問題
1散吵、線程的名字,名字有兩個(gè)來源,一個(gè)是虛擬機(jī)自己給的名字矾睦,一個(gè)是你自己的定的名字(構(gòu)造函數(shù)傳線程名字或者setName方法)晦款。在沒有指定線程名字的情況下,虛擬機(jī)總會(huì)為線程指定名字枚冗,并且主線程的名字總是main缓溅,非主線程的名字Thread-number(該number將是自動(dòng)增加的,并被所有的Thread對(duì)象所共享赁温,因?yàn)樗莝tatic的成員變量)坛怪。
2、獲取當(dāng)前線程的對(duì)象的方法是:Thread.currentThread()束世;
3酝陈、想成的運(yùn)行,只能保證:每個(gè)線程都將啟動(dòng)毁涉,每個(gè)線程都將運(yùn)行直到完成沉帮。一系列線程以某種順序啟動(dòng)并不意味著將按該順序執(zhí)行。對(duì)于任何一組啟動(dòng)的線程來說贫堰,調(diào)度程序不能保證其執(zhí)行次序穆壕,持續(xù)時(shí)間也無法保證。
4其屏、當(dāng)線程目標(biāo)run()方法結(jié)束時(shí)該線程完成喇勋。
5、一旦線程啟動(dòng)偎行,它就永遠(yuǎn)不能再重新啟動(dòng)川背。只有一個(gè)新的線程可以被啟動(dòng),并且只能一次蛤袒。一個(gè)可運(yùn)行的線程或死線程可以被重新啟動(dòng)熄云。
6、線程的調(diào)度是JVM的一部分妙真,在一個(gè)CPU的機(jī)器上上缴允,實(shí)際上一次只能運(yùn)行一個(gè)線程。一次只有一個(gè)線程棧執(zhí)行珍德。JVM線程調(diào)度程序決定實(shí)際運(yùn)行哪個(gè)處于可運(yùn)行狀態(tài)的線程练般。眾多可運(yùn)行線程中的某一個(gè)會(huì)被選中做為當(dāng)前線程⌒夂颍可運(yùn)行線程被選擇運(yùn)行的順序是沒有保障的薄料。

三、線程的生命周期

與人有生老病死一樣晴及,線程要經(jīng)歷新建都办、就緒嫡锌、運(yùn)行、死亡和阻塞這5種不同的狀態(tài)琳钉。

線程的生命周期

新建(new Thread)
當(dāng)創(chuàng)建Thread類的一個(gè)實(shí)例(對(duì)象)時(shí)势木,此線程進(jìn)入新建狀態(tài)(未被啟動(dòng))。例如:Thread t1=new Thread();

就緒(runnable)
線程已經(jīng)被啟動(dòng)歌懒,正在等待被分配給CPU時(shí)間片啦桌,也就是說此時(shí)線程正在就緒隊(duì)列中排隊(duì)等候得到CPU資源。例如:t1.start();

運(yùn)行(running)
線程獲得CPU資源正在執(zhí)行任務(wù)(調(diào)用run()方法)及皂,此時(shí)除非此線程自動(dòng)放棄CPU資源或者有優(yōu)先級(jí)更高的線程進(jìn)入甫男,線程將一直運(yùn)行到結(jié)束。

死亡(dead)
當(dāng)線程執(zhí)行完畢或被其它線程殺死验烧,線程就進(jìn)入死亡狀態(tài)板驳,這時(shí)線程不可能再進(jìn)入就緒狀態(tài)等待執(zhí)行。
自然終止:正常運(yùn)行run()方法后終止
異常終止:調(diào)用stop()方法讓一個(gè)線程終止運(yùn)行

阻塞(blocked)
由于某種原因?qū)е抡谶\(yùn)行的線程讓出CPU并暫停自己的執(zhí)行碍拆,即進(jìn)入阻塞狀態(tài)若治。
正在睡眠:sleep(long t) 方法可使線程進(jìn)入睡眠方式。一個(gè)睡眠著的線程在指定的時(shí)間過去可進(jìn)入就緒狀態(tài)感混。
正在等待:調(diào)用wait()方法端幼。(調(diào)用notify()方法回到就緒狀態(tài))
被另一個(gè)線程所阻塞:調(diào)用suspend()方法。(調(diào)用resume()方法恢復(fù))

下面給出了Thread類中和各個(gè)狀態(tài)相關(guān)的方法弧满。

// 開始線程
public void start( );
public void run( );
// 掛起和喚醒線程
public void resume( );     // 不建議使用
public void suspend( );    // 不建議使用
public static void sleep(long millis);
public static void sleep(long millis, int nanos);
public static void yied() //可以對(duì)當(dāng)前線程進(jìn)行臨時(shí)暫停(讓線程將資源釋放出來)
// 終止線程
public void stop( );       // 不建議使用
public void interrupt( );
// 得到線程狀態(tài)
public boolean isAlive( );
public boolean isInterrupted( );
public static boolean interrupted( );
//join方法讓線程加入執(zhí)行婆跑,執(zhí)行某一線程join方法的線程會(huì)被凍結(jié),
//等待某一線程執(zhí)行結(jié)束庭呜,該線程才會(huì)恢復(fù)到可運(yùn)行狀態(tài)
public void join( ) throws InterruptedException;

四滑进、線程的同步與鎖

線程的同步是為了防止多個(gè)線程訪問一個(gè)數(shù)據(jù)對(duì)象時(shí),對(duì)數(shù)據(jù)造成的破壞募谎。
Java線程鎖的原理

  • Java中每個(gè)對(duì)象都有一個(gè)內(nèi)置鎖郊供,當(dāng)程序運(yùn)行到非靜態(tài)的synchronized同步方法上時(shí),自動(dòng)獲得與正在執(zhí)行代碼類的當(dāng)前實(shí)例(this實(shí)例)有關(guān)的鎖近哟。
  • 獲得一個(gè)對(duì)象的鎖也稱為獲取鎖、鎖定對(duì)象鲫寄、在對(duì)象上鎖定或在對(duì)象上同步吉执。當(dāng)程序運(yùn)行到synchronized同步方法或代碼塊時(shí)該對(duì)象鎖才起作用。
  • 一個(gè)對(duì)象只有一個(gè)鎖地来。所以戳玫,如果一個(gè)線程獲得該鎖,就沒有其他線程可以獲得鎖未斑,直到第一個(gè)線程釋放(或返回)鎖咕宿。這也意味著任何其他線程都不能進(jìn)入該對(duì)象上的synchronized方法或代碼塊,直到該鎖被釋放。
    *釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊府阀。

Java線程同步的synchronized關(guān)鍵字的使用

關(guān)于同步與鎖的要點(diǎn):
1)只能同步方法缆镣,而不能同步變量和類;
2)每個(gè)對(duì)象只有一個(gè)鎖试浙;當(dāng)提到同步時(shí)董瞻,應(yīng)該清楚在什么上同步?也就是說田巴,在哪個(gè)對(duì)象上同步钠糊?
3)不必同步類中所有的方法,類可以同時(shí)擁有同步和非同步方法壹哺。
4)如果兩個(gè)線程要執(zhí)行一個(gè)類中的synchronized方法抄伍,并且兩個(gè)線程使用相同的實(shí)例來調(diào)用方法,那么一次只能有一個(gè)線程能夠執(zhí)行方法管宵,另一個(gè)需要等待截珍,直到鎖被釋放。也就是說:如果一個(gè)線程在對(duì)象上獲得一個(gè)鎖啄糙,就沒有任何其他線程可以進(jìn)入(該對(duì)象的)類中的任何一個(gè)同步方法笛臣。
5)如果線程擁有同步和非同步方法,則非同步方法可以被多個(gè)線程自由訪問而不受鎖的限制隧饼。
6)線程睡眠時(shí)沈堡,它所持的任何鎖都不會(huì)釋放。
7)線程可以獲得多個(gè)鎖燕雁。比如诞丽,在一個(gè)對(duì)象的同步方法里面調(diào)用另外一個(gè)對(duì)象的同步方法,則獲取了兩個(gè)對(duì)象的同步鎖拐格。
8)同步損害并發(fā)性僧免,應(yīng)該盡可能縮小同步范圍。同步不但可以同步整個(gè)方法捏浊,還可以同步方法中一部分代碼塊懂衩。
9)在使用同步代碼塊時(shí)候,應(yīng)該指定在哪個(gè)對(duì)象上同步金踪,也就是說要獲取哪個(gè)對(duì)象的鎖浊洞。要保證多個(gè)線程的同步,被鎖定的對(duì)象胡岔,在它們之間是共享的(就是多個(gè)線程使用的同一個(gè)對(duì)象的鎖)

(1)同步方法
使用synchronized關(guān)鍵字修飾方法法希。 當(dāng)用此關(guān)鍵字修飾方法時(shí),內(nèi)置鎖會(huì)保護(hù)整個(gè)方法靶瘸。在調(diào)用該方法前苫亦,需要獲得內(nèi)置鎖毛肋,否則就處于阻塞狀態(tài)。

public class TestSynClass extends Thread{{

 public synchronized void synMethod(){
  //some codes
 }
}

(2)同步代碼塊
使用synchronized關(guān)鍵字修飾代碼語句塊屋剑。 被該關(guān)鍵字修飾的語句塊會(huì)自動(dòng)被加上內(nèi)置鎖润匙,從而實(shí)現(xiàn)同步。使用synchronized關(guān)鍵字將需要互斥的代碼包含起來饼丘,并上一把鎖趁桃。并且鎖定的對(duì)象必須是多個(gè)線程之間共享的對(duì)象。(如下面實(shí)例中第三個(gè)就是無效的同步代碼塊)

public class TestSynClass extends Thread{
 private Object object = new Object();

 public void synThisClass(){
  synchronized(this){
   //some code
  }
 }
 
 public void synOtherObject(){
  synchronized(object){
   //some code
  }
 }

  public void synDifferentObject(){
    //這個(gè)方法是不能實(shí)現(xiàn)同步的肄鸽,因?yàn)槊看芜\(yùn)行都會(huì)生成一個(gè)新的Object對(duì)象
    //不同調(diào)用者調(diào)用的是不同對(duì)象
    synchronized(new Object){
      //some code
    }
  }
}

(3)synchronized作用于static 函數(shù)
要同步靜態(tài)方法卫病,一是在靜態(tài)方法上加synchronized關(guān)鍵字,另一個(gè)是在整個(gè)類對(duì)象的鎖典徘,這個(gè)對(duì)象是就是這個(gè)類(XXX.class)蟀苛。
public class TestSynClass extends Thread{{

 public synchronized static void methodA()      { 
   //some code
  }    
  public static void methodB()    {       
    synchronized(TestSynClass.class) {
      //some code
    }  
  }
}

線程同步小結(jié)
1、線程同步的目的是為了保護(hù)多個(gè)線程反問一個(gè)資源時(shí)對(duì)資源的破壞逮诲。
2帜平、線程同步方法是通過鎖來實(shí)現(xiàn),每個(gè)對(duì)象都有切僅有一個(gè)鎖梅鹦,這個(gè)鎖與一個(gè)特定的對(duì)象關(guān)聯(lián)裆甩,線程一旦獲取了對(duì)象鎖,其他訪問該對(duì)象的線程就無法再訪問該對(duì)象的其他同步方法齐唆。
3嗤栓、對(duì)于靜態(tài)同步方法,鎖是針對(duì)這個(gè)類的箍邮,鎖對(duì)象是該類的Class對(duì)象茉帅。靜態(tài)和非靜態(tài)方法的鎖互不干預(yù)。一個(gè)線程獲得鎖锭弊,當(dāng)在一個(gè)同步方法中訪問另外對(duì)象上的同步方法時(shí)堪澎,會(huì)獲取這兩個(gè)對(duì)象鎖。
4味滞、對(duì)于同步樱蛤,要時(shí)刻清醒在哪個(gè)對(duì)象上同步,這是關(guān)鍵剑鞍。
5刹悴、編寫線程安全的類,需要時(shí)刻注意對(duì)多個(gè)線程競(jìng)爭(zhēng)訪問資源的邏輯和安全做出正確的判斷攒暇,對(duì)“原子”操作做出分析,并保證原子操作期間別的線程無法訪問競(jìng)爭(zhēng)資源子房。
6形用、當(dāng)多個(gè)線程等待一個(gè)對(duì)象鎖時(shí)就轧,沒有獲取到鎖的線程將發(fā)生阻塞。
7田度、死鎖是線程間相互等待鎖鎖造成的妒御,在實(shí)際中發(fā)生的概率非常的小。真讓你寫個(gè)死鎖程序镇饺,并不一定好使乎莉。但是,一旦程序發(fā)生死鎖奸笤,程序?qū)⑺赖簟?/p>

五惋啃、線程的交互與調(diào)度(線程狀態(tài)轉(zhuǎn)換實(shí)例)

1. 線程的交互
線程交互的方法

  • void notify() 喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線程。
  • void notifyAll() 喚醒在此對(duì)象監(jiān)視器上等待的所有線程监右,應(yīng)該在同步塊中調(diào)用边灭。
  • void wait()導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 方法健盒。
    當(dāng)然绒瘦,wait()還有另外兩個(gè)重載方法:
    void wait(long timeout)導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 方法扣癣,或者超過指定的時(shí)間量惰帽。
    void wait(long timeout, int nanos) 導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 方法父虑,或者其他某個(gè)線程中斷當(dāng)前線程该酗,或者已超過某個(gè)實(shí)際時(shí)間量。

關(guān)于線程/通知要關(guān)鍵點(diǎn)
(1)必須從同步環(huán)境內(nèi)調(diào)用wait()频轿、notify()垂涯、notifyAll()方法。線程不能調(diào)用對(duì)象上等待或通知的方法航邢,除非它擁有那個(gè)對(duì)象的鎖耕赘。
(2)wait()、notify()膳殷、notifyAll()都是Object的實(shí)例方法操骡。與每個(gè)對(duì)象具有鎖一樣,每個(gè)對(duì)象可以有一個(gè)線程列表赚窃,他們等待來自該信號(hào)(通知)册招。線程通過執(zhí)行對(duì)象上的wait()方法獲得這個(gè)等待列表。從那時(shí)候起勒极,它不再執(zhí)行任何其他指令是掰,直到調(diào)用對(duì)象的notify()方法為止。如果多個(gè)線程在同一個(gè)對(duì)象上等待辱匿,則將只選擇一個(gè)線程(不保證以何種順序)繼續(xù)執(zhí)行键痛。如果沒有線程等待炫彩,則不采取任何特殊操作。

線程交互的實(shí)例

  • wait()/notify()的使用

當(dāng)在對(duì)象上調(diào)用wait()方法時(shí)絮短,執(zhí)行該代碼的線程立即放棄它在對(duì)象上的鎖江兢。然而調(diào)用notify()時(shí),并不意味著這時(shí)線程會(huì)放棄其鎖丁频。如果線程榮然在完成同步代碼杉允,則線程在移出之前不會(huì)放棄鎖。因此席里,只要調(diào)用notify()并不意味著這時(shí)該鎖變得可用叔磷。

主函數(shù)實(shí)例化線程,然后調(diào)用線程的wait()函數(shù)胁勺,等待線程計(jì)算1到100的和的結(jié)果世澜。

package tym.ThreadBase.waitAndnotify;
/** * Created by TyiMan on 2016/5/16. */
public class TestWaitNotifyMain {   
  public static void main(String[] args){        
    TestThread thread = new TestThread();        
    thread.start();        
    synchronized (thread){            
      try{                
        System.out.println("等待對(duì)象b完成計(jì)算……");               
        thread.wait();           
      } catch (InterruptedException e) {                
        e.printStackTrace();            
      }            
      System.out.println("線程計(jì)算結(jié)果為 total is "+thread.total);        
    }    
  }
}

TestThread類的run方法計(jì)算1到100的和,計(jì)算完后署穗,調(diào)用notify()函數(shù)寥裂。

package tym.ThreadBase.waitAndnotify;
/** * Created by TyiMan on 2016/5/16. */
public class TestThread extends Thread {    
  int total ;    
  @Override    
  public void run(){        
    synchronized (this){           
      for(int i = 0;i<=100;i++){                
       total +=i;            
      }            
      notify();        
    }    
  }
}
  • wait()/notifyAll()的使用
    在多數(shù)情況下,最好通知等待某個(gè)對(duì)象的所有線程案疲。如果這樣做封恰,可以在對(duì)象上使用notifyAll()讓所有在此對(duì)象上等待的線程沖出等待區(qū),返回到可運(yùn)行狀態(tài)褐啡。

Calculator計(jì)算1到100的和诺舔,計(jì)算完喚醒所有其他線程。

package tym.ThreadBase.waitAndnotify;
/** * Created by TyiMan on 2016/5/16. */
public class Calculator extends Thread {    
  int total;   
  @Override    
  public void run() {        
    synchronized (this) {            
      for (int i = 0; i < 101; i++) {                
        total += i;            
      } 
      notifyAll();        
    }   
  }
}

ResultReader類等待結(jié)果备畦,被喚醒后顯示Calculator類的計(jì)算結(jié)果低飒。

package tym.ThreadBase.waitAndnotify;

/**
 * Created by TyiMan on 2016/5/16.
 */
public class ReaderResult extends Thread {
    Calculator calculator;

    public ReaderResult(Calculator c) {
        this.calculator = c;
    }

    @Override
    public void run() {
        synchronized (calculator) {
            try {
                System.out.println(Thread.currentThread() + "等待計(jì)算結(jié)果。懂盐。褥赊。");
                calculator.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + "計(jì)算結(jié)果為:" + calculator.total);
        }
    }

    public static void main(String[] args) {
        Calculator calculator = new Calculator();

        //啟動(dòng)三個(gè)線程,分別獲取計(jì)算結(jié)果
        new ReaderResult(calculator).start();
        new ReaderResult(calculator).start();
        new ReaderResult(calculator).start();
        //啟動(dòng)計(jì)算線程
        calculator.start();
     }
}

最后輸出結(jié)果:

Thread[Thread-1,5,main]等待計(jì)算結(jié)果莉恼。拌喉。。
Thread[Thread-2,5,main]等待計(jì)算結(jié)果俐银。尿背。。
Thread[Thread-3,5,main]等待計(jì)算結(jié)果捶惜。田藐。。
Thread[Thread-3,5,main]計(jì)算結(jié)果為:5050
Thread[Thread-2,5,main]計(jì)算結(jié)果為:5050
Thread[Thread-1,5,main]計(jì)算結(jié)果為:5050

問題注意
實(shí)際上,上面代碼中汽久,我們期望的是讀取結(jié)果的線程在計(jì)算線程調(diào)用notifyAll()之前等待即可茴晋。 但是,如果計(jì)算線程先執(zhí)行回窘,并在讀取結(jié)果線程等待之前調(diào)用了notify()方法,那么又會(huì)發(fā)生什么呢市袖?
問題分析
這種情況是可能發(fā)生的啡直。因?yàn)闊o法保證線程的不同部分將按照什么順序來執(zhí)行。幸運(yùn)的是當(dāng)讀取線程運(yùn)行時(shí)苍碟,它只能馬上進(jìn)入等待狀態(tài)酒觅,它沒有做任何事情來檢查等待的事件是否已經(jīng)發(fā)生。因此微峰,如果計(jì)算線程已經(jīng)調(diào)用了notifyAll()方法舷丹,那么它就不會(huì)再次調(diào)用notifyAll(),并且等待的讀取線程將永遠(yuǎn)保持等待蜓肆。這當(dāng)然是開發(fā)者所不愿意看到的問題颜凯。
問題解決
當(dāng)?shù)却氖录l(fā)生時(shí),需要能夠檢查notifyAll()通知事件是否已經(jīng)發(fā)生仗扬。通常是利用某種循環(huán)症概,該循環(huán)檢查某個(gè)條件表達(dá)式,只有當(dāng)正在等待的事情還沒有發(fā)生的情況下早芭,它才繼續(xù)等待彼城。

2. 線程的調(diào)度——休眠
線程休眠的目的是使線程讓出CPU的最簡(jiǎn)單的做法之一,線程休眠時(shí)候退个,會(huì)將CPU資源交給其他線程募壕,以便能輪換執(zhí)行,當(dāng)休眠一定時(shí)間后语盈,線程會(huì)蘇醒舱馅,進(jìn)入準(zhǔn)備狀態(tài)等待執(zhí)行。
線程休眠的方法是Thread.sleep(long millis)Thread.sleep(long millis, int nanos)黎烈,均為靜態(tài)方法习柠,那調(diào)用sleep休眠的哪個(gè)線程呢?簡(jiǎn)單說照棋,哪個(gè)線程調(diào)用sleep资溃,就休眠哪個(gè)線程。
sleep()實(shí)例代碼

package tym.ThreadBase.waitAndnotify;

/**
 * Created by TyiMan on 2016/5/16.
 */
public class Test {
  public static void main(String[] args) {
    Thread t1 = new MyThread1();
    Thread t2 = new Thread(new MyRunnable());
    t1.start();
    t2.start();
  }
}

class MyThread1 extends Thread {
  public void run() {
    for (int i = 0; i < 3; i++) {
        System.out.println("線程1第" + i + "次執(zhí)行烈炭!");
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
  }
}

class MyRunnable implements Runnable {
  public void run() {
    for (int i = 0; i < 3; i++) {
        System.out.println("線程2第" + i + "次執(zhí)行溶锭!");
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
  }
}

輸出結(jié)果:

線程2第0次執(zhí)行!
線程1第0次執(zhí)行符隙!
線程1第1次執(zhí)行趴捅!
線程2第1次執(zhí)行垫毙!
線程2第2次執(zhí)行!
線程1第2次執(zhí)行拱绑!

3. 線程調(diào)度——優(yōu)先級(jí)
void setPriority(int newPriority)函數(shù)設(shè)置線程優(yōu)先級(jí)
與線程休眠類似综芥,線程的優(yōu)先級(jí)仍然無法保障線程的執(zhí)行次序。只不過猎拨,優(yōu)先級(jí)高的線程獲取CPU資源的概率較大膀藐,優(yōu)先級(jí)低的并非沒機(jī)會(huì)執(zhí)行。

  • 線程的優(yōu)先級(jí)用1-10之間的整數(shù)表示红省,數(shù)值越大優(yōu)先級(jí)越高额各,默認(rèn)的優(yōu)先級(jí)為5。
  • 在一個(gè)線程中開啟另外一個(gè)新線程吧恃,則新開線程稱為該線程的子線程虾啦,子線程初始優(yōu)先級(jí)與父線程相同。

setPriority()代碼實(shí)例

package tym.ThreadBase.waitAndnotify;

/**
 * Created by TyiMan on 2016/5/16.
 */
public class Test {
  public static void main(String[] args) {
    Thread t1 = new MyThread1();
    Thread t2 = new Thread(new MyRunnable());
    t1.setPriority(10);
    t2.setPriority(1);

    t2.start();
    t1.start();
  }
}

class MyThread1 extends Thread {
  public void run() {
    for (int i = 0; i < 10; i++) {
        System.out.println("線程1第" + i + "次執(zhí)行痕寓!");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
  }
}

class MyRunnable implements Runnable {
  public void run() {
    for (int i = 0; i < 10; i++) {
        System.out.println("線程2第" + i + "次執(zhí)行傲醉!");
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
  }
}

執(zhí)行結(jié)果:

線程1第0次執(zhí)行! 
線程2第0次執(zhí)行厂抽! 
線程2第1次執(zhí)行需频! 
線程1第1次執(zhí)行! 
線程2第2次執(zhí)行筷凤! 
線程1第2次執(zhí)行昭殉! 
線程1第3次執(zhí)行! 
線程2第3次執(zhí)行藐守! 
線程2第4次執(zhí)行挪丢! 
線程1第4次執(zhí)行! 
線程1第5次執(zhí)行卢厂! 
線程2第5次執(zhí)行乾蓬! 
線程1第6次執(zhí)行! 
線程2第6次執(zhí)行慎恒! 
線程1第7次執(zhí)行任内! 
線程2第7次執(zhí)行! 
線程1第8次執(zhí)行融柬!
線程2第8次執(zhí)行死嗦!
線程1第9次執(zhí)行!
線程2第9次執(zhí)行粒氧! 

4. 線程的調(diào)度——讓步

線程的讓步含義就是使當(dāng)前運(yùn)行著線程讓出CPU資源越除,但是然給誰不知道,僅僅是讓出,線程狀態(tài)回到可運(yùn)行狀態(tài)摘盆。

  • 線程的讓步使用Thread.yield()方法翼雀,yield() 為靜態(tài)方法,功能是暫停當(dāng)前正在執(zhí)行的線程對(duì)象孩擂,并執(zhí)行其他線程狼渊。

yield()代碼實(shí)例

package tym.ThreadBase.waitAndnotify;

/**
 * Created by TyiMan on 2016/5/16.
 */
public class Test {
  public static void main(String[] args) {
    Thread t1 = new MyThread1();
    Thread t2 = new Thread(new MyRunnable());

    t2.start();
    t1.start();
  }
}

class MyThread1 extends Thread {
  public void run() {
    for (int i = 0; i < 10; i++) {
        System.out.println("線程1第" + i + "次執(zhí)行!");
    }
  }
}

class MyRunnable implements Runnable {
  public void run() {
    for (int i = 0; i < 10; i++) {
        System.out.println("線程2第" + i + "次執(zhí)行类垦!");
        Thread.yield();
    }
  }
}

執(zhí)行結(jié)果:

線程2第0次執(zhí)行囤锉!
線程1第0次執(zhí)行!
線程2第1次執(zhí)行护锤!
線程1第1次執(zhí)行!
線程2第2次執(zhí)行酿傍!
線程1第2次執(zhí)行烙懦!
線程2第3次執(zhí)行!
線程1第3次執(zhí)行赤炒!
線程2第4次執(zhí)行氯析!
線程1第4次執(zhí)行!
線程2第5次執(zhí)行莺褒!
線程1第5次執(zhí)行掩缓!
線程2第6次執(zhí)行!
線程1第6次執(zhí)行遵岩!
線程2第7次執(zhí)行你辣!
線程1第7次執(zhí)行!
線程2第8次執(zhí)行尘执!
線程1第8次執(zhí)行舍哄!
線程2第9次執(zhí)行!
線程1第9次執(zhí)行誊锭!

在使用synchronized關(guān)鍵字時(shí)候表悬,應(yīng)該盡可能避免在synchronized方法或synchronized塊中使用sleep或者yield方法,因?yàn)閟ynchronized程序塊占有著對(duì)象鎖丧靡,sleep()讓程序睡眠還不釋放鎖蟆沫,不但嚴(yán)重影響效率,也不合邏輯温治。在同步程序塊內(nèi)調(diào)用yeild()方法讓出CPU資源也沒有意義饭庞,因?yàn)樗加弥i,其他互斥線程還是無法訪問同步程序塊罐盔。當(dāng)然與同步程序塊無關(guān)的線程可以獲得更多的執(zhí)行時(shí)間但绕。

5. 線程的調(diào)度——合并

線程的合并的含義就是將幾個(gè)并行線程的線程合并為一個(gè)單線程執(zhí)行,在很多情況下,主線程生成并起動(dòng)了子線程捏顺,如果子線程里要進(jìn)行大量的耗時(shí)的運(yùn)算六孵,主線程往往將于子線程之前結(jié)束,但是如果主線程處理完其他的事務(wù)后幅骄,需要用到子線程的處理結(jié)果劫窒,也就是主線程需要等待子線程執(zhí)行完成之后再結(jié)束,這個(gè)時(shí)候就要用到j(luò)oin()方法了拆座。

  • join()的作用是:“等待該線程終止”主巍,這里需要理解的就是該線程是指的主線程等待子線程的終止。也就是在子線程調(diào)用了join()方法后面的代碼挪凑,只有等到子線程結(jié)束了才能執(zhí)行孕索。
  • join()為非靜態(tài)方法,定義如下:
    void join()等待該線程終止躏碳。
    void join(long millis)等待該線程終止的時(shí)間最長(zhǎng)為 millis 毫秒搞旭。
    void join(long millis, int nanos)等待該線程終止的時(shí)間最長(zhǎng)為 millis 毫秒 + nanos 納秒。

join()代碼實(shí)例

package tym.ThreadBase.waitAndnotify;

/**
 * Created by TyiMan on 2016/5/16.
 */
public class Test {
  public static void main(String[] args) {
    Thread t1 = new MyThread1();
    t1.start();

    System.out.println("主線程開始執(zhí)行菇绵!");
    try {
        //t1線程合并到主線程中肄渗,主線程停止執(zhí)行過程,轉(zhuǎn)而執(zhí)行t1線程咬最,直到t1執(zhí)行完畢后繼續(xù)翎嫡。
        t1.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("主線程執(zhí)行完畢!");
  }
}

class MyThread1 extends Thread {
public void run() {
    for (int i = 0; i < 2; i++) {
        System.out.println("線程1第" + i + "次循環(huán)永乌!");
    }
  }
}

執(zhí)行結(jié)果:

主線程第0次執(zhí)行惑申!
線程1第0次執(zhí)行翅雏!
線程1第1次執(zhí)行!
主線程第1次執(zhí)行枚荣!

6. 守護(hù)線程

守護(hù)線程與普通線程寫法上基本么啥區(qū)別,調(diào)用線程對(duì)象的方法setDaemon(true)橄妆,則可以將其設(shè)置為守護(hù)線程衙伶。
守護(hù)線程使用的情況較少害碾,但并非無用,舉例來說慌随,JVM的垃圾回收芬沉、內(nèi)存管理等線程都是守護(hù)線程躺同。還有就是在做數(shù)據(jù)庫應(yīng)用時(shí)候,使用的數(shù)據(jù)庫連接池蹋艺,連接池本身也包含著很多后臺(tái)線程黄刚,監(jiān)控連接個(gè)數(shù)、超時(shí)時(shí)間憔维、狀態(tài)等等。

setDaemon方法的詳細(xì)說明:
public final void setDaemon(boolean on)將該線程標(biāo)記為守護(hù)線程或用戶線程检吆。當(dāng)正在運(yùn)行的線程都是守護(hù)線程時(shí)程储,Java 虛擬機(jī)退出。
該方法必須在啟動(dòng)線程前調(diào)用虱肄。
該方法首先調(diào)用該線程的 checkAccess 方法交煞,且不帶任何參數(shù)。這可能拋出 SecurityException(在當(dāng)前線程中)集嵌。
參數(shù): on - 如果為 true御毅,則將該線程標(biāo)記為守護(hù)線程。
拋出
IllegalThreadStateException - 如果該線程處于活動(dòng)狀態(tài)端蛆。
SecurityException - 如果當(dāng)前線程無法修改該線程。

setDaemon()代碼實(shí)例

package tym.ThreadBase.waitAndnotify;

/**
 * Created by TyiMan on 2016/5/16.
 */
public class Test {
  public static void main(String[] args) {
    Thread t1 = new MyCommon();
    Thread t2 = new Thread(new MyDaemon());
    t2.setDaemon(true);        //設(shè)置為守護(hù)線程

    t2.start();
    t1.start();
  }
}

class MyCommon extends Thread {
  public void run() {
    for (int i = 0; i < 5; i++) {
        System.out.println("線程1第" + i + "次執(zhí)行嫌拣!");
        try {
            Thread.sleep(7);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
  }
}

class MyDaemon implements Runnable {
  public void run() {
    for (long i = 0; i < 9999999L; i++) {
        System.out.println("后臺(tái)線程第" + i + "次執(zhí)行呆躲!");
        try {
            Thread.sleep(7);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
  }
}

執(zhí)行結(jié)果:守護(hù)進(jìn)程隨著主進(jìn)程結(jié)束而結(jié)束

后臺(tái)線程第0次執(zhí)行!
線程1第0次執(zhí)行灰瞻!
線程1第1次執(zhí)行!
后臺(tái)線程第1次執(zhí)行酝润!
后臺(tái)線程第2次執(zhí)行!
線程1第2次執(zhí)行底瓣!
后臺(tái)線程第3次執(zhí)行!
線程1第3次執(zhí)行蕉陋!
后臺(tái)線程第4次執(zhí)行捐凭!
線程1第4次執(zhí)行!
后臺(tái)線程第5次執(zhí)行凳鬓!

六茁肠、線程基礎(chǔ)總結(jié)

該部分為Java線程比較基礎(chǔ)的部分,接下來會(huì)繼續(xù)整理一些更加深入的知識(shí)垦梆。想到Java多線程基礎(chǔ)仅孩,我們應(yīng)該知道自己應(yīng)該了解:

  • Java線程的兩種實(shí)現(xiàn)方式集成Thread類和實(shí)現(xiàn)Runnable接口,調(diào)用start()函數(shù)來啟動(dòng)線程京腥。
  • Java線程新建[new]溅蛉、就緒[start()]、運(yùn)行[執(zhí)行run()]欠气、阻塞[sleep(),join(),wait()镜撩、喚醒notify(),等待鎖]、死亡[結(jié)束所有操作卫旱,stop()或destroy(),發(fā)生異常終止]的生命周期围段。
  • 了解線程同步的原理和作用、synchronized關(guān)鍵字的使用方法:同步方法和同步代碼塊适贸,靜態(tài)方法的同步。
  • 了解Java線程交互與調(diào)度函數(shù)wait(), notify(), notifyAll(), sleep(), setPriority(), yield(), join(), setDaemon()方法和使用拜姿。

七蕊肥、參考引用

http://lavasoft.blog.51cto.com/62575/27069/
http://blog.csdn.net/csh624366188/article/details/7318245
http://www.cnblogs.com/riskyer/p/3263032.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市批狱,隨后出現(xiàn)的幾起案子展东,更是在濱河造成了極大的恐慌,老刑警劉巖爪膊,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件砸王,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡耘成,警方通過查閱死者的電腦和手機(jī)荷辕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門件豌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茧彤,“玉大人,你說我怎么就攤上這事曾掂。” “怎么了溜歪?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵蝴猪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我自阱,道長(zhǎng),這世上最難降的妖魔是什么趋箩? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任加派,我火速辦了婚禮哼丈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘醉旦。我一直安慰自己,他們只是感情好檬输,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布匈棘。 她就那樣靜靜地躺著,像睡著了一般逃默。 火紅的嫁衣襯著肌膚如雪簇搅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天吟税,我揣著相機(jī)與錄音肠仪,去河邊找鬼备典。 笑死,一個(gè)胖子當(dāng)著我的面吹牛提佣,可吹牛的內(nèi)容都是我干的欲险。 我是一名探鬼主播天试,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼然低,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了带兜?” 一聲冷哼從身側(cè)響起吨灭,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎无畔,沒想到半個(gè)月后吠冤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡郭变,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年诉濒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了夕春。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茄猫,死狀恐怖困肩,靈堂內(nèi)的尸體忽然破棺而出脆侮,到底是詐尸還是另有隱情,我是刑警寧澤潭枣,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站命咐,受9級(jí)特大地震影響谐岁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜窜司,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一航揉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧议薪,春花似錦漠秋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至焰雕,卻和暖如春芳杏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背爵赵。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工空幻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留威彰,地道東北人木人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓鬓梅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親士袄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谎僻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

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