Java多线程(1)
3、具體內(nèi)容
???????? 從多線程開始,Java正式進(jìn)入到應(yīng)用部分,而對于多線程的開發(fā),從Java EE上表現(xiàn)的并不是特別多,但是在Android開發(fā)之中使用較多,并且需要提醒的是,筆試或面試的過程之中,多線程所問到的問題是最多的。
3.1、多線程的基本概念
???????? 如果要想解釋多線程,那么首先應(yīng)該從單進(jìn)程開始講起,最早的DOS系統(tǒng)有一個最大的特征:一旦電腦出現(xiàn)了病毒,電腦會立刻死機(jī),因為傳統(tǒng)DOS系統(tǒng)屬于單進(jìn)程的處理方式,即:在同一個時間段上只能有一個程序執(zhí)行。后來到了windows時代,電腦即使(非致命)存在了病毒,那么也可以正常使用,只是慢一些而已,因為windows屬于多進(jìn)程的處理操作,但是這個 時候的資源依然只有一塊,所以在同一個時間段上會有多個程序共同執(zhí)行,而在一個時間點上只能有一個程序在執(zhí)行,多線程是在一個進(jìn)程基礎(chǔ)之上的進(jìn)一步劃分, 因為進(jìn)程的啟動所消耗的時間是非常長的,所以在進(jìn)程之上的進(jìn)一步的劃分就變得非常重要,而且性能也會有所提高。
?
???????? 所有的線程一定要依附于進(jìn)程才能夠存在,那么進(jìn)程一旦消失了,線程也一定會消失,但是反過來不一定。而Java是為數(shù)不多的支持多線程的開發(fā)語言之一。
3.2、多線程的實現(xiàn)(重點)
???????? 在Java之中,如果要想實現(xiàn)多線程的程序,那么就必須依靠一個線程的主體類(就好比主類的概念一樣,表示的是一個線程的主類),但是這個線程的主體類在定義的時候也需要有一些特殊的要求,這個類可以繼承Thread類或?qū)崿F(xiàn)Runnable接口來完成定義。
3.2.1 、繼承Thread類實現(xiàn)多線程
???????? java.lang.Thread是一個負(fù)責(zé)線程操作的類,任何的類只需要繼承了Thread類就可以成為一個線程的主類,但是既然是主類必須有它的使用方法,而線程啟動的主方法是需要覆寫Thread類中的run()方法才可以。
范例:定義一個線程的主體類
| class MyThread extends Thread { // 線程的主體類 ??? private String title; ??? public MyThread(String title) { ??????? this.title = title; ??? } ??? @Override ??? public void run() { // 線程的主方法 ??????? for (int x = 0; x < 50; x++) { ??????????? System.out.println(this.title + "運行,x = " + x); ??????? } ??? } } |
???????? 現(xiàn)在按照道理來講,已經(jīng)出現(xiàn)了線程類,并且里面也存在了相應(yīng)的操作方法,那么就應(yīng)該產(chǎn)生對象并調(diào)用里面的方法,自然下面編寫出了下的程序。
| public class TestDemo { ??? public static void main(String[] args) throws Exception { ??????? MyThread mt1 = new MyThread("線程A") ; ??????? MyThread mt2 = new MyThread("線程B") ; ??????? MyThread mt3 = new MyThread("線程C") ; ??????? mt1.run() ; ??????? mt2.run() ; ??????? mt3.run() ; ??? } } |
???????? 但是以上的操作實話而言并沒有真正的啟動多線程,因為多個線程彼此之間的執(zhí)行一定是交替的方式運行,而此時是順序執(zhí)行,即:每一個對象的代碼執(zhí)行完之后才向下繼續(xù)執(zhí)行。如果要想在程序之中真正的啟動多線程,必須依靠Thread類的一個方法:public void start(),表示真正啟動多線程,調(diào)用此方法后會間接調(diào)用run()方法。
| public class TestDemo { ??? public static void main(String[] args) throws Exception { ??????? MyThread mt1 = new MyThread("線程A") ; ??????? MyThread mt2 = new MyThread("線程B") ; ??????? MyThread mt3 = new MyThread("線程C") ; ??????? mt1.start() ; ??????? mt2.start() ; ??????? mt3.start() ; ??? } } |
???????? 此時可以發(fā)現(xiàn),多個線程之間彼此交替執(zhí)行,但是每次的執(zhí)行結(jié)果肯定是不一樣的。通過以上的代碼就可以得出結(jié)論:要想啟動線程必須依靠Thread類的start()方法執(zhí)行,線程啟動之后會默認(rèn)調(diào)用了run()方法。
疑問?為什么線程啟動的時候必須調(diào)用start()而不是直接調(diào)用run()?
???????? 發(fā)現(xiàn)調(diào)用了start()之后,實際上它執(zhí)行的還是覆寫后的run()方法,那為什么不直接調(diào)用run()方法呢?那么為了解釋此問題,下面打開Thread類的源代碼,觀察一下start()方法的定義。
| ??? public synchronized void start() { ??????? if (threadStatus != 0) ??????????? throw new IllegalThreadStateException(); ??????? group.add(this); ??????? boolean started = false; ??????? try { ??????????? start0(); ??????????? started = true; ??????? } finally { ??????????? try { ??????????????? if (!started) { ??????????????????? group.threadStartFailed(this); ??????????????? } ??????????? } catch (Throwable ignore) { ??????????? } ??????? } ??? } ??? private native void start0(); |
???????? 打開此方法的實現(xiàn)代碼首先可以發(fā)現(xiàn)方法會拋出一個“IllegalThreadStateException”異常。按照之前所學(xué)習(xí)的方式來講,如果一個方法之中使用了throw拋出一個異常對象,那么這個異常應(yīng)該使用try…catch捕獲,或者是方法的聲明上使用throws拋出,但是這塊都沒有,因為這個異常類是屬于運行時異常(RuntimeException)的子類。
| java.lang.Object ???????? |- java.lang.Throwable ?????????????????? |- java.lang.Exception ??????????????????????????? |- java.lang.RuntimeException ???????????????????????????????????? |- java.lang.IllegalArgumentException ?????????????????????????????????????????????? |- java.lang.IllegalThreadStateException |
???????? 當(dāng)一個線程對象被重復(fù)啟動之后會拋出此異常,即:一個線程對象只能啟動唯一的一次。在start()方法之中有一個最為關(guān)鍵的部分就是start0()方法,而且這個方法上使用了一個native關(guān)鍵字的定義。
???????? native關(guān)鍵字指的是Java本地接口調(diào)用(Java Native Interface),即:是使用Java調(diào)用本機(jī)操作系統(tǒng)的函數(shù)功能完成一些特殊的操作,而這樣的代碼開發(fā)在Java之中幾乎很少出現(xiàn),因為Java的最大特點是可移植性,如果一個程序只能在固定的操作系統(tǒng)上使用,那么可移植性就將徹底的喪失,所以,此操作一般只作為興趣使用。
???????? 多線程的實現(xiàn)一定需要操作系統(tǒng)的支持,那么以上的start0()方法實際上就和抽象方法很類似沒有方法體,而這個方法體交給JVM去實現(xiàn),即:在windows下的JVM可能使用A方法實現(xiàn)了start0(),而在linux下的JVM可能使用了B方法實現(xiàn)了start0(),但是在調(diào)用的時候并不會去關(guān)心具體是何方式實現(xiàn)了start0()方法,只會關(guān)心最終的操作結(jié)果,交給JVM去匹配了不同的操作系統(tǒng)。
???????? 所以在多線程操作之中,使用start()方法啟動多線程的操作是需要進(jìn)行操作系統(tǒng)函數(shù)調(diào)用的。
3.2.2 、實現(xiàn)Runnable接口實現(xiàn)多線程
???????? 使用Thread類的確是可以方便的進(jìn)行多線程的實現(xiàn),但是這種方式最大的缺點就是單繼承的問題,為此,在java之中也可以利用Runnable接口來實現(xiàn)多線程,而這個接口的定義如下:
| public interface Runnable { ??? public void run(); } |
分享:如何區(qū)分新老接口?
???????? 在JDK之中,由于其發(fā)展的時間較長,那么會出現(xiàn)一些新的接口和老的接口,這兩者有一個最大的明顯特征:所有最早提供的接口方法里面都不加上public,所有的新接口里面都有public。
范例:通過Runnable接口實現(xiàn)多線程
| class MyThread implements Runnable { // 線程的主體類 ??? private String title; ??? public MyThread(String title) { ??????? this.title = title; ??? } ??? @Override ??? public void run() { // 線程的主方法 ??????? for (int x = 0; x < 50; x++) { ??????????? System.out.println(this.title + "運行,x = " + x); ??????? } ??? } } |
???????? 這個時候和之前的繼承Thread類區(qū)別不大,但是唯一的好處就是避免了單繼承局限,不過現(xiàn)在問題也就來了。剛剛解釋過,如果要想啟動多線程依靠Thread類的start()方法完成,之前繼承Thread類的時候可以將此方法直接繼承過來使用,但現(xiàn)在實現(xiàn)的是Runable接口,沒有這個方法可以繼承了,為了解決這個問題,還是需要依靠Thread類完成,在Thread類中定義了一個構(gòu)造方法:public Thread(Runnable target),接收Runnable接口對象。
范例:利用Thread類啟動多線程
| public class TestDemo { ??? public static void main(String[] args) throws Exception { ??????? MyThread mt1 = new MyThread("線程A"); ??????? MyThread mt2 = new MyThread("線程B"); ??????? MyThread mt3 = new MyThread("線程C"); ??????? new Thread(mt1).start(); ??????? new Thread(mt2).start(); ??????? new Thread(mt3).start(); ??? } } |
???????? 這個時候就實現(xiàn)了多線程的啟動,而且沒有了單繼承局限。
3.2.3 、Thread類和Runnable接口實現(xiàn)多線程的區(qū)別(面試題)
???????? 現(xiàn)在Thread類和Runnable接口都可以做為同一功能的方式來實現(xiàn)多線程,那么這兩者如果從Java的實際開發(fā)而言,肯定使用Runnable接口,因為可以有效的避免單繼承的局限,那么除了這些之外,這兩種方式是否還有其他聯(lián)系呢?
???????? 為了解釋這兩種方式的聯(lián)系,下面可以打開Thread類的定義:
| public class Thread extends Object implements Runnable |
???????? 發(fā)現(xiàn)Thread類也是Runnable接口的子類,而如果真的是這樣,那么之前程序的結(jié)構(gòu)就變?yōu)榱艘韵滦问健?/p>
?
???????? 這個時候所表現(xiàn)出來的代碼模式非常類似于代理設(shè)計模式,但是它并不是嚴(yán)格意義上代理設(shè)計模式,因為從嚴(yán)格來講代理設(shè)計模式之中,代理主題所能夠使用的方法依然是接口中定義的run()方法,而此處代理主題調(diào)用的是start()方法,所以只能夠說形式上類似于代理設(shè)計模式,但本質(zhì)上還是有差別的。
???????? 但是除了以上的聯(lián)系之外,對于Runnable和Thread類還有一個不太好區(qū)分的區(qū)別:使用Runnable接口可以更加方便的表示出數(shù)據(jù)共享的概念。
范例:通過繼承Thread類實現(xiàn)賣票程序
| package cn.mldn.demo; class MyThread extends Thread { // 線程的主體類 ??? private int ticket = 5; // 一共5張票 ??? @Override ??? public void run() { // 線程的主方法 ??????? for (int x = 0; x < 50; x++) { ??????????? if (this.ticket > 0) { ??????????????? System.out.println("賣票,ticket = " + this.ticket --); ??????????? } ??????? } ??? } } public class TestDemo { ??? public static void main(String[] args) throws Exception { ??????? MyThread mt1 = new MyThread(); ??????? MyThread mt2 = new MyThread(); ??????? MyThread mt3 = new MyThread(); ??????? mt1.start() ; ??????? mt2.start() ; ??????? mt3.start() ; ??? } } |
???????? 現(xiàn)在的結(jié)果是一共買出了15張票,等于是每一個線程對象各自賣各自的5張票,這個時候的內(nèi)存關(guān)系圖如下:
?
范例:利用Runnable來實現(xiàn)多線程
| package cn.mldn.demo; class MyThread implements Runnable { // 線程的主體類 ??? private int ticket = 5; // 一共5張票 ??? @Override ??? public void run() { // 線程的主方法 ??????? for (int x = 0; x < 50; x++) { ??????????? if (this.ticket > 0) { ??????????????? System.out.println("賣票,ticket = " + this.ticket--); ??????????? } ??????? } ??? } } public class TestDemo { ??? public static void main(String[] args) throws Exception { ??????? MyThread mt = new MyThread(); ??????? new Thread(mt).start(); ??????? new Thread(mt).start(); ??????? new Thread(mt).start(); ??? } } |
??? 現(xiàn)在使用繼承Thread類也可以實現(xiàn)同樣的功能。
| package cn.mldn.demo; class MyThread extends Thread { // 線程的主體類 ??? private int ticket = 5; // 一共5張票 ??? @Override ??? public void run() { // 線程的主方法 ??????? for (int x = 0; x < 50; x++) { ??????????? if (this.ticket > 0) { ??????????????? System.out.println("賣票,ticket = " + this.ticket--); ??????????? } ??????? } ??? } } public class TestDemo { ??? public static void main(String[] args) throws Exception { ??????? MyThread mt = new MyThread(); ??????? new Thread(mt).start(); ??????? new Thread(mt).start(); ??????? new Thread(mt).start(); ??? } } |
面試題:請解釋多線程的兩種實現(xiàn)方式及區(qū)別?分別編寫程序以驗證兩種實現(xiàn)方式。
???????? · 多線程的兩種實現(xiàn)方式都需要一個線程的主類,而這個類可以實現(xiàn)Runnable接口或繼承Thread類,不管使用何種方式都必須在子類之中覆寫run()方法,此方法為線程的主方法;
???????? · Thread類是Runnable接口的子類,而且使用Runnable接口可以避免單繼承局限,以及更加方便的實現(xiàn)數(shù)據(jù)共享的概念。
轉(zhuǎn)載于:https://www.cnblogs.com/guwenren/archive/2013/04/10/3013203.html
總結(jié)
以上是生活随笔為你收集整理的Java多线程(1)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Cognos 10.1 饼图百分比小数点
- 下一篇: 英宝通4.0公开课---致力于提供最新的