目錄
- 1. 理解執行緒與行程
- 2、理解并行和并發
- 3、特殊的一個單執行緒:主執行緒(Main執行緒)
- 4、 創建多執行緒的四種方式
- 5、使用匿名內部類方式創建執行緒
- 6、執行緒安全問題
- 7、解決執行緒安全問題:執行緒同步
- 8、執行緒常用方法
- 9、執行緒的狀態
- 10、執行緒池
1. 理解執行緒與行程
由于并發肯定涉及到多執行緒,因此在進入并發編程主題之前,我們先來了解一下行程和執行緒的由來,這對后面對并發編程的理解將會有很大的幫助,
行程和執行緒的對比這一知識點由于過于基礎,正因為過于基礎,所以我們更應該透徹它!我們必須掌握什么是執行緒和行程,掌握執行緒與行程的關系、區別及優缺點 !
1.1、何為行程?
首先我們來看一下行程的概念:
行程:是指一個記憶體中運行的應用程式,每個行程都有一個獨立的記憶體空間,一個應用程式可以同時運行多個行程;行程也是程式的一次執行程序,是系統運行程式的基本單位;系統運行一個程式即是一個行程從創建、運行到消亡的程序,
看完之后,是不是感覺很抽象?很懵bi?懵bi就對了,說明你和我智商一樣高....~開個玩笑~
不妨先憋棄上面的概念,放松一下大腦,雙擊打開LOL,秒選德馬打野,輸了直接退出游戲并且保持微笑,然后正襟危坐心平氣和的看宜春寫的博客....
這個時候的你不僅僅是愉快的擼了一把游戲,而且還親自體驗擼了一把行程...其實在你雙擊打開LOL的時候就已經創建了行程,此話怎講?眾所周知,我們的電腦安裝的軟體比如:LOL、微信、谷歌等等都是存盤在我們的硬碟上的,硬碟上的資料可以說是永久存盤(ORM),當我們雙擊LOL的時候,LOL程式執行就進入了記憶體中,所有的程式必須進入記憶體中才能執行,記憶體屬于臨時存盤(RAM),而進入記憶體的程式都可以叫做是行程,把LOL程式退出的時候,LOL程式就會退出記憶體,行程也就隨之銷毀了!因此說各位擼了一把行程也不為過吧,
啥?字太多了,看的不夠明了,不如看圖得勁....額,,,
上面主要是通過抽象的描述了行程,其實行程是可以很直觀的看的到的,我們可以再電腦底部任務欄,右鍵----->打開任務管理器,可以查看當前任務的行程:
其實,關于執行緒博主我完全可以一兩句話概括,但是這樣并不負責,畢竟這篇文章標題就是要讓你徹底入門java多執行緒,如果連行程都理解不好談何徹底理解多執行緒?
1.2、何為執行緒?
同樣的,我們先來看執行緒的概念
執行緒是行程中的一個執行單位,負責當前行程中程式的執行,一個行程中至少有一個執行緒,也就是說一個行程可以有多個執行緒的,而多個執行緒的行程運用程式就叫做多執行緒程式
執行緒的概念稍微好理解很多,但是想更深層次的去理解光靠上面一段文字的概述是完全不夠的!
這不打LOL的程序中,屬實卡的一批,果然花高價998買的6手戴爾筆記本打LOL屬實像極了愛情,這個時候不得不雙擊打開電腦安全管家進行殺毒,果然2500天沒有進行過病毒查殺,我天,,,其實我相信很多人都用過電腦管家或者手機管家之類的安全軟體,我們都很清楚我們開啟病毒查殺之后一般要幾分鐘掃描查殺,這個時候我們是可以讓它后臺進行的,我們不會等而是開啟另一個垃圾清理的功能,這個時候我們也不會等而是再去啟動電腦加速功能,等到 這些操作都完成之后果斷退出電腦管家,繼續LOL,果然高價998買的6手戴爾筆記本再怎么殺毒打LOL還是照樣的卡....
其實清楚執行緒必然涉及到CPU的相關概念了,將上面文字所描述的用圖片概括,大致為:

