一、線程與進程
操作系統中可以支持多個程序同時運行,每個程序就代表了一個進程,各個程序即進程間相互獨立工作,而每個程序中可能包含多個順序執行流,每個執行流就代表了一個線程,各個線程共享進程中的資源。
1.什麼是進程
進程是系統進行資源調度與分配的最小單位,一般而言,進程具有三個特點:
- 獨立性:每個進程都有自己獨立的私有資源,不與其他進程共享,也就是說,其他進程不可以直接訪問其他進程的地址空間。
- 動態性:進程具有自己的生命週期和狀態轉變。
- 併發性:併發是指在同一時刻只能有一條指令執行,但多個進程可以被快速輪換執行,由於處理速度非常快,感覺上多個進程在同時進行。
2.什麼是線程
線程也被成爲輕量級進程,當進程中需要併發執行多個任務時,就需要使用線程的概念,線程是進程的執行單元,它們可以共享進程中的資源。
3.進程與線程的區別
- 進程是資源的分配和調度的一個獨立單元,而線程是CPU調度的基本單元。
- 同一個進程中可以包含多個線程,一個進程中至少包含一個主線程。
- 同一進程中的多個線程共享代碼段(代碼和常量),數據段(全局變量和靜態變量),擴展段(堆存儲)。但是每個線程擁有自己的棧段,棧段又叫運行時段,用來存放所有局部變量和臨時變量。
- 同一個進程中的線程可以併發執行。
- 線程間的通信比進程間通信要方便的多,但是線程之間需要處理好同步與互斥的關係。
二、線程的創建與啓動
Java使用Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實例,Java中創建多線程有三種方法:
- 繼承Thread類重寫run方法
- 實現Runnable接口重寫run方法
- 使用Callable和Future創建線程
1.繼承Thread類創建線程類
(1)定義Thread類的子類,並重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務。
(2)創建Thread子類的實例,即創建了線程對象。
(3)調用線程對象的start()方法來啓動該線程。
代碼如下所示:
public class ThreadTest extends Thread{
int i = 0;
@Override
public void run(){
for(;i<50;i++){
System.out.println(getName() + " " +i);
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadTest thread1 = new ThreadTest();
ThreadTest thread2 = new ThreadTest();
for(int i=0; i<50;i++){
System.out.println(Thread.currentThread().getName()+" : "+i);
if(i==10)
{
thread1.start();
thread2.start();
}
}
}
}
運行結果可能如下所示:
main : 15
Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 3
Thread-1 4
Thread-1 5
main : 16
Thread-1 6
main : 17
Thread-0 0
Thread-1 7
Thread-0 1
main : 18
Thread-0 2
Thread-1 8
Thread-0 3
main : 19
main : 20
Thread-0 4
由測試結果可知,main進程執行至i=10時,開始新建兩個新線程,兩個新線程從i=0開始分別執行,Thread-0和Thread-1中的i值是不連續的,證明兩個線程中的i值不共享,因爲兩個新線程是ThreadTest創建的兩個實例,Thread-0和Thread-1不能共享實例變量。
2.實現Runnable接口創建線程類
(1)定義runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
(2)創建 Runnable實現類的實例,並依此實例作爲Thread的target來創建Thread對象,該Thread對象纔是真正的線程對象。
(3)調用線程對象的start()方法來啓動該線程。
代碼如下所示:
public class RunnableTest implements Runnable{
int i = 0;
@Override
public void run() {
// TODO Auto-generated method stub
for(;i<50;i++){
System.out.println(Thread.currentThread().getName() + " " +i);
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
RunnableTest runnable = new RunnableTest();
for(int i = 0;i < 100;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20)
{
new Thread(runnable,"線程1").start();
new Thread(runnable,"線程2").start();
}
}
}
}
運行結果可能如下所示:
main 53
main 54
main 55
main 56
線程1 0
線程2 0
main 57
線程1 1
main 58
線程2 2
main 59
線程1 3
main 60
線程2 4
main 61
由測試結果可知,兩個子線程的i值是連續變化的,說明使用Runnable接口的方式創建的多個線程可以共享線程類的實例變量。
3.使用Callable和Future創建線程
(1)創建Callable接口的實現類,並實現call()方法,該call()方法將作爲線程執行體,並且有返回值,call()方法還可以拋出異常。
(2)創建Callable實現類的實例,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
(3)使用FutureTask對象作爲Thread對象的target創建並啓動新線程。
(4)調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值
實現代碼如下所示:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 創建Callable接口的實現類,並實現call()方法,該call()方法將作爲線程執行體,並且有返回值
* @author zhx
*
*/
class CallableDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 計算1-100的和
int sum = 0;
for (int i = 1; i <= 100; i++)
sum += i;
return sum;
}
}
public class CallableTest {
public static void main(String[] args) {
CallableDemo cd = new CallableDemo();
//創建Callable實現類的實例,使用FutureTask類來包裝Callable對象
//該FutureTask對象封裝了該Callable對象的call()方法的返回值
FutureTask<Integer> result = new FutureTask<>(cd);
//使用FutureTask對象作爲Thread對象的target創建並啓動新線程。
new Thread(result).start();
int sum = 0;
// 接收運算結果
// 只有當該線程執行完畢後纔會獲取到運算結果,等同於閉鎖的效果
try {
//調用FutureTask對象的get()方法來獲得子線程執行結束後的返回值
sum = result.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("sum is " + sum);
}
}
三、三種創建方式的對比
1.採用實現Runnable、Callable接口的方式創見多線程
優勢:
線程類只是實現了Runnable接口或Callable接口,還可以繼承其他類。
在這種方式下,多個線程可以共享同一個target對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU、代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。
劣勢:
編程稍微複雜,如果要訪問當前線程,則必須使用Thread.currentThread()方法。
2.使用繼承Thread類的方式創建多線程
優勢:
編寫簡單,如果需要訪問當前線程,則無需使用Thread.currentThread()方法,直接使用this即可獲得當前線程。
劣勢:
線程類已經繼承了Thread類,所以不能再繼承其他父類。