Java線程概念與原理
-
- 作業系統中線程和程序的概念
- Java線程的實作形式
-
- 繼承Thread類
- 實作Runnable接口
- 使用Callable和Future接口建立線程。
作業系統中線程和程序的概念
現在的作業系統是多任務作業系統。多線程是實作多任務的一種方式。
程序是指一個記憶體中運作的應用程式,每個程序都有自己獨立的一塊記憶體空間,一個程序中可以啟動多個線程。比如在Windows系統中,一個運作的exe就是一個程序。
線程是指程序中的一個執行流程,一個程序中可以運作多個線程。比如 java.exe 程序中可以運作很多線程。
線程總是屬于某個程序,程序中的多個線程共享程序的記憶體
。
在java中要想實作多線程,有兩種手段,
一種是繼承Thread類
,另外一種是
實作Runable接口
.(其實準确來講,應該有三種,還有一種是實作Callable接口,并與Future、線程池結合使用,此文這裡不講這個)
Java線程的實作形式
這裡繼承Thread類的方法是比較常用的一種,如果說你隻是想起一條線程。沒有什麼其它特殊的要求,那麼可以使用Thread.
繼承Thread類
擴充Thread類實作的多線程例子:
/**
* 測試擴充Thread類實作的多線程程式
*/
public class TestThread extends Thread {
public TestThread(String name){
super(name);
}
@Override
public void run() {
for(int i=0;i<5;i++){
for(long k=0;k<100000000;k++);
System.out.println(this.getName()+":"+i);
}
}
public static void main(String[] args){
Thread t1=new TestThread("李白");
Thread t2=new TestThread("屈原");
t1.start();
t2.start();
}
}
執行結果:
屈原:0
李白:0
屈原:1
李白:1
屈原:2
李白:2
李白:3
屈原:3
李白:4
屈原:4
實作Runnable接口
public class RunnableImpl implements Runnable{
private String name;
public RunnableImpl(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
for(long k=0;k<100000000;k++);
System.out.println(name+":"+i);
}
}
}
/**
* 測試Runnable類實作的多線程程式
*/
public class TestRunnable {
public static void main(String[] args) {
RunnableImpl ri1=new RunnableImpl("李白");
RunnableImpl ri2=new RunnableImpl("屈原");
Thread t1=new Thread(ri1);
Thread t2=new Thread(ri2);
t1.start();
t2.start();
}
}
輸出:
屈原:0
李白:0
屈原:1
李白:1
屈原:2
李白:2
屈原:3
李白:3
屈原:4
李白:4
說明: Thread2類通過實作Runnable接口,使得該類有了多線程類的特征。
run()方法是多線程程式的一個約定。所有的多線程代碼都在run方法裡面。
Thread類實際上也是實作了Runnable接口的類,在啟動的多線程的時候,需要
先通過Thread類的構造方法Thread(Runnable target) 構造出對象,然後調用Thread對象的start()方法來運作多線程代碼
。 實際上所有的多線程代碼都是通過運作Thread的
start()方法
來運作的。是以,不管是擴充Thread類還是實作Runnable接口來實作多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程程式設計的基礎。
使用Callable和Future接口建立線程。
具體是建立Callable接口的實作類,并實作clall()方法。并使用FutureTask類來包裝Callable實作類的對象,且以此FutureTask對象作為Thread對象的target來建立線程。
public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 建立MyCallable對象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask來包裝MyCallable對象
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread thread = new Thread(ft); //FutureTask對象作為Thread對象的target建立新的線程
thread.start(); //線程進入到就緒狀态
}
}
System.out.println("主線程for循環執行完畢..");
try {
int sum = ft.get(); //取得新建立的新線程中的call()方法傳回的結果
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = 0;
// 與run()方法不同的是,call()方法具有傳回值
@Override
public Integer call() {
int sum = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}
輸出:
main 0
main 1
main 2
…
main 99
主線程for循環執行完畢…
Thread-0 0
Thread-0 1
…
Thread-0 98
Thread-0 99
sum = 4950
首先,我們發現,在實作Callable接口中,此時不再是
run()方法
了,而是
call()方法
,此call()方法作為線程執行體,同時還具有傳回值!
在建立新的線程時,是通過
FutureTask來包裝MyCallable對象
,同時作為了Thread對象的target。那麼看下FutureTask類的定義:
public class FutureTask<V> implements RunnableFuture<V> {
//....
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
于是,我們發現 FutureTask 類實際上是同時實作了 Runnable 和 Future 接口,由此才使得其具有 Future 和 Runnable 雙重特性。通過Runnable特性,可以作為Thread對象的target,而Future特性,使得其可以取得新建立線程中的call()方法的傳回值。
執行下此程式,我們發現sum = 4950永遠都是最後輸出的。而
“主線程for循環執行完畢..”
則很可能是在子線程循環中間輸出。由CPU的線程排程機制,我們知道,“主線程for循環執行完畢…”的輸出時機是沒有任何問題的,那麼為什麼sum =4950會永遠最後輸出呢?
原因在于
**通過ft.get()方法擷取子線程call()方法的傳回值時,當子線程此方法還未執行完畢,ft.get()方法會一直阻塞,直到call()方法執行完畢才能取到傳回值。**
main方法其實也是一個線程。在java中是以的線程都是同時啟動的,至于什麼時候,哪個先執行,完全看誰先得到CPU的資源。
轉載自–《面試題:Java線程基礎》【https://mp.weixin.qq.com/s/-4wo4Q6Dkr_d3mk2wFy16g】