1.3、何為多執行緒?
從上一節中,我們也提到過多執行緒,所以理解起來應該不難,
多執行緒就是多個執行緒同時運行 或 交替運行,
單核CPU:交替運行,
多核CPU:同時運行,
其實,多執行緒程式并不能提高程式的運行速度,但能夠提高程式運行效率,讓CPU的使用率更高,
1.4、何為執行緒調度優先級?
說起執行緒調度優先級這個概念,就讓我想到現在我們大部分人投簡歷一樣,如果你的學歷或者作業經驗越高,那么你的優先級就越高,面試官很大幾率就會讓你去面試但也不是一定只是幾率特別大,如果執行緒的優先級相同,那么會隨機選擇一個(執行緒隨機性)!在我們每個人的電腦中執行緒是可以設定執行緒的優先級的,但是生活中沒有優先級(學歷、作業經驗)的孩子就只能靠自己的能力了~媽耶,太真實了...~
執行緒優先級具有繼承特性比如A執行緒啟動B執行緒,則B執行緒的優先級和A是一樣的,
執行緒優先級具有隨機性也就是說執行緒優先級高的不一定每一次都先執行完,只是被執行的可能性更大,
在今后的多執行緒學習旅游中我們會使用到getPriority()方法獲取執行緒的優先級,
1.5、為什么提倡使用多執行緒而不是多行程?
執行緒與行程相似,但執行緒是一個比行程更小的執行單位,是程式執行的最小單位,一個行程在其執行的程序中可以產生多個執行緒,與行程不同的是同類的多個執行緒共享同一塊記憶體空間和一組系統資源,所以系統在產生一個執行緒,或是在各個執行緒之間作切換作業時,負擔要比行程小得多,也正因為如此,執行緒也被稱為輕量級行程,同時執行緒是程式執行的最小單位,使用多執行緒而不是用多行程去進行并發程式的設計,是因為執行緒間的切換和調度的成本遠遠小于行程,
而使用多執行緒,多執行緒會將程式運行方式從串行運行變為并發運行,效率會有很大提高,
2、理解并行和并發
在博主認為并發和并行是兩個非常容易被混淆的概念,為了防止繞暈大家,所以我選擇長話短說!
- 并發:一個
時間段內同時發生(并不是同時發生), - 并行:同一
時刻發生(真正的同時發生),
它們都可以表示兩個或者多個任務一起執行,但是偏重點有些不同,
于此同時,我們不妨回顧一下上面所提到過的CPU,并再次理解并發與并行的區別,從而溫故知新 ~我TM簡直是個天才!~
單核CPU:交替運行【并發】
多核CPU:同時運行【并行】
并發給人的感覺是同時運行,那是因為分時交替運行的時間是非常短的!
3、特殊的一個單執行緒:主執行緒(Main執行緒)
我們常說的主執行緒就是Main執行緒,它是一個特殊的單執行緒,話不多說,直接擼碼:
定義一個用于測驗的demo類Person
package demo;
public class Person {
public String name;
public Person(String name){
this.name=name;
}
public void run(){
int i=1;
while (i<5){
System.out.println(name+i);
i++;
}
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
撰寫Main方法
package demo;
public class MainThreadDemo {
public static void main(String[] args) {
Person per=new Person("常威");
per.run();
Person Per2=new Person("來福");
Per2.run();
}
}
運行結果就已經很顯而易見了,放心我不是靠你們運行結果而是單純的先分析主執行緒,
運行結果:
常威1
常威2
常威3
常威4
來福1
來福2
來福3
來福4
3.1、分析主執行緒原理

3.2、 單執行緒的局限性
單執行緒不僅效率低下,而且存在很大的局限性,惟一的優點就是安全,所以說女孩子長得安全其實也是一種優點,噗哈哈哈...
如何體現出單執行緒效率低下以及它的局限性呢?其實只要一句代碼即可,還是以上面的單執行緒Main執行緒為例:
package demo;
public class MainThreadDemo {
public static void main(String[] args) {
Person per=new Person("常威");
per.run();
int a=6/0; //=====================特別注意這行代碼
Person Per2=new Person("來福");
Per2.run();
}
}
試想一下運行結果...
如果對上面的運行結果有問題,或者疑問,那沒錯了,你簡直是個天(小)才(白)!真真的天(小)才(白),很有可能例外機制沒學好,好吧我給你貼出來:【java基礎之例外】死了都要try,不淋漓盡致地catch我不痛快!
言歸正傳,效率低下何以見得?這是資料少,如果是一億條資料呢,單執行緒就是一個一個列印,那局限性又何以見得呢?從上面運行結果來看也能看出,只因為一行代碼而導致下面代碼不再執行,已經很明顯了,
4、 創建多執行緒的四種方式
說是說創建多執行緒有四種方式,但考慮到是入門文章還是主要寫入門的兩種方式,剩下的兩個暫時忽略,忽略的兩種方法有:實作Callable介面通過FutureTask包裝器來創建Thread執行緒、使用ExecutorService、Callable、Future實作有回傳結果的執行緒,現在可能對于入門的童鞋來說是接收不了的,以后再去了解也不晚!
4.1、繼承Thread類
Java使用java.lang.Thread類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的實體,每個執行緒的作用是完成一定的任務,實際上就是執行一段程式流即一段順序執行的代碼,Java使用執行緒執行體來代表這段程式流,
Java中通過繼承Thread類來創建并啟動多執行緒的步驟如下:
- 定義
Thread類的子類,并重寫該類的run()方法,該run()方法的方法體就代表了執行緒需要完成的任務,因此把run()方法稱為執行緒執行體, - 創建
Thread子類的實體,即創建了執行緒物件 - 呼叫執行緒物件的
start()方法來啟動該執行緒
代碼如下:
測驗類:
public class Demo01 {
public static void main(String[] args) {
//創建自定義執行緒物件
MyThread mt = new MyThread("新的執行緒!");
//開啟新執行緒
mt.start();
//在主方法中執行for回圈
for (int i = 0; i < 10; i++) {
System.out.println("main執行緒!"+i);
}
}
}
自定義執行緒類:
public class MyThread extends Thread {
//定義指定執行緒名稱的構造方法
public MyThread(String name) {
//呼叫父類的String引數的構造方法,指定執行緒的名稱
super(name);
}
/**
* 重寫run方法,完成該執行緒執行的邏輯
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在執行!"+i);
}
}
}
Thread類本質上是實作了Runnable介面的一個實體,代表一個執行緒的實體,啟動執行緒的唯一方法就是通過Thread類的start()實體方法,start()方法是一個native方法,它將啟動一個新執行緒,并執行run()方法,這種方式實作多執行緒很簡單,通過自己的類直接extend Thread,并復寫run()方法,就可以啟動新執行緒并執行自己定義的run()方法,
4.2、實作Runnable介面
如果自己的類已經繼承另一個類,就無法直接繼承Thread,此時,可以實作一個Runnable介面來創建執行緒,顯然實作Runnable介面方式創建執行緒的優勢就很明顯了,
直接擼碼:
自定義一個類實作Runnable介面,并重寫介面中的run()方法,并為run方法添加要執行的代碼方法,
public class RunableDemo implements Runnable{
@Override
public void run() {
int a = 1;
while (a<20){
System.out.println(Thread.currentThread().getName()+ a);//Thread.currentThread().getName()為獲取當前執行緒的名字
a++;
}
}
}
撰寫Main方法
為了啟動自定義類RunableDemo ,需要首先實體化一個Thread,并傳入RunableDemo 實體:
public class MainThreadDemo {
public static void main(String[] args) {
RunableDemo runn=new RunableDemo();
//實體化一個Thread并傳入自己的RunableDemo 實體
Thread thread=new Thread(runn);
thread.start();
int a = 1;
while (a<20){
//Thread.currentThread().getName()為獲取當前執行緒的名字
System.out.println(Thread.currentThread().getName()+ a);
a++;
}
}
}
運行結果:
main1
main2
main3
Thread-01
Thread-02
Thread-03
Thread-04
Thread-05
Thread-06
....
其實多運行幾遍,你會方法每次運行的結果順序都不一樣,這主要是由于多執行緒會去搶占CPU的資源,誰搶到了誰就執行,而Main和Thread兩個執行緒一直在爭搶,
實際上,當傳入一個Runnable target(目標)引數給Thread后,Thread的run()方法就會呼叫target.run(),參考JDK源代碼:
public void run() {
if (target != null) {
target.run();
}
}
4.3、兩種入門級創建執行緒的區別
采用繼承Thread類方式:
(1)優點:撰寫簡單,如果需要訪問當前執行緒,無需使用Thread.currentThread()方法,直接使用this,即可獲得當前執行緒,
(2)缺點:因為執行緒類已經繼承了Thread類,所以不能再繼承其他的父類,
采用實作Runnable介面方式:
(1)優點:執行緒類只是實作了Runable介面,還可以繼承其他的類,在這種方式下,可以多個執行緒共享同一個目標物件,所以非常適合多個相
同執行緒來處理同一份資源的情況,從而可以將CPU代碼和資料分開,形成清晰的模型,較好地體現了面向物件的思想,
(2)缺點:編程稍微復雜,如果需要訪問當前執行緒,必須使用Thread.currentThread()方法,
小結:
如果一個類繼承Thread,則不適合資源共享,但是如果實作了Runable介面的話,則很容易的實作資源共享,
實作Runnable介面比繼承Thread類的優勢:
1.適合多個相同代碼的執行緒去處理同一個資源,
2.可以避免java中單繼承的限制,
3.增加代碼的健壯性,實作解耦,代碼可以被多個執行緒共享,代碼和資料獨立,
4.執行緒池中只能放入實作Runnable或Callable類執行緒,不能放入繼承Thread的類【執行緒池概念之后會慢慢涉及】
所以,如果選擇哪種方式,盡量選擇實作Runnable介面!
其實學到后面的執行緒池,你會發現上面兩種創建執行緒的方法實際上很少使用,一般都是用執行緒池的方式比較多一點,使用執行緒池的方式也是最推薦的一種方式,另外,《阿里巴巴Java開發手冊》在第一章第六節并發處理這一部分也強調到“執行緒資源必須通過執行緒池提供,不允許在應用中自行顯示創建執行緒”,不過處于入門階段的童鞋博主還是強烈建議一步一個腳印比較好!
5、使用匿名內部類方式創建執行緒
談起匿名內部類,可能很多小白是比較陌生的,畢竟開發中使用的還是比較少,但是同樣是非常重要的一個知識!于此同時我就貼出關于匿名內部類的文章程式員你真的理解匿名內部類嗎?如果小白童鞋能看懂下面這個代碼,真的你不需要看那篇文章了,你T喵的簡直是個天才!
package AnonymousInner;
public class NiMingInnerClassThread {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i<5;i++){
System.out.println("熊孩子:"+i);
}
}
};
new Thread(r).start();
for (int i = 0; i < 5 ; i++){
System.out.println("傻狍子:"+i);
}
}
}
小白童鞋還愣著干啥呀趕緊去補補...
6、執行緒安全問題
執行緒安全問題主要是共享資源競爭的問題,也就是在多個執行緒情況下,一個或多個執行緒同時搶占同一資源導致出現的一些不必要的問題,最典型的例子就是火車四個視窗售票問題了,這里就不再舉售票例子了,已經爛大街了,這里就簡單實作一個執行緒安全問題代碼....
實作Runnable介面方式為例,主要實作程序是:實體化三個Thread,并傳入同一個RunableDemo 實體作為引數,最后開啟三條相同引數的執行緒,代碼如下:
public class RunableDemo implements Runnable{
public int a = 100;//執行緒共享資料
@Override
public void run() {
while (a>0){
System.out.println("執行緒"+Thread.currentThread().getName()+"執行到"+ a);
a--;
}
}
}
public class MainThreadDemo {
public static void main(String[] args) {
RunableDemo runn=new RunableDemo();
Thread thread1=new Thread(runn);
Thread thread2=new Thread(runn);
Thread thread3=new Thread(runn);
thread1.start();
thread2.start();
thread3.start();
}
}
運行結果:
Thread-0==100
Thread-0==99
Thread-1==100
Thread-1==97
Thread-1==96
Thread-1==95
Thread-2==98
...
根據結果可以看出,確實是三條執行緒(Thread-0、1、2)在執行,安全問題就出在執行緒會出現相同的結果比如上面的100就出現了兩次,如果回圈條件更改一下可能也會出現負數的情況,這種情況該怎么解決呢?這個時候就需要執行緒同步了!
7、解決執行緒安全問題:執行緒同步
實際上,執行緒安全問題的解決方法有三種:
1、同步代碼塊
2、同步方法
3、鎖機制
7.1、 synchronized同步代碼塊
第一種方法:同步代碼塊
格式:
synchronized(鎖物件) {
可能會出現執行緒安全問題的代碼(訪問共享資料的代碼)
}
使用同步代碼塊特別注意:
1、通過代碼塊的鎖物件,可以是任意物件
2、必須保證多個執行緒使用的鎖物件必須是同一個
3、鎖物件的作用是把同步代碼快鎖住,只允許一個執行緒在同步代碼塊執行
還是以上面執行緒安全問題為例子,使用同步代碼塊舉例:
public class RunableDemo implements Runnable{
public int a = 100;//執行緒共享資料
Object object=new Object(); //事先準備好一個鎖物件
@Override
public void run() {
synchronized (object){ //使用同步代碼塊
while (a>0){
System.out.println("執行緒"+Thread.currentThread().getName()+"執行到"+ a);
a--;
}
}
}
}
Main方法沒有任何改動,運行一下結果是絕對沒問題的,資料都是正確的沒有出現重復情況這一出,各位可以自己嘗試一下!
同步代碼塊的原理:
使用了一個鎖物件,叫同步鎖,物件鎖,也叫同步監視器,當開啟多個執行緒的時候,多個執行緒就開始搶奪CPU的執行權,比如現在t0執行緒首先的到執行,就會開始執行run方法,遇到同步代碼快,首先檢查是否有鎖物件,發現有,則獲取該鎖物件,執行同步代碼塊中的代碼,之后當CUP切換執行緒時,比如t1得到執行,也開始執行run方法,但是遇到同步代碼塊檢查是否有鎖物件時發現沒有鎖物件,t1便被阻塞,等待t0執行完畢同步代碼塊,釋放鎖物件,t1才可以獲取從而進入同步代碼塊執行,
同步中的執行緒,沒有執行完畢是不會釋放鎖的,這樣便實作了執行緒對臨界區的互斥訪問,保證了共享資料安全,
缺點:頻繁的獲取釋放鎖物件,降低程式效率
7.2、同步方法
使用步驟:
1、把訪問了共享資料的代碼抽取出來,放到一個方法中
2、在該方法上添加synchronized修飾符
格式:
修飾符 synchronized 回傳值型別 方法名稱(引數串列) {
方法體...
}
代碼示例:
public class RunableDemo implements Runnable{
public int a = 100;//執行緒共享資料
@Override
public void run() {
while (true){
sell(); //呼叫下面的sell方法
}
}
//訪問了共享資料的代碼抽取出來,放到一個方法sell中
public synchronized void sell(){
while (a>0){
System.out.println("執行緒"+Thread.currentThread().getName()+"執行到"+ a);
a--;
}
}
}
同步方法的也是一樣鎖住同步的代碼,但是鎖物件的是Runable實作類物件,也就是this,誰呼叫方法,就是誰,
說到同步方法,就不得不說一下靜態同步方法,顧名思義,就是在同步方法上加上static,靜態的同步方法,添加一個靜態static修飾符,此時鎖物件就不是this了,靜態同步方法的鎖物件是本類的class屬性,class檔案物件(反射)
public class RunableDemo implements Runnable{
public static int a = 100;//執行緒共享資料 =====此時共享資料也要加上static
@Override
public void run() {
while (true){
sell();
}
}
public static synchronized void sell(){ //注意添加了static關鍵字
while (a>0){
System.out.println("執行緒"+Thread.currentThread().getName()+"執行到"+ a);
a--;
}
}
}
使用靜態同步方法時,此時共享資料也要加上static,因為static成員才能訪問static成員,如果對static關鍵字不是他別理解的可以補補了,放心,博主有信心讓你有所識訓,會讓你重新認識到static的魅力:深入理解static關鍵字
當然靜態同步方法了解即可!
7.3、Lock鎖
Lock介面位于java.util.concurrent.locks.Lock它是JDK1.5之后出現的,Lock介面中的方法:
void lock(): 獲取鎖
void unlock(): 釋放鎖
Lock介面的一個實作類java.util.concurrent.locks.ReentrantLock implements Lock介面
使用方法:
1、在Runable實作類的成員變量創建一個ReentrantLock物件
2、在可能產生執行緒安全問題的代碼前該物件呼叫lock方法獲取鎖
3、在可能產生執行緒安全問題的代碼后該物件呼叫unlock方法釋放鎖
代碼示例:
import java.util.concurrent.locks.ReentrantLock;
public class RunableDemo implements Runnable{
public static int a = 100;//執行緒共享資料
//1、在Runable實作類的成員變數創建一個ReentrantLock物件============
ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
// 2、在可能產生執行緒安全問題的代碼前該物件呼叫lock方法獲取鎖=======
reentrantLock.lock();
while (a>0){
System.out.println("執行緒"+Thread.currentThread().getName()+"執行到"+ a);
a--;
}
// 3、在可能產生執行緒安全問題的代碼后該物件呼叫unlock方法獲取鎖======
reentrantLock.unlock();
}
}
當然更安全的寫法是,在執行緒安全問題代碼中try...catchy,最后在finally陳述句中添加reentrantLock.unlock();,這樣方為上上策!
7.4、三種方法小結
第一種
synchronized 同步代碼塊:可以是任意的物件必須保證多個執行緒使用的鎖物件是同一個
第二種
synchronized 同步方法: 鎖物件是this,誰呼叫鎖物件就是誰
synchronized 靜態同步方法: 鎖物件是其class物件,該物件可以用
this.getClass()方法獲取,也可以使用當前類名.class表示,【了解即可】
第三種
Look鎖方法:該方法提供的方法遠遠多于synchronized方式,主要在Runable實作類的成員變數創建一個ReentrantLock物件,并使用該物件呼叫lock方法獲取鎖以及unlock方法釋放鎖!
8、執行緒常用方法
8.1、Thread類
Thread():用于構造一個新的Thread,
Thread(Runnable target):用于構造一個新的Thread,該執行緒使用了指定target的run方法,
Thread(ThreadGroup group,Runnable target):用于在指定的執行緒組中構造一個新的Thread,該
執行緒使用了指定target的run方法,
currentThread():獲得當前運行執行緒的物件參考,
interrupt():將當前執行緒置為中斷狀態,
sleep(long millis):使當前運行的執行緒進入睡眠狀態,睡眠時間至少為指定毫秒數,
join():等待這個執行緒結束,即在一個執行緒中呼叫other.join(),將等待other執行緒結束后才繼續本執行緒,
yield():當前執行的執行緒讓出CPU的使用權,從運行狀態進入就緒狀態,讓其他就緒執行緒執行,
8.2、Object類
wait():讓當前執行緒進入等待阻塞狀態,直到其他執行緒呼叫了此物件的notify()或notifyAll()方法后,當前執行緒才被喚醒進入就緒狀態,
notify():喚醒在此物件監控器(鎖物件)上等待的單個執行緒,
notifyAll():喚醒在此物件監控器(鎖物件)上等待的所有執行緒,
注意:
wait()、notify()、notifyAll()都依賴于同步鎖,而同步鎖是物件持有的,且每個物件只有一個,所以這些方法定義在Object類中,而不是Thread類中,
8.3、yield()、sleep()、wait()比較
wait():讓執行緒從運行狀態進入等待阻塞狀態,并且會釋放它所持有的同步鎖,
yield():讓執行緒從運行狀態進入就緒狀態,不會釋放它鎖持有的同步鎖,
sleep():讓執行緒從運行狀態進入阻塞狀態,不會釋放它鎖持有的同步鎖,
9、執行緒的狀態

以上只是簡單的一個執行緒狀態圖,其實執行緒狀態博大精深,要講清楚還是要一大篇文筆,作為入門文章先了解一下吧,之后的并發編程文章將再講述吧!
如果想要去深入了解一下的話也是可以的:Java執行緒的6種狀態及切換
10、執行緒池
在java中只要說到池,基本都是一個套路,啥資料庫連接池、jdbc連接池等,思想基本上就是:一個容納多個要使用資源的容器,其中的資源可以反復使用,省去了頻繁創建執行緒物件的操作,無需反復創建資源而消耗過多資源,
10.1、執行緒池概述
執行緒池其實就是一個容納多個執行緒的容器,其中的執行緒可以反復使用,省去了頻繁創建執行緒物件的操作,無需反復創建執行緒而消耗過多資源,
合理利用執行緒池能夠帶來三個好處:
- 降低資源消耗,減少了創建和銷毀執行緒的次數,每個作業執行緒都可以被重復利用,可執行多個任務,
- 提高回應速度,當任務到達時,任務可以不需要的等到執行緒創建就能立即執行,
- 提高執行緒的可管理性,可以根據系統的承受能力,調整執行緒池中作業線執行緒的數目,防止因為消耗過多的記憶體,而把服務器累趴下(每個執行緒需要大約1MB記憶體,執行緒開的越多,消耗的記憶體也就越大,最后死機),
10.2、 執行緒池的使用
Java里面執行緒池的最頂級介面是java.util.concurrent.Executor,但是嚴格意義上講Executor并不是一個執行緒池,而只是一個執行執行緒的工具,真正的執行緒池介面是java.util.concurrent.ExecutorService,
要配置一個執行緒池是比較復雜的,尤其是對于執行緒池的原理不是很清楚的情況下,很有可能配置的執行緒池不是較優的,因此在java.util.concurrent.Executors執行緒工廠類里面提供了一些靜態工廠,生成一些常用的執行緒池,官方建議使用Executors工程類來創建執行緒池物件,
Executors類中有個創建執行緒池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads):回傳執行緒池物件,(創建的是有界執行緒池,也就是池中的執行緒個數可以指定最大數量)
獲取到了一個執行緒池ExecutorService 物件,那么怎么使用呢,在這里定義了一個使用執行緒池物件的方法如下:
public Future<?> submit(Runnable task):獲取執行緒池中的某一個執行緒物件,并執行
Future介面:用來記錄執行緒任務執行完畢后產生的結果,執行緒池創建與使用,
使用執行緒池中執行緒物件的步驟:
- 創建執行緒池物件,
- 創建Runnable介面子類物件,(task)
- 提交Runnable介面子類物件,(take task)
- 關閉執行緒池(一般不操作這一步),
Runnable實作類代碼:
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一個游泳教練");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教練來了: " + Thread.currentThread().getName());
System.out.println("教我游泳,教會后,教練又回到了游泳池");
}
}
執行緒池測驗類:
public class ThreadPoolDemo {
public static void main(String[] args) {
// 創建執行緒池物件
ExecutorService service = Executors.newFixedThreadPool(2);//包含2個執行緒物件
// 創建Runnable實體物件
MyRunnable r = new MyRunnable();
//自己創建執行緒物件的方式
// Thread t = new Thread(r);
// t.start(); ---> 呼叫MyRunnable中的run()
// 從執行緒池中獲取執行緒物件,然后呼叫MyRunnable中的run()
service.submit(r);
// 再獲取個執行緒物件,呼叫MyRunnable中的run()
service.submit(r);
service.submit(r);
// 注意:submit方法呼叫結束后,程式并不終止,是因為執行緒池控制了執行緒的關閉,
// 將使用完的執行緒又歸還到了執行緒池中
// 關閉執行緒池
//service.shutdown();
}
}
以上只是簡單的使用執行緒池,僅僅是入門階段!道阻且長,路還很長....
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/106062.html
標籤:其他
