1. 操作系統(tǒng)中線程和進程的概念
- 現(xiàn)在的操作系統(tǒng)是多任務操作系統(tǒng)。多線程是實現(xiàn)多任務的一種方式。
- 進程是指一個內(nèi)存中運行的應用程序,每個進程都有自己獨立的一塊內(nèi)存空間,一個進程中可以啟動多個線程。比如在windows系統(tǒng)中,一個運行的exe就是一個進程。
- 線程是指進程中的一個執(zhí)行流程,一個進程中可以運行多個線程。比如java.exe進程中可以運行很多線程。線程總是屬于某個進程,進程中的多個線程共享進程的內(nèi)存。
- 在java中要想實現(xiàn)多線程,有兩種手段,一種是繼承Thread類,另一種是實現(xiàn)Runable接口(其實準確來講,應該有三中的,還有一種是實現(xiàn)Callable接口,并與Future、線程池結合使用)
2. java線程的實現(xiàn)形式
這里繼承Thread類的方式是比較常用的一種,如果說你只是想起一條線程。沒有什么其他特殊的要求,那么可以使用Thread
3.繼承Thread類
/**
* 測試擴展Thread類實現(xiàn)的多線程程序
*/
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();
}
}
執(zhí)行結果:
張飛:0
李白:0
張飛:1
李白:1
張飛:2
李白:2
張飛:3
張飛:4
李白:3
李白:4
4.實現(xiàn)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類實現(xiàn)的多線程程序
*/
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();
}
}
執(zhí)行結果:
李白:0
張飛:0
張飛:1
李白:1
張飛:2
李白:2
張飛:3
李白:3
張飛:4
李白:4
說明: Thread2類通過實現(xiàn)Runnable接口,使得該類有了多線程類的特征。run()方法是多線程程序的一個約定。所有的多線程代碼都在run方法里面。Thread類實際上也是實現(xiàn)了Runnable接口的類。 在啟動的多線程的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出對象,然后調(diào)用Thread對象的start()方法來運行多線程代碼。 實際上所有的多線程代碼都是通過運行Thread的start()方法來運行的。因此,不管是擴展Thread類還是實現(xiàn)Runnable接口來實現(xiàn)多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程編程的基礎。
5.使用Callable和Future接口創(chuàng)建線程
具體是創(chuàng)建Callable接口的實現(xiàn)類,并實現(xiàn)clall()方法。并使用FutureTask類來包裝Callable實現(xiàn)類的對象,且以此FutureTask對象作為Thread對象的target來創(chuàng)建線程。
public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 創(chuàng)建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創(chuàng)建新的線程
thread.start(); //線程進入到就緒狀態(tài)
}
}
System.out.println("主線程for循環(huán)執(zhí)行完畢..");
try {
int sum = ft.get(); //取得新創(chuàng)建的新線程中的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;
}
執(zhí)行結果:
main 0
main 1
main 2
main 3
...省略
main 99
主線程for循環(huán)執(zhí)行完畢..
Thread-0 0
Thread-0 1
Thread-0 2
Thread-0 3
...省略
Thread-0 99
sum = 4950
首先,我們發(fā)現(xiàn),在實現(xiàn)Callable接口中,此時不再是run()方法了,而是call()方法,此call()方法作為線程執(zhí)行體,同時還具有返回值!在創(chuàng)建新的線程時,是通過FutureTask來包裝MyCallable對象,同時作為了Thread對象的target。那么看下FutureTask類的定義:
public class FutureTask<V> implements RunnableFuture<V> {
//....
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
- 于是,我們發(fā)現(xiàn)FutureTask類實際上是同時實現(xiàn)了Runnable和Future接口,由此才使得其具有Future和Runnable雙重特性。通過Runnable特性,可以作為Thread對象的target,而Future特性,使得其可以取得新創(chuàng)建線程中的call()方法的返回值。
- 執(zhí)行下此程序,我們發(fā)現(xiàn)sum = 4950永遠都是最后輸出的。而“主線程for循環(huán)執(zhí)行完畢..”則很可能是在子線程循環(huán)中間輸出。由CPU的線程調(diào)度機制,我們知道,“主線程for循環(huán)執(zhí)行完畢..”的輸出時機是沒有任何問題的,那么為什么sum =4950會永遠最后輸出呢?
- 原因在于通過ft.get()方法獲取子線程call()方法的返回值時,當子線程此方法還未執(zhí)行完畢,ft.get()方法會一直阻塞,直到call()方法執(zhí)行完畢才能取到返回值。
- main方法其實也是一個線程。在java中所以的線程都是同時啟動的,至于什么時候,哪個先執(zhí)行,完全看誰先得到CPU的資源。
- 在java中,每次程序運行至少啟動2個線程。一個是main線程,一個是垃圾收集線程。因為每當使用java命令執(zhí)行一個類的時候,實際上都會啟動一個JVM,每一個jVM實習在就是在操作系統(tǒng)中啟動了一個進程。