【Java并發(fā)學習】之線程的創(chuàng)建
前言
Java并發(fā)一直是學習中的一個難點,之前雖然也有接觸一部分的內容,不過由于當時對于并發(fā)的認識不足,所以學習的效果不是很好,乘著最近有時間,重新拾起這一部分內容,重新學習一下,并且將學習過程的筆記整理出來,本小節(jié)主要學習的內容是線程的創(chuàng)建
線程的相關概念
談到并發(fā),不可避免的會涉及到進程、線程相關的概念問題,關于這部分,有比較詳細的定義,這里只是做個簡單的總結與梳理
進程
進程簡單的可以理解為是程序的一次執(zhí)行,是動態(tài)的一個概念,進程是現(xiàn)在計算機體系結構中資源分配的最小單位,這里需要注意的是,在沒有使用線程之前,進程還是系統(tǒng)進行調度的最小單位,不過由于對進程進行調度時所需要的時間以及空間代價比較大,所以現(xiàn)在進程一般只作為資源分配的最小單位,而不是調度的單位
線程
上面提到了,線程現(xiàn)在普通作為調度的最小單位,一個進程中至少包含一個線程,并且可以創(chuàng)建多個線程,這些線程之間共享進程所擁有的資源,并且可以執(zhí)行不同的任務
Java中多線程的創(chuàng)建
上面我們簡單了解了進程與線程的概念之后,接下來我們來學習Java中線程的創(chuàng)建與使用
在具體使用之前,我們需要明確兩個概念
任務,任務是指具線程所要執(zhí)行的具體內容,也就是所要執(zhí)行的代碼,需要明確的是,任務跟線程是沒有太明確的關系的,任務是描述,而線程則是具體執(zhí)行的工具
線程,線程只是負責執(zhí)行委托給它的任務,不清楚具體的內容是什么,因為具體的內容是定義在任務之中
明確了這兩個概念之后,接著來看下Java中所提供的實現(xiàn)多線程的工具
任務的描述
任務描述的方式總體上來看有兩種,一種是沒有具體返回值的任務,另一個一種是可以有返回值的任務,分別對應的接口是Runnable、Callable<T>
接下來我們來看下具體描述任務的方式
/**
* 計算任務描述
* 實現(xiàn)Runnable接口并且重寫run方法
*/
class CalcTask1 implements Runnable{
/**
* 具體的任務描述
* 稍后具體的任務會有對應的線程來執(zhí)行
*/
@Override
public void run() {
int sum = 0;
for (int i = 0; i < 100000; i++){
sum +=i ;
}
System.out.println(sum);
}
}
/**
* 計算任務描述
* 實現(xiàn)Callable接口并重寫call方法
*/
class CalcTask2 implements Callable<Integer>{
/**
* 具體的任務描述
* 稍后具體的任務會有對應的線程來執(zhí)行
*/
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100000; i++){
sum +=i ;
}
return sum;
}
}
需要注意的是,Callable接口是泛型設計,需要為其指定對應的返回類型
除了上面的這兩種描述方式外,其實還有另外一種,那就是直接繼承Thread類,并且重寫對應的run方法,這種方式也是可以用于描述對應的任務,不過會導致具體任務與對應的線程綁定在一起
/**
* 通過繼承Thread并且重寫run方式來描述任務
*/
class CalcTask3 extends Thread{
@Override
public void run() {
int sum = 0;
for (int i = 0; i < 100000; i++){
sum +=i ;
}
System.out.println(sum);
}
}
使用多線程的方式
上面我們提到了,在Java中,在使用多線程的時候,一般是把操作邏輯與具體的執(zhí)行者分開,這種實現(xiàn)方式看上去比較麻煩,但是實際上將實現(xiàn)邏輯與具體執(zhí)行者分離是一種非常好的設計,由于線程的創(chuàng)建以及回收時非常耗費資源的,所以為了避免頻繁的創(chuàng)建與回收線程,我們可以采用線程池的方式來管理線程,而如果是將實現(xiàn)邏輯與具體的執(zhí)行者綁定,就無法充分利用這種優(yōu)勢了
從本質上來講,實現(xiàn)多線程的方式只有一種,那就是創(chuàng)建對應的線程,并且將具體的操作邏輯交給線程,并且調用對應線程的start方法,如下所示
// 創(chuàng)建對應的任務
Runnable calcTask = new CalcTask1();
// 將任務提交給線程
// 這里需要注意的是,如果是直接繼承自Thread,則直接調用對應的
// 線程對象的run方法即可
Thread executor = new Thread(calcTask);
// 啟動線程
executor.start();
上面這種實現(xiàn)方式是比較原始的方式,通過這種方式來啟動多線程的缺點是,在需要使用線程的時候,必須自己手動創(chuàng)建,也就是說,無法充分利用線程池的優(yōu)勢,而且,還必須自己管理線程的生命周期,于是,在JDK5的時候,Java引入了一個通用的線程執(zhí)行框架Executor(關于Executor將在后面學習到),從而簡化了這一系列的操作過程
// 獲取一個ExecutorService實例
ExecutorService service = Executors.newCachedThreadPool();
// 創(chuàng)建對應的任務
Runnable calcTask = new CalcTask1();
// 將任務提交給ExecutorService
service.submit(calcTask);
// 關閉對應的ExecutorService
service.shutdown();
總結
本小節(jié)主要學習了進程、線程的相關概念,以及在Java中描述任務的方式,實現(xiàn) Runnable、Callable<T>以及繼承Thread,并且學習學習了啟動線程來驅動對應任務的方式,直接將任務交給Thread對象,或者將其交給Executor框架