10.1 多線程的概念
10.1.1 程序、進(jìn)程和多任務(wù)
程序:是對(duì)數(shù)據(jù)描述與操作的代碼的集合,是應(yīng)用程序執(zhí)行的腳本
進(jìn)程:是程序的一次執(zhí)行過(guò)程,程序是靜態(tài)的,進(jìn)程是動(dòng)態(tài)的。系統(tǒng)運(yùn)行一個(gè)程序就是一個(gè)進(jìn)程從創(chuàng)建、運(yùn)行到消亡的過(guò)程。
多任務(wù):一個(gè)系統(tǒng)中可以同時(shí)運(yùn)行多個(gè)程序。一個(gè)CPU同時(shí)只能執(zhí)行一個(gè)程序的一條指令,多任務(wù)運(yùn)行的并發(fā)機(jī)制使這些任務(wù)交替運(yùn)行。
10.1.2 線程
線程也稱為輕型進(jìn)程(LWP),是比進(jìn)程更小的運(yùn)行單位,一個(gè)進(jìn)程可以被劃分成多個(gè)線程。當(dāng)一個(gè)程序執(zhí)行多線程時(shí),可以運(yùn)行兩個(gè)或多個(gè)由同一個(gè)程序啟動(dòng)的任務(wù)。這樣一個(gè)程序可以使得多個(gè)活動(dòng)任務(wù)同時(shí)發(fā)生。
10.1.3 多線程
與進(jìn)程不同的是,同類多線程共享一塊內(nèi)存空間和一組系統(tǒng)資源,所以系統(tǒng)創(chuàng)建多線程開(kāi)銷相對(duì)較小。
10.1.4 線程的生命同期與Java的多線程機(jī)制
1.線程的生命同期與狀態(tài)
線程有創(chuàng)建(New)、可運(yùn)行(Runnable)、運(yùn)行中(Running)、掛起(Not Runnable)、死亡(Dead)5種狀態(tài)。
2.Java的多線程機(jī)制
java.lang中的線程類Thread封裝了所有需要的線程操作控制,有很多方法用來(lái)控制一個(gè)線程的運(yùn)行、休眠、掛起或停止。
10.2 創(chuàng)建線程
建立一個(gè)線程需要完成三件事:
> 建立一個(gè)虛擬的CPU
> 給出線程的執(zhí)行代碼
> 提供代碼所操作的數(shù)據(jù)
兩種方法可以創(chuàng)建線程:一種方法是通過(guò)繼承線程類Thread來(lái)創(chuàng)建線程類;另一種方法是建立一個(gè)實(shí)現(xiàn)Runnable接口的類一創(chuàng)建線程 。
10.2.1 通過(guò)繼承Thread類創(chuàng)建線程
繼承Thread類這種方法中,需要覆蓋run( )方法來(lái)提供線程的執(zhí)行代碼,定義Thread類的成員變量來(lái)提供線程的數(shù)據(jù)。線程執(zhí)行時(shí),從它的run( )方法中開(kāi)始執(zhí)行,run()方法是線程執(zhí)行的起點(diǎn)
例:public class SC extends Thread{
int count=1,number;
SC(int num){
number=num;
System.out.println("創(chuàng)建線程"+number); }
public void run(){
while(true){
System.out.println("線程"+number+":計(jì)數(shù)"+count);
if(++count==3) return; } }
public static void main(String args[]){
for (int i=0;i<3;i++)
new SC(i+1).start(); } }
運(yùn)行結(jié)果:創(chuàng)建線程1
創(chuàng)建線程2
創(chuàng)建線程3
線程1:計(jì)數(shù)1
線程1:計(jì)數(shù)2
線程2:計(jì)數(shù)1
線程2:計(jì)數(shù)2
線程3:計(jì)數(shù)1
線程3:計(jì)數(shù)2
例:P208
public class SC extends Thread{
public static void main(String args[]){
testThread t1=new testThread("線程1");//僅是一空線程對(duì)象,沒(méi)有啟動(dòng)這一線程,系統(tǒng)不為它分配資源,
testThread t2=new testThread("線程2");
t1.start(); t2.start(); } } //啟動(dòng)線程
class testThread extends Thread{
public testThread(String str)
{super(str);} //調(diào)用父類構(gòu)造方法為線程對(duì)象命名
public void run(){
for (int i=0;i<3;i++){
System.out.println(getName()+"在運(yùn)行"); //getName返回線程名稱
try
{sleep(1000); //用休眠1000毫秒來(lái)區(qū)分哪個(gè)線程在運(yùn)行
System.out.println(getName()+"在休眠");}
catch(InterruptedException e){} }
System.out.println(getName()+"已結(jié)束"); }}
說(shuō)明:由繼承Thread創(chuàng)建的子類,必須覆蓋run方法,因?yàn)閞un方法是abstact抽象方法
10.2.2 通過(guò)Runnable接口創(chuàng)建線程
如果是Applet類應(yīng)不能再繼承Thread類(不能多繼承),這時(shí)可以通過(guò)接口Runnable直接創(chuàng)建線程對(duì)象。接口中只聲明了一個(gè)未實(shí)現(xiàn)的run方法。
線程體的構(gòu)造方法:
public Thread([ThreadGroup group][,Runnable target][,String name])
其中g(shù)roup指明該線程所屬的線程組,target是執(zhí)行線程體的目標(biāo)對(duì)象,它必須實(shí)現(xiàn)接口Runnable,name則為線程名。這三個(gè)參數(shù)均可任意沒(méi)有。
任何實(shí)現(xiàn)接口Runnable的對(duì)象都可以作為一個(gè)線程的目標(biāo)對(duì)象,類Thread本身也實(shí)現(xiàn)了接口Runnable,因此我們可以通過(guò)兩種方法實(shí)現(xiàn)線程體。
(1)定義一個(gè)線程類,它繼承類Thread并重寫其中的方法run(),這時(shí)在初始化這個(gè)類的實(shí)例時(shí),目標(biāo)對(duì)象target可為null
(2)提供一個(gè)實(shí)現(xiàn)接口Runnable類作為線程的目標(biāo)對(duì)象,在初始化一個(gè)Thread類或者Thread子類的對(duì)象時(shí),把目標(biāo)對(duì)象傳遞給這個(gè)線程實(shí)例,由該目標(biāo)對(duì)象提供線程體run()。這時(shí)實(shí)現(xiàn)接口Runnable的類仍然要以繼承其他父類。
例:P210
import java.awt.*;
import java.applet.Applet;
import java.util.*;
import java.text.DateFormat;
public class SC extends Applet implements Runnable{
Thread clockThread=null;
public void init(){
setBackground(Color.blue);
setForeground(Color.yellow); }
public void start(){
if(clockThread==null){
clockThread=new Thread(this,"Clock2");
//Thread(ThreadGroup group,String name)name是線程名,group指定線程所屬的組
clockThread.start(); } }
public void run(){
Thread myThread=Thread.currentThread();
//找到當(dāng)前執(zhí)行的線程,返回它的引用
while(clockThread==myThread){
repaint();
try{ Thread.sleep(1000);
} catch(InterruptedException e){}
} }
public void paint(Graphics g){
Date date=new Date();
DateFormat formatter=DateFormat.getTimeInstance();
String str=formatter.format(date);
g.drawString(str,5,10); }
public void stop()
{clockThread=null;} }
10.2.3 可運(yùn)行狀態(tài)(Runnable)
Thread MyThread=new Thread()
MyThread.start();
這樣該線程處于可運(yùn)行(Runnable)狀態(tài),注意,這狀態(tài)并不是運(yùn)行中狀態(tài)(Running),因?yàn)榫€程也許實(shí)際并未真正運(yùn)行(因很多計(jì)算機(jī)是單CPU)。當(dāng)一個(gè)線程正在運(yùn)行時(shí),它是可運(yùn)行的,并也是當(dāng)前正運(yùn)行的線程
10.2.4 不可運(yùn)行狀態(tài)(Not Runnable)
當(dāng)下面四種情況發(fā)生時(shí),線程就是進(jìn)入不可運(yùn)行狀態(tài):
(1)調(diào)用了sleep()方法
(2)調(diào)用了suspend()方法
(3)為等候一個(gè)條件變量,線程調(diào)用wait()方法
(4)輸入輸出流中發(fā)生線程阻塞
我們來(lái)看看下面這個(gè)例子:
Thread myThread=new Thread();
MyThread.start();
try{ myThread.sleep(10000);}
catch(InterrupedException e){
}
對(duì)于上面四種情況,都有特定的返回可運(yùn)行狀態(tài)的方法與之對(duì)應(yīng),對(duì)應(yīng)方法如下:
?。?)如果線程處于睡眠狀態(tài)中,sleep()方法中的參數(shù)為休息時(shí)間,當(dāng)時(shí)間過(guò)去后,線程即為可運(yùn)行的。
?。?)如果一線程被掛起,須由其他線程調(diào)用resume()方法來(lái)恢復(fù)該線程的執(zhí)行。
?。?)如果線程在等待條件變量,那么要停止等待的話,要該條件變量所在的對(duì)象調(diào)用notify(),notifyAll()方法。
(4)如果在I/O流中發(fā)生線程阻塞,則特定的I/O指令將結(jié)束這種不可運(yùn)行狀態(tài)
10.2.5 死亡狀態(tài)(Dead)
一般可通過(guò)兩種方法實(shí)現(xiàn):自然撤消或是被停止
1.自然撤消
public void run(){
int i=0;
while(i<100){
i++;
System.out.println(“i=”+i); }}
當(dāng)run()方法結(jié)束后,該線程應(yīng)自然撤消了
2.被停止
Thread myThread=new Thread();
MyThread.start();
try{Trhead.currentThread().sleep(10000);
} catch(InterruptedException e)
myThread.stop();
10.2.6 非法狀態(tài)處理(IllegalThreadStateException)
當(dāng)一個(gè)線程剛被創(chuàng)建后,只能對(duì)它調(diào)用start()或stop()方法,若調(diào)用其他方法則會(huì)引起非法狀態(tài)處理。同樣對(duì)于任何狀態(tài),如果所調(diào)用的方法與狀態(tài)不符,都會(huì)引起非法狀態(tài)處理。
10.3 線程的優(yōu)先級(jí)
Java為使有些線程可以提前得到服務(wù),可給線程設(shè)置優(yōu)先級(jí)。在單個(gè)CPU上運(yùn)行多線程時(shí)采用了線程隊(duì)列技術(shù),Java虛擬機(jī)支持固定優(yōu)先級(jí)隊(duì)列,一個(gè)線程的執(zhí)行順序取決于其對(duì)其他Runnable線程的優(yōu)先級(jí)。優(yōu)先級(jí)分1~10同,默認(rèn)級(jí)別是5級(jí)。
Thread的公用靜態(tài)常量表示:
public static final int MAX_PRORITY=10 (最大優(yōu)先級(jí)10)
public static final int MIN_PRORITY=1 (最小優(yōu)先級(jí)10)
public static final int NORM_PRORITY=5 (默認(rèn)優(yōu)先級(jí)5)
public final int getPriority( ) //獲得線程優(yōu)先級(jí)
public final setPriority(int newPriority) //設(shè)置線程優(yōu)先級(jí)
10.4 線程的調(diào)度與控制
10.4.1 線程類的方法
1.線程的類方法
Thread類的靜態(tài)方法,即可直接從Thread類調(diào)用
CurrentThread( ):返回正在運(yùn)行的Thread對(duì)象名稱
sleep(int n):讓當(dāng)前線程休眠n毫秒
2.實(shí)例方法P213
10.4.2 控制線程的狀態(tài)
1.掛起一個(gè)線程:suspend( )方法
t.suspend():將t暫停執(zhí)行,必須由其他線程調(diào)用t.resume()恢復(fù),不提倡使用該方法,因?yàn)槿菀自斐伤梨i
2.停止一個(gè)線程:stop( )方法
當(dāng)線程完成運(yùn)行并結(jié)束后,將不能再運(yùn)行。另不可用t.stop()強(qiáng)行終止線程。注:這并沒(méi)有消滅這個(gè)線程,只是停止線程的執(zhí)行。但這個(gè)線程不能用t.start()重新啟動(dòng)。不提倡采用這種方法,易造成線程的不一致。要通過(guò)設(shè)置flag通知一個(gè)線程應(yīng)該結(jié)束。
3.線程休眠:sleep(long )方法
Thread.sleep()使一個(gè)線程暫停運(yùn)行一段固定時(shí)間。
4.連接線程:join( )方法
t.join()使當(dāng)前的線程等待,直到t結(jié)束為止,線程恢復(fù)到可運(yùn)行狀態(tài)。三種調(diào)用格式:
?。?)join():如當(dāng)前線程發(fā)出調(diào)用t.join(),則當(dāng)前線程將等待線程t結(jié)束后再繼續(xù)執(zhí)行。
?。?)join(long millis):如當(dāng)前線程發(fā)出調(diào)用t.join(long millis),則當(dāng)前線程將等待線程t結(jié)束或最多等待mills毫秒后再繼續(xù)執(zhí)行。
(3)join(long millis,int nanos):如當(dāng)前線程發(fā)出調(diào)用t.join(long millis,int nanos),則當(dāng)前線程將等待線程t結(jié)束或最多等待mills毫秒+nanos納秒后再繼續(xù)執(zhí)行。
5.暫停(退讓)線程:yield( )方法(只讓給同優(yōu)先級(jí)運(yùn)行)
調(diào)用t.yield()方法可暫停當(dāng)前運(yùn)行線程,但處于可運(yùn)行狀態(tài),讓同優(yōu)先級(jí)線程先運(yùn)行。若沒(méi)有同等優(yōu)先級(jí)的線程是可運(yùn)行狀態(tài),yield方法將什么也不做。
6.中斷線程:interrupt( )方法
如果一個(gè)線程t在調(diào)用sleep(),join(),wait()方法被阻塞時(shí),則t.interrupt()方法將使t的中斷狀態(tài)被清除,中斷t的阻塞狀態(tài),并且將接收到InterruptException異常。
一個(gè)線程可以通過(guò)獲取另一個(gè)線程的引用,調(diào)用該線程的interrupt()方法來(lái)中斷另一個(gè)sleep或wait線程的執(zhí)行
7.了解線程的狀態(tài):isAlive( )方法
返回true則線程處于Runnable或Not Runnable狀態(tài)(即已啟動(dòng)但還沒(méi)有運(yùn)行結(jié)束),返回false則說(shuō)明線程處于New Thread或Dead狀態(tài)
start() 調(diào)度
時(shí)間片到y(tǒng)ield() run(),stop()結(jié)束
時(shí)間到 sleep()
interrupt() join() wait()
synchronized()
獲得互斥
使用權(quán)
notify()
notifyall()
線程狀態(tài)的轉(zhuǎn)換圖
例:public class SC{
public static void main(String args[]) throws Exception{
int i=0;
Hello t=new Hello();
t.start();
while(true){
System.out.println("早上好!"+i++);
if (i==2&&t.isAlive()){
System.out.println("Main waiting for Hello!");
t.join(); //等待t運(yùn)行結(jié)束
}
if (i==4) break; }}}
class Hello extends Thread{
int i;
public void run(){
while (true){
System.out.println("嗨"+i++);
if(i==4) break; } } }
運(yùn)行結(jié)果:
早上好!0
早上好!1
Main waiting for Hello!
嗨0
嗨1
嗨2
嗨3
早上好!2
早上好!3
10.5 線程的同步機(jī)制與共享資源(自學(xué))