java必學(xué)必會(huì)之線(xiàn)程(1)
一、線(xiàn)程的基本概念
線(xiàn)程理解:線(xiàn)程是一個(gè)程序里面不同的執(zhí)行路徑
每一個(gè)分支都叫做一個(gè)線(xiàn)程,main()叫做主分支,也叫主線(xiàn)程。
程只是一個(gè)靜態(tài)的概念,機(jī)器上的一個(gè).class文件,機(jī)器上的一個(gè).exe文件,這個(gè)叫做一個(gè)進(jìn)程。程序的執(zhí)行過(guò)程都是這樣的:首先把程序的代碼放到內(nèi)存的代碼區(qū)里面,代碼放到代碼區(qū)后并沒(méi)有馬上開(kāi)始執(zhí)行,但這時(shí)候說(shuō)明了一個(gè)進(jìn)程準(zhǔn)備開(kāi)始,進(jìn)程已經(jīng)產(chǎn)生了,但還沒(méi)有開(kāi)始執(zhí)行,這就是進(jìn)程,所以進(jìn)程其實(shí)是一個(gè)靜態(tài)的概念,它本身就不能動(dòng)。平常所說(shuō)的進(jìn)程的執(zhí)行指的是進(jìn)程里面主線(xiàn)程開(kāi)始執(zhí)行了,也就是main()方法開(kāi)始執(zhí)行了。進(jìn)程是一個(gè)靜態(tài)的概念,在我們機(jī)器里面實(shí)際上運(yùn)行的都是線(xiàn)程。
Windows操作系統(tǒng)是支持多線(xiàn)程的,它可以同時(shí)執(zhí)行很多個(gè)線(xiàn)程,也支持多進(jìn)程,因此Windows操作系統(tǒng)是支持多線(xiàn)程多進(jìn)程的操作系統(tǒng)。Linux和Uinux也是支持多線(xiàn)程和多進(jìn)程的操作系統(tǒng)。DOS就不是支持多線(xiàn)程和多進(jìn)程了,它只支持單進(jìn)程,在同一個(gè)時(shí)間點(diǎn)只能有一個(gè)進(jìn)程在執(zhí)行,這就叫單線(xiàn)程。
CPU難道真的很神通廣大,能夠同時(shí)執(zhí)行那么多程序嗎?不是的,CPU的執(zhí)行是這樣的:CPU的速度很快,一秒鐘可以算好幾億次,因此CPU把自己的時(shí)間分成一個(gè)個(gè)小時(shí)間片,我這個(gè)時(shí)間片執(zhí)行你一會(huì),下一個(gè)時(shí)間片執(zhí)行他一會(huì),再下一個(gè)時(shí)間片又執(zhí)行其他人一會(huì),雖然有幾十個(gè)線(xiàn)程,但一樣可以在很短的時(shí)間內(nèi)把他們通通都執(zhí)行一遍,但對(duì)我們?nèi)藖?lái)說(shuō),CPU的執(zhí)行速度太快了,因此看起來(lái)就像是在同時(shí)執(zhí)行一樣,但實(shí)際上在一個(gè)時(shí)間點(diǎn)上,CPU只有一個(gè)線(xiàn)程在運(yùn)行。
學(xué)習(xí)線(xiàn)程首先要理清楚三個(gè)概念:
1、進(jìn)程:進(jìn)程是一個(gè)靜態(tài)的概念
2、線(xiàn)程:一個(gè)進(jìn)程里面有一個(gè)主線(xiàn)程叫main()方法,是一個(gè)程序里面的,一個(gè)進(jìn)程里面不同的執(zhí)行路徑。
3、在同一個(gè)時(shí)間點(diǎn)上,一個(gè)CPU只能支持一個(gè)線(xiàn)程在執(zhí)行。因?yàn)镃PU運(yùn)行的速度很快,因此我們看起來(lái)的感覺(jué)就像是多線(xiàn)程一樣。
什么才是真正的多線(xiàn)程?如果你的機(jī)器是雙CPU,或者是雙核,這確確實(shí)實(shí)是多線(xiàn)程。
二、線(xiàn)程的創(chuàng)建和啟動(dòng)
在JAVA里面,JAVA的線(xiàn)程是通過(guò)java.lang.Thread類(lèi)來(lái)實(shí)現(xiàn)的,每一個(gè)Thread對(duì)象代表一個(gè)新的線(xiàn)程。創(chuàng)建一個(gè)新線(xiàn)程出來(lái)有兩種方法:第一個(gè)是從Thread類(lèi)繼承,另一個(gè)是實(shí)現(xiàn)接口runnable。VM啟動(dòng)時(shí)會(huì)有一個(gè)由主方法(public static void main())所定義的線(xiàn)程,這個(gè)線(xiàn)程叫主線(xiàn)程??梢酝ㄟ^(guò)創(chuàng)建Thread的實(shí)例來(lái)創(chuàng)建新的線(xiàn)程。你只要new一個(gè)Thread對(duì)象,一個(gè)新的線(xiàn)程也就出現(xiàn)了。每個(gè)線(xiàn)程都是通過(guò)某個(gè)特定的Thread對(duì)象所對(duì)應(yīng)的方法run()來(lái)完成其操作的,方法run()稱(chēng)為線(xiàn)程體。
范例1:使用實(shí)現(xiàn)Runnable接口創(chuàng)建和啟動(dòng)新線(xiàn)程
開(kāi)辟一個(gè)新的線(xiàn)程來(lái)調(diào)用run方法
package cn.galc.test; public class TestThread1{ public static void main(String args[]){ Runner1 r1 = new Runner1();//這里new了一個(gè)線(xiàn)程類(lèi)的對(duì)象出來(lái) //r1.run();//這個(gè)稱(chēng)為方法調(diào)用,方法調(diào)用的執(zhí)行是等run()方法執(zhí)行完之后才會(huì)繼續(xù)執(zhí)行main()方法 Thread t = new Thread(r1);//要啟動(dòng)一個(gè)新的線(xiàn)程就必須new一個(gè)Thread對(duì)象出來(lái) //這里使用的是Thread(Runnable target) 這構(gòu)造方法 t.start();//啟動(dòng)新開(kāi)辟的線(xiàn)程,新線(xiàn)程執(zhí)行的是run()方法,新線(xiàn)程與主線(xiàn)程會(huì)一起并行執(zhí)行 for(int i=0;i<10;i++){ System.out.println("maintheod:"+i); } } } /*定義一個(gè)類(lèi)用來(lái)實(shí)現(xiàn)Runnable接口,實(shí)現(xiàn)Runnable接口就表示這個(gè)類(lèi)是一個(gè)線(xiàn)程類(lèi)*/ class Runner1 implements Runnable{ public void run(){ for(int i=0;i<10;i++){ System.out.println("Runner1:"+i); } } }
多線(xiàn)程程序執(zhí)行的過(guò)程如下所示:
不開(kāi)辟新線(xiàn)程直接調(diào)用run方法
運(yùn)行結(jié)果如下:
范例2:繼承Thread類(lèi),并重寫(xiě)其run()方法創(chuàng)建和啟動(dòng)新的線(xiàn)程
package cn.galc.test; /*線(xiàn)程創(chuàng)建與啟動(dòng)的第二種方法:定義Thread的子類(lèi)并實(shí)現(xiàn)run()方法*/ public class TestThread2{ public static void main(String args[]){ Runner2 r2 = new Runner2(); r2.start();//調(diào)用start()方法啟動(dòng)新開(kāi)辟的線(xiàn)程 for(int i=0;i<=10;i++){ System.out.println("mainMethod:"+i); } } } /*Runner2類(lèi)從Thread類(lèi)繼承 通過(guò)實(shí)例化Runner2類(lèi)的一個(gè)對(duì)象就可以開(kāi)辟一個(gè)新的線(xiàn)程 調(diào)用從Thread類(lèi)繼承來(lái)的start()方法就可以啟動(dòng)新開(kāi)辟的線(xiàn)程*/ class Runner2 extends Thread{ public void run(){//重寫(xiě)run()方法的實(shí)現(xiàn) for(int i=0;i<=10;i++){ System.out.println("Runner2:"+i); } } }
使用實(shí)現(xiàn)Runnable接口和繼承Thread類(lèi)這兩種開(kāi)辟新線(xiàn)程的方法的選擇應(yīng)該優(yōu)先選擇實(shí)現(xiàn)Runnable接口這種方式去開(kāi)辟一個(gè)新的線(xiàn)程。因?yàn)榻涌诘膶?shí)現(xiàn)可以實(shí)現(xiàn)多個(gè),而類(lèi)的繼承只能是單繼承。因此在開(kāi)辟新線(xiàn)程時(shí)能夠使用Runnable接口就盡量不要使用從Thread類(lèi)繼承的方式來(lái)開(kāi)辟新的線(xiàn)程。
三、線(xiàn)程狀態(tài)轉(zhuǎn)換
3.1.線(xiàn)程控制的基本方法
3.2. sleep/join/yield方法介紹
sleep方法的應(yīng)用范例:
package cn.galc.test; import java.util.*; public class TestThread3 { public static void main(String args[]){ MyThread thread = new MyThread(); thread.start();//調(diào)用start()方法啟動(dòng)新開(kāi)辟的線(xiàn)程 try { /*Thread.sleep(10000); sleep()方法是在Thread類(lèi)里面聲明的一個(gè)靜態(tài)方法,因此可以使用Thread.sleep()的格式進(jìn)行調(diào)用 */ /*MyThread.sleep(10000); MyThread類(lèi)繼承了Thread類(lèi),自然也繼承了sleep()方法,所以也可以使用MyThread.sleep()的格式進(jìn)行調(diào)用 */ /*靜態(tài)方法的調(diào)用可以直接使用“類(lèi)名.靜態(tài)方法名” 或者“對(duì)象的引用.靜態(tài)方法名”的方式來(lái)調(diào)用*/ MyThread.sleep(10000); System.out.println("主線(xiàn)程睡眠了10秒種后再次啟動(dòng)了"); //在main()方法里面調(diào)用另外一個(gè)類(lèi)的靜態(tài)方法時(shí),需要使用“靜態(tài)方法所在的類(lèi).靜態(tài)方法名”這種方式來(lái)調(diào)用 /* 所以這里是讓主線(xiàn)程睡眠10秒種 在哪個(gè)線(xiàn)程里面調(diào)用了sleep()方法就讓哪個(gè)線(xiàn)程睡眠,所以現(xiàn)在是主線(xiàn)程睡眠了。 */ } catch (InterruptedException e) { e.printStackTrace(); } //thread.interrupt();//使用interrupt()方法去結(jié)束掉一個(gè)線(xiàn)程的執(zhí)行并不是一個(gè)很好的做法 thread.flag=false;//改變循環(huán)條件,結(jié)束死循環(huán) /** * 當(dāng)發(fā)生InterruptedException時(shí),直接把循環(huán)的條件設(shè)置為false即可退出死循環(huán), * 繼而結(jié)束掉子線(xiàn)程的執(zhí)行,這是一種比較好的結(jié)束子線(xiàn)程的做法 */ /** * 調(diào)用interrupt()方法把正在運(yùn)行的線(xiàn)程打斷 相當(dāng)于是主線(xiàn)程一盆涼水潑上去把正在執(zhí)行分線(xiàn)程打斷了 分線(xiàn)程被打斷之后就會(huì)拋InterruptedException異常,這樣就會(huì)執(zhí)行return語(yǔ)句返回,結(jié)束掉線(xiàn)程的執(zhí)行 所以這里的分線(xiàn)程在執(zhí)行完10秒鐘之后就結(jié)束掉了線(xiàn)程的執(zhí)行 */ } } class MyThread extends Thread { boolean flag = true;// 定義一個(gè)標(biāo)記,用來(lái)控制循環(huán)的條件 public void run() { /* * 注意:這里不能在run()方法的后面直接寫(xiě)throw Exception來(lái)拋異常, * 因?yàn)楝F(xiàn)在是要重寫(xiě)從Thread類(lèi)繼承而來(lái)的run()方法,重寫(xiě)方法不能拋出比被重寫(xiě)的方法的不同的異常。 * 所以這里只能寫(xiě)try……catch()來(lái)捕獲異常 */ while (flag) { System.out.println("==========" + new Date().toLocaleString() + "==========="); try { /* * 靜態(tài)方法的調(diào)用格式一般為“類(lèi)名.方法名”的格式去調(diào)用 在本類(lèi)中聲明的靜態(tài)方法時(shí)調(diào)用時(shí)直接寫(xiě)靜態(tài)方法名即可。 當(dāng)然使用“類(lèi)名.方法名”的格式去調(diào)用也是沒(méi)有錯(cuò)的 */ // MyThread.sleep(1000);//使用“類(lèi)名.方法名”的格式去調(diào)用屬于本類(lèi)的靜態(tài)方法 sleep(1000);//睡眠的時(shí)如果被打斷就會(huì)拋出InterruptedException異常 // 這里是讓這個(gè)新開(kāi)辟的線(xiàn)程每隔一秒睡眠一次,然后睡眠一秒鐘后再次啟動(dòng)該線(xiàn)程 // 這里在一個(gè)死循環(huán)里面每隔一秒啟動(dòng)一次線(xiàn)程,每個(gè)一秒打印出當(dāng)前的系統(tǒng)時(shí)間 } catch (InterruptedException e) { /* * 睡眠的時(shí)一盤(pán)冷水潑過(guò)來(lái)就有可能會(huì)打斷睡眠 * 因此讓正在運(yùn)行線(xiàn)程被一些意外的原因中斷的時(shí)候有可能會(huì)拋被打擾中斷(InterruptedException)的異常 */ return; // 線(xiàn)程被中斷后就返回,相當(dāng)于是結(jié)束線(xiàn)程 } } } }
運(yùn)行結(jié)果:
join方法的使用范例:
package cn.galc.test; public class TestThread4 { public static void main(String args[]) { MyThread2 thread2 = new MyThread2("mythread"); // 在創(chuàng)建一個(gè)新的線(xiàn)程對(duì)象的同時(shí)給這個(gè)線(xiàn)程對(duì)象命名為mythread thread2.start();// 啟動(dòng)線(xiàn)程 try { thread2.join();// 調(diào)用join()方法合并線(xiàn)程,將子線(xiàn)程mythread合并到主線(xiàn)程里面 // 合并線(xiàn)程后,程序的執(zhí)行的過(guò)程就相當(dāng)于是方法的調(diào)用的執(zhí)行過(guò)程 } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i <= 5; i++) { System.out.println("I am main Thread"); } } } class MyThread2 extends Thread { MyThread2(String s) { super(s); /* * 使用super關(guān)鍵字調(diào)用父類(lèi)的構(gòu)造方法 * 父類(lèi)Thread的其中一個(gè)構(gòu)造方法:“public Thread(String name)” * 通過(guò)這樣的構(gòu)造方法可以給新開(kāi)辟的線(xiàn)程命名,便于管理線(xiàn)程 */ } public void run() { for (int i = 1; i <= 5; i++) { System.out.println("I am a\t" + getName()); // 使用父類(lèi)Thread里面定義的 //public final String getName(),Returns this thread's name. try { sleep(1000);// 讓子線(xiàn)程每執(zhí)行一次就睡眠1秒鐘 } catch (InterruptedException e) { return; } } } }
運(yùn)行結(jié)果:
yield方法的使用范例:
package cn.galc.test; public class TestThread5 { public static void main(String args[]) { MyThread3 t1 = new MyThread3("t1"); /* 同時(shí)開(kāi)辟了兩條子線(xiàn)程t1和t2,t1和t2執(zhí)行的都是run()方法 */ /* 這個(gè)程序的執(zhí)行過(guò)程中總共有3個(gè)線(xiàn)程在并行執(zhí)行,分別為子線(xiàn)程t1和t2以及主線(xiàn)程 */ MyThread3 t2 = new MyThread3("t2"); t1.start();// 啟動(dòng)子線(xiàn)程t1 t2.start();// 啟動(dòng)子線(xiàn)程t2 for (int i = 0; i <= 5; i++) { System.out.println("I am main Thread"); } } } class MyThread3 extends Thread { MyThread3(String s) { super(s); } public void run() { for (int i = 1; i <= 5; i++) { System.out.println(getName() + ":" + i); if (i % 2 == 0) { yield();// 當(dāng)執(zhí)行到i能被2整除時(shí)當(dāng)前執(zhí)行的線(xiàn)程就讓出來(lái)讓另一個(gè)在執(zhí)行run()方法的線(xiàn)程來(lái)優(yōu)先執(zhí)行 /* * 在程序的運(yùn)行的過(guò)程中可以看到, * 線(xiàn)程t1執(zhí)行到(i%2==0)次時(shí)就會(huì)讓出線(xiàn)程讓t2線(xiàn)程來(lái)優(yōu)先執(zhí)行 * 而線(xiàn)程t2執(zhí)行到(i%2==0)次時(shí)也會(huì)讓出線(xiàn)程給t1線(xiàn)程優(yōu)先執(zhí)行 */ } } } }
運(yùn)行結(jié)果如下:
以上就是本文的全部?jī)?nèi)容,對(duì)java線(xiàn)程進(jìn)行全面學(xué)習(xí),希望可以幫助到大家。
- java向多線(xiàn)程中傳遞參數(shù)的三種方法詳細(xì)介紹
- java 實(shí)現(xiàn)線(xiàn)程同步的方式有哪些
- JAVA中 終止線(xiàn)程的方法介紹
- Java多線(xiàn)程下載的實(shí)現(xiàn)方法
- Java多線(xiàn)程的用法詳解
- java線(xiàn)程之使用Runnable接口創(chuàng)建線(xiàn)程的方法
- Java線(xiàn)程關(guān)閉的3種方法
- java基本教程之Thread中start()和run()的區(qū)別 java多線(xiàn)程教程
- java基本教程之java線(xiàn)程等待與java喚醒線(xiàn)程 java多線(xiàn)程教程
相關(guān)文章
SpringBoot加入Guava Cache實(shí)現(xiàn)本地緩存代碼實(shí)例
這篇文章主要介紹了SpringBoot加入Guava Cache實(shí)現(xiàn)本地緩存代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09Java并發(fā)編程之volatile與JMM多線(xiàn)程內(nèi)存模型
這篇文章主要介紹了Java并發(fā)volatile與JMM多線(xiàn)程內(nèi)存模型,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05JAVA為什么要使用封裝及如何封裝經(jīng)典實(shí)例
這篇文章主要給大家介紹了關(guān)于JAVA為什么要使用封裝及如何封裝的相關(guān)資料,封裝就是將屬性私有化,提供公有的方法訪(fǎng)問(wèn)私有屬性,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10Java通過(guò)關(guān)閉Socket終止線(xiàn)程
這篇文章主要為大家詳細(xì)介紹了Java通過(guò)關(guān)閉Socket終止線(xiàn)程的相關(guān)代碼2017-04-04Springboot 使用 JSR 303 對(duì) Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn)
這篇文章主要介紹了Springboot 使用 JSR 303 對(duì) Controller 控制層校驗(yàn)及 Service 服務(wù)層 AOP 校驗(yàn) 使用消息資源文件對(duì)消息國(guó)際化的相關(guān)知識(shí),需要的朋友可以參考下2017-12-12spring boot啟動(dòng)時(shí)加載外部配置文件的方法
這篇文章主要給大家介紹了關(guān)于spring boot啟動(dòng)時(shí)加載外部配置文件的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02Java中spring boot 字符串判斷是否為空方法小結(jié)
這篇文章主要介紹了Java中spring boot字符串判斷是否為空,通過(guò)安裝依賴(lài),結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-11-11Spring AOP有多少個(gè)通知以及它們的執(zhí)行順序介紹
這篇文章主要介紹了Spring AOP有多少個(gè)通知以及它們的執(zhí)行順序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11