並發編程 -- 多線程(一)
作者 : Stanley 羅昊
【轉載請注明出處和署名,謝謝!】
進程
在理解多線程之前,我們先需要了解什么是進程?
進程說白了就是在你的內存空間中開辟出的一個獨立的空間;
如果還不理解的話,我再解釋一下;
想必各位之前都安裝過軟件吧,你安裝完軟件之后,在你的軟件安裝包里面是不是有一個.exe文件,那你雙擊exe文件的時候,在你的任務管理器,在里面就有一個進程選項卡,就是說,每當你打開一個exe文件的時候,它都會顯示在任務管理器的進程當中,所以就可以把運行中的任意一款軟件,都可以把它看做一個進程;
當然,以上的操作方式是在windows系統的操作的,也就是說,想查看Windows的進程,只需要在任務管理器中查看即可;
在linux下使用命令 ps 或 pstree、ps -eflgrep;
如果想在linux系統下查看java的相關進程,命令為:jps;
那么問題就來了,當你打開QQ的時候,是不是就是開啟了一個進程,當你開始使用它並且聊天的時候,比如你是a,你現在要跟b聊天然后再去跟c聊天,那么這樣的操作是不是相互獨立的呢?也就是說,你現在要跟b發送你的游戲密碼,這個時候c問你晚上吃啥飯,你發的密碼c知道嗎?肯定不會啦,所以你跟b聊天的時候,是不會影響你跟c聊天的,因為你跟b c 是相互獨立的;
那么,在這個里面,你跟他們每個人產生的通話底層是怎么實現的呢?
底層就是靠線程去實現的;
線程
什么是線程呢?
線程是指程序在執行過程中,能夠執行程序代碼的一個執行單元,在Java語言中,線程有四種狀態:運行,就緒,掛起,結束。一般情況下,一個操作系統是有多個進程,那么每個進程都要對應多個線程;
線程與進程有區別嗎?
有,進程是一段正在運行的程序,而線程有時也被稱為輕量級進程,它是進程的執行單元,一個進程可以擁有多個線程,各個線程之間共享程序的內存空間,但是,各個線程擁有自己的棧空間。
一個進程可以有很多線程,每條線程並行執行不同的任務;
實現單個線程
在之前的一些簡單的java練習中,我們運行的時候,是不是都是在main方法中測試運行啊,那么,在這之前,我僅僅編寫了一些非常簡單的java代碼,甚至就在main方法中輸出了一句話,就可以直接完成運行,在這期間,我並沒有創建有關線程方面的方法以及程序,那么就怎么實現運行了呢?
其實很簡單,Main方法既然能運行你的程序,那么必然就會有一個線程,那么這個線程就是單線程,那,我們如何查看本次運行線程的線程名呢?
我們僅需在main方法里面輸出以下即可:
System.out.println(Thread.currentThread());
打印出來后,我們就可以看到線程名師Main,因為就一個線程,所以main就是主線程;
那么就一個線程,就表明,在這之前,我們所做的一些練習程序都是單線程的;
線程的創建方式
兩種:
1.繼承(extends) Thread
繼承完Thread類之后需要重寫run方法;
2.實現(implem) Runnable接口
也許需要重寫run方法;
繼承Thread類創建線程
講了那么多,那么就開始上手實操一下,我們將要練習的是繼承Thread來創建一個線程;
首先,我們創建一個類(MyThread)然后繼承Thread類;
繼承完Thread后,實現run方法;
run方法的作用就是,相當於這個線程對用戶提供的一個接口;
所以,用戶有什么業務邏輯,都需要寫在run方法里面:
public class MyThread extends Thread{ public void run(){ //作用:相當於這個線程向用戶提供的接口,用戶有什么樣的業務邏輯,寫在這個方法中 } }
那我們現在就讓這個run方法就實現一個簡單的打印;
這個就是我使用了第一種方式來創建了一個線程;
那么,你這個線程創建完成后,如果你想調用它怎么辦?
調用線程
我再建一個類(TestThread);
然后提供一個Main方法;
在main方法中創建一個線程,語法:
//創建一個線程 MyThread mt = new MyThred();
這個就是創建線程,創建完后,下一步我們需要調用start方法;
public class TestThread{ public static void main (String [] args){ //創建線程 MyThread mt = new MyThred(); //啟動線程 mt.start(); } }
為什么要調用start,而不是run?
其實很簡單,mt.start就是啟動你的線程,那么啟動后,它底層就會去調用你的run方法;
這個就是線程的啟動式start方法;
我們運行一下后,看一下控制台打印:
就是一些輸出,感覺就跟調用方法一樣,對吧;
那么,接下來,我們看一下第二種創建方式
實現(implem) Runnable接口創建線程
這種方法跟上面的那種,大同小異,只不過上面那個是繼承,這個是實現接口;
實現Runnable接口后,需要實現它的run方法;
public class MyRunnableThread implements Runable{ public void run(){ //作用:相當於這個線程向用戶提供的接口,用戶有什么樣的業務邏輯,寫在這個方法中 } }
現在,我們用第二種方式創建了一個線程,當然,業務邏輯跟上面的那個相同,因為舉例,所以沒有深究別的;
第二種方式調用線程就有所不同
因為運行線程永遠需要Thread里面的start方法來啟動線程,所以需要把Thread創建出來,再將創建出來的線程放進去;
所以打印出來的結果是跟上面的結果是一樣的,這里就不再放圖上去了;
線程關鍵字分析
start,是線程啟動的方法;
run方法是線程執行過程中調用的方法(默認調用),在上面的例子我們也看到了,你並沒有手動去調用run方法,是他自動調用的,就跟你創建對象的時候,默認調用構造方法一樣;
深究run與start
那,啟動線程一定是要用staet方法啟動,我試試不用它,我直接調用Thread中的run方法可行嗎?
可行,因為拋開線程,你本身就是實例化了Thread這個類,並調用該類中的run方法是沒有問題的,但是,不納入線程中!!
我們直接調用run方法后,發現,方法可以正常打印,因為,僅僅完成了普通方法的調用,實際上並沒有啟動線程;