2017年計(jì)算機(jī)二級(jí)JAVA考點(diǎn)解析:多線程

字號(hào):


    7.1多線程的概念
    多線程編程的含義是你可將程序任務(wù)分成幾個(gè)并行的子任務(wù)。特別是在網(wǎng)絡(luò)編程中,你會(huì)發(fā)現(xiàn)很多功能是可以并發(fā)執(zhí)行的。比如網(wǎng)絡(luò)傳輸速度較慢,用戶輸入速度較慢,你可以用兩個(gè)獨(dú)立的線程去完成這?copy;功能,而不影響正常的顯示或其他功能。多線程是與單線程比較而言的,普通的WINDOWS采用單線程程序結(jié)構(gòu),其工作原理是:主程序有一個(gè)消息循環(huán),不斷從消息隊(duì)列中讀入消息來決定下一步所要干的事情,一般是一個(gè)子函數(shù),只有等這個(gè)子函數(shù)執(zhí)行完返回后,主程序才能接收另外的消息來執(zhí)行。比如子函數(shù)功能是在讀一個(gè)網(wǎng)絡(luò)數(shù)據(jù),或讀一個(gè)文件,只有等讀完這?copy;數(shù)據(jù)或文件才能接收下一個(gè)消息。在執(zhí)行這個(gè)子函數(shù)過程中你什么也不能干。但往往讀網(wǎng)絡(luò)數(shù)據(jù)和等待用戶輸入有很多時(shí)間處于等待狀態(tài),多線程利用這個(gè)特點(diǎn)將任務(wù)分成多個(gè)并發(fā)任務(wù)后,就可以解決這個(gè)問題。
    7.1.1Java線程的模型
    Java的設(shè)計(jì)思想是建立在當(dāng)前大多數(shù)操作系統(tǒng)都實(shí)現(xiàn)了線程調(diào)度。Java虛擬機(jī)的很多任務(wù)都依賴線程調(diào)度,而且所有的類庫都是為多線程設(shè)計(jì)的。實(shí)時(shí)上,Java支持Macintosh和Ms-dos的平臺(tái)?reg;所以遲遲未出來就是因?yàn)檫@兩個(gè)平臺(tái)都不支持多線程。Java利用多線程實(shí)現(xiàn)了整個(gè)執(zhí)行環(huán)境是異步的。在Java程序里沒有主消息循環(huán)。如果一個(gè)線程等待讀取網(wǎng)絡(luò)數(shù)據(jù),它可以運(yùn)行但不停止系統(tǒng)的其他線程執(zhí)行。用于處理用戶輸入的線程大多時(shí)間是等待用戶敲鍵盤或擊鼠標(biāo)。你還可以使動(dòng)畫的每一幀?reg;間停頓一秒而并不使系統(tǒng)暫停。一?copy;線程啟動(dòng)后,它可以被掛起,暫時(shí)不讓它執(zhí)行。掛起的線程可以重新恢復(fù)執(zhí)行。任何時(shí)間線程都可以被停止,被停止的線程就不能再重新啟動(dòng)。Java語言里,線程表現(xiàn)為線程類,線程類封裝了所有需要的線程操作控制。在你心里,必須很清晰地區(qū)分開線程對(duì)象和運(yùn)行線程,你可以將線程對(duì)象看作是運(yùn)行線程的控制面板。在線程對(duì)象里有很多函數(shù)來控制一個(gè)線程是否運(yùn)行,睡眠,掛起或停止。線程類是控制線程行為的的手段。一?copy;一個(gè)Java程序啟動(dòng)后,就已經(jīng)有一個(gè)線程在運(yùn)行。你可通過調(diào)用Thread.currentThread函數(shù)來查看當(dāng)前運(yùn)行的是哪一個(gè)線程。
    你得到一個(gè)線程的控制柄,你就可以作很有趣的事情,即使單線程也一樣。下面這個(gè)例子讓你知道怎樣操縱當(dāng)前線程。Filename:testthread
    classtestthread{publicstaticvoidmain(Stringargs[]){Threadt
    =Thread.currentThread();t.setName(\"ThisThreadisrunning\");
    System.out.println(\"Therunningthread:\"+t);try{for(inti=0;i
    {System.out.println(\"Sleeptime\"+i);Thread.sleep(1000);}
    }catch(InterruptedExceptione){System.out.println(\"threadhaswrong\");}
    }}
    執(zhí)行結(jié)果:javatestthreadTherunningthread:Thread[ThisThreadisrunning,5,main]Sleeptime0Sleeptime1Sleeptime2Sleeptime3Sleeptime4
    7.1.2啟動(dòng)接口
    一個(gè)線程并不激動(dòng)人心,多個(gè)線程才有實(shí)際意義。我們?cè)鯓觿?chuàng)建更多的線程呢?我們需要?jiǎng)?chuàng)建線程類的另一個(gè)實(shí)例。當(dāng)我們構(gòu)造了線程類的一個(gè)新的實(shí)例,我們必須告訴它在新的線程里應(yīng)執(zhí)行哪一段程序。你可以在任意實(shí)現(xiàn)了啟動(dòng)接口的對(duì)象上啟動(dòng)一個(gè)線程。啟動(dòng)接口是一個(gè)抽象接口,來表示本對(duì)象有一?copy;函數(shù)想異步執(zhí)行。要實(shí)現(xiàn)啟動(dòng)接口,一個(gè)類只需要有一個(gè)叫run的函數(shù)。下面是創(chuàng)建一個(gè)新線程的例子:
    Filename:twothread.java
    classtwothreadimplementsRunnable{twothread(){Threadt1
    =Thread.currentThread();t1.setName(\"Thefirstmainthread\");
    System.out.println(\"Therunningthread:\"+t1);Threadt2=new
    Thread(this,\"thesecondthread\");System.out.println(\"creatanother
    thread\");t2.start();try{System.out.println(\"firstthreadwill
    sleep\");Thread.sleep(3000);}catch(InterruptedExceptione)
    {System.out.println(\"firstthreadhaswrong\");}
    System.out.println(\"firstthreadexit\");}publicvoidrun(){try{for
    (inti=0;i
    Thread.sleep(1000);}
    }catch(InterruptedExceptione){System.out.println(\"threadhas
    wrong\");}
    System.out.println(\"secondthreadexit\");}publicstaticvoid
    main(Stringargs[]){newtwothread();}}
    執(zhí)行結(jié)果:javatwothread
    Therunningthread:Thread[Thefirstmainthread,5,main]creatanother
    threadfirstthreadwillsleepSleeptimeforthread2:0Sleeptimefor
    thread2:1Sleeptimeforthread2:2firstthreadexitSleeptimefor
    thread2:3Sleeptimeforthread2:4secondthreadexit
    main線程用newThread(this,\"thesecondthread\")創(chuàng)建了一個(gè)Thread對(duì)象,通過傳遞第一個(gè)參數(shù)來標(biāo)明新線程來調(diào)用this對(duì)象的run函數(shù)。然后我們調(diào)用start函數(shù),它將使線程從run函數(shù)開始執(zhí)行。
    7.1.3同步
    因?yàn)槎嗑€程給你提?copy;了程序的異步執(zhí)行的功能,所以在必要時(shí)必須還提?copy;一種同步機(jī)制。例如,你想兩個(gè)線程通訊并共享一個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu),你需要一種機(jī)制讓他們相互牽制并正確執(zhí)行。為這個(gè)目的,Java用一種叫監(jiān)視器(monitor)的機(jī)制實(shí)現(xiàn)了進(jìn)程間的異步執(zhí)行??梢詫⒈O(jiān)視器看作是一個(gè)很小的盒子,它只能容納一個(gè)線程。一?copy;一個(gè)線程進(jìn)入一個(gè)監(jiān)視器,所有其他線程必須等到第一個(gè)線程退出監(jiān)視器后才能進(jìn)入。這?copy;監(jiān)視器可以設(shè)計(jì)成保護(hù)共享的數(shù)據(jù)不被多個(gè)線程同時(shí)操作。大多數(shù)多線程系統(tǒng)將這?copy;監(jiān)視器設(shè)計(jì)成對(duì)象,Java提?copy;了一種更清晰的解決方案。沒有Monitor類;每個(gè)對(duì)象通過將他們的成員函數(shù)定義成synchronized來定義自己的顯式監(jiān)視器,一?copy;一個(gè)線程執(zhí)行在一個(gè)synchronized函數(shù)里,其他任何線程都不能調(diào)用同一個(gè)對(duì)象的
    synchronized函數(shù)。
    7.1.4消息
    你的程序被分成幾個(gè)邏輯線程,你必須清晰的知道這?copy;線程?reg;間應(yīng)怎樣相互通訊。Java提了wait和notify等功能來使線程?reg;間相互交談。一個(gè)線程可以進(jìn)入某一個(gè)對(duì)象的synchronized函數(shù)進(jìn)入等待狀態(tài),直到其他線程顯式地將它喚醒??梢杂卸鄠€(gè)線程進(jìn)入同一個(gè)函數(shù)并等待同一個(gè)喚醒消息。
    7.2Java線程例子
    7.2.1顯式定義線程
    在我們的單線程應(yīng)用程序里,我們并沒有看見線程,因?yàn)镴ava能自動(dòng)創(chuàng)建和控制你的線程。如果你使用了理解Java語言的瀏覽器,你就已經(jīng)看到使用多線程的Java程序了。你也許注意到兩個(gè)小程序可以同時(shí)運(yùn)行,或在你移動(dòng)滾動(dòng)條時(shí)小程序繼續(xù)執(zhí)行。這并不是表明小程序是多線程的,但說明這個(gè)瀏覽器是多線程的。多線程應(yīng)用程序(或applet)可以使用好幾個(gè)執(zhí)行上下文來完成它們的工
    作。多線程利用了很多任務(wù)包含單獨(dú)的可分離的子任務(wù)的特點(diǎn)。每一個(gè)線程完成一個(gè)子任務(wù)。但是,每一個(gè)線程完成子任務(wù)時(shí)還是順序執(zhí)行的。一個(gè)多線程程序允許各個(gè)線程盡快執(zhí)行完它們。這種特點(diǎn)會(huì)有更好的實(shí)時(shí)輸入反應(yīng)。
    7.2.2多線程例子
    下面這個(gè)例子創(chuàng)建了三個(gè)單獨(dú)的線程,它們分別打印自己的\"HelloWorld\":
    //Defineoursimplethreads.Theywillpauseforashorttime//andthen
    printouttheirnamesanddelaytimesclassTestThreadextendsThread
    {privateStringwhoami;privateintdelay;
    //Ourconstructortostorethename(whoami)//andtimetosleep(delay)
    publicTestThread(Strings,intd){whoami=s;delay=d;}
    //Run-thethreadmethodsimilartomain()//Whenrunisfinished,the
    threaddies.//Runiscalledfromthestart()methodofThreadpublicvoid
    run(){//Trytosleepforthespecifiedtimetry{sleep(delay);}
    catch(InterruptedExceptione){}//Nowprintoutourname
    System.out.println(\"HelloWorld!\"+whoami+\"\"+delay);}}/***Multimtest.
    Asimplemultithreadthestprogram*/publicclassmultitest{public
    staticvoidmain(Stringargs[]){TestThreadt1,t2,t3;//Createourtest
    threadst1=newTestThread(\"Thread1\",(int)(Math.readom()*2000));t2=
    newTestThread(\"Thread2\",(int)(Math.readom()*2000));t3=new
    TestThread(\"Thread3\",(int)(Math.readom()*2000));
    //Starteachofthethreadst1.start();t2.start();t3.start();}}
    7.2.3啟動(dòng)一個(gè)線程
    程序啟動(dòng)時(shí)總是調(diào)用main()函數(shù),因此main()是我們創(chuàng)建和啟動(dòng)線程的地方:
    t1=newTestThread(\"Thread1\",(int)(Math.readom()*2000));
    這一行創(chuàng)建了一個(gè)新的線程。后面的兩個(gè)參數(shù)傳遞了線程的名稱和線程在打印信息?reg;前的延時(shí)時(shí)間。因?yàn)槲覀冎苯涌刂凭€程,我們必須直接啟動(dòng)它:t1.start();
    7.2.4操作線程
    如果創(chuàng)建線程正常,t1應(yīng)包含一個(gè)有效的執(zhí)行線程。我們?cè)诰€程的run()函數(shù)里控制線程。一?copy;我們進(jìn)入run()函數(shù),我們便可執(zhí)行里面的任何程序。run()好象main()一樣。
    run()執(zhí)行完,這個(gè)線程也就結(jié)束了。在這個(gè)例子里,我們?cè)囍舆t一個(gè)隨機(jī)的時(shí)間(通過參數(shù)傳遞?)sleep(delay);
    sleep()函數(shù)只是簡(jiǎn)單地告訴線程休息多少個(gè)毫秒時(shí)間。
    如果你想推遲一個(gè)線程的執(zhí)行,你應(yīng)使用sleep()函數(shù)。當(dāng)線程睡眠是sleep()并不占用系統(tǒng)資源。其它線程可繼續(xù)工作。一?copy;延遲時(shí)間完畢,它將打印\"HelloWorld\"和線程名稱及延遲時(shí)間。
    7.2.5暫停一個(gè)線程
    我們經(jīng)常需要掛起一個(gè)線程而不指定多少時(shí)間。例如,如果你創(chuàng)建了一個(gè)含有動(dòng)畫線程的小程序。也許你讓用戶暫停動(dòng)畫至到他們想恢復(fù)為止。你并不想將動(dòng)畫線程仍調(diào),但想讓它停止。象這種類似的線程你可用suspend()函數(shù)來控制:t1.suspend();這個(gè)函數(shù)并不永久地停止了線程,你還可用resume()函數(shù)重新激活線程:t1.resume();
    7.2.6停止一個(gè)線程
    線程的最后一個(gè)控制是停止函數(shù)stop()。我們用它來停止線程的執(zhí)行:t1.stop();
    注意:這并沒有消滅這個(gè)線程,但它停止了線程的執(zhí)行。并且這個(gè)線程不能用t1.start()重新啟動(dòng)。在我們的例子里,我們從來不用顯式地停止一個(gè)線程。我們只簡(jiǎn)單地讓它執(zhí)行完而已。很多復(fù)雜的線程例子將需要我們控制每一個(gè)線程。在這種情況下會(huì)使用到stop()函數(shù)。如果需要,你可以測(cè)試你的線程是否被激活。一個(gè)線程已經(jīng)啟動(dòng)而且沒有停止被認(rèn)為是激活的。t1.isAlive()如果t1是激活的,這個(gè)函數(shù)將返回true.
    7.2.7動(dòng)畫例子
    下面是一個(gè)包含動(dòng)畫線程的applet例子:
    importjava.awt.*;importjava.awt.image.ImageProducer;import
    java.applet.Applet;
    publicclassatest3extendsAppletimplementsRunnable{Imageimages[];
    MediaTrackertracker;intindex=0;Threadanimator;
    intmaxWidth,maxHeight;//Ouroff-screencomponentsfordoublebuffering.
    ImageoffScrImage;GraphicsoffScrGC;
    //Canwepaintyes?booleanloaded=false;
    //Initializetheapplet.Setoursizeandloadtheimagespublicvoidinit()
    [//Setupourimagemonitortracker=newMediaTracker(this);
    //SetthesizeandwidthofourappletmaxWidth=100;maxHeight=100;
    images=newImage[10];//Setupthedouble-bufferandresizeourapplet
    try{offScrImage=createImage(maxWidth,maxHeight);offScrGC=
    offScrImage.getGraphics();offScrGC.setColor(Color.lightGray);
    offScrGC.fillRect(0,0,maxWidth,maxHeight);
    resize(maxWidth,maxHeight);}catch(Exceptione)
    {e.printStackTrace();}
    //loadtheanimationimagesintoanarrayfor(inti=0;i
    imageFile=newString(\"images/Duke/T\"+String.valueOf(i+1)+\".gif\");
    images[i]=getImage(getDocumentBase(),imageFile)://Registerthis
    imagewiththetrackertracker.addImage(images[i],i);}try{//Use
    trackertomakesurealltheimagesareloadedtracker.waitForAll();}
    catch(InterruptedExceptione){}loaded=true;}
    //Paintthecurrentframe.publicvoidpaint(Graphicsg){if(loaded)
    {g.drawImage(offScrImage,0,0,this);}}
    //Start,setupourfirstimagepublicvoidstart(){if(tracker.checkID
    (index)){offScrGC.drawImage(images[index],0,0,this);}animator=new
    Thread(this);animator.start();}
    //Run,dotheanimationworkhere.//Grabanimage,pause,grabthenext...
    publicvoidrun(){//GettheidofthecurrentthreadThreadme=
    Thread.currentThread();
    //Ifouranimatorthreadexist,andisthecurrentthread...while
    ((animatr!=null)&&(animator==me)){if(tracker.checkID(index))
    {//Clearthebackgroundandgetthenextimage
    offScrGC.fillRect(0,0,100,100);
    offScrGCdrawImage(images[index],0,0,this);index++;//Loopbacktothe
    beginningandkeepgoingif(index>=images.length){index=0;}}
    //Delayheresoanimationlooksnormaltry{animator.sleep(200);}catch
    (InterruptedExceptione){}//Drawthenextframerepaint();}}}