天天看點

Java線程--概念與原理

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】