一饲齐、線程概念
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