1.什麼是線程?
我們知道一份代碼就是一個程序,當代碼運行起來後就是一個進程。進程中可以完成一些我們想要它完成的任務,那麼每一個任務就是一個線程。例如我們常用的瀏覽器,瀏覽器打開就是開啓了一個進程。接着我們可以在瀏覽器中打開網頁窗口,那麼一個窗口實際上就是一個線程。當然這只是一個很簡單的例子,實際上瀏覽器除了網頁窗口外還要很多線程。
線程基於進程,各自獨立。線程共享數據,這使線程之間的通訊非常高效,便捷。
2.創建線程
2.1 線程狀態
線程的狀態有:1.就緒狀態;2.運行狀態;3.阻塞狀態
就緒狀態:線程已經啓動,但是沒有獲得CPU資源,無法運行。
運行狀態:線程在就緒狀態獲得到CPU資源,此時線程就處於運行中。
阻塞狀態:造成線程阻塞的因素有很多,此時與CPU資源無關,即使CPU有資源運行線程,線程仍然阻塞。
由以上三種線程的狀態定義我們就可以推出以下關係:
首先創建線程,接下來調用start()方法啓動線程。線程啓動後進入就緒狀態,此時線程等待CPU資源以開始運行。線程得到CPU資源進入運行狀態,當線程的時間片結束,則線程會返回就緒狀態再次等待CPU資源。如此往復,直到線程完成所有任務,此時線程終止。但是線程在運行狀態時,可能因爲異常或業務要求等等原因會進入阻塞狀態。當阻塞狀態結束後,卻不會返回運行狀態,而是進入就緒狀態,再次等候CPU資源。
以上的描述可能不利於理解,實際邏輯如圖:
2.2 創建線程
瞭解了線程的狀態以及它們之間的關係後,我們先來創建一個線程。Java中我們有一個Thread類,顧名思義就是線程的類。我們創建線程圍繞這個類有三種方法
2.2.1 繼承Thread類創建線程
Thread類中有一個run()方法,這個方法的內容實際上就是最終的線程所要完成的任務或要執行的代碼。爲什麼說run()方法中的內容是線程最終要執行的代碼呢?因爲我們的Thread只是一個普通的類,而我們爲這個類實例化後得到的也只是一個普通對象。只有調用了這個對象的start()方法後,才能真正啓動一個線程,而此時這個線程所執行的代碼正是run()方法中的代碼。
那麼我們就可以通過創建一個類繼承Thread類,覆寫Thread類的run方法來添加我們所要的代碼。通過對我們的類實例化創建線程。
如下代碼:
public class MyThreadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//實際上使用start()方法將會運行一個進程,此時這個進程的運行是與main()方法無關而獨立的
//所以線程對象的run方法與main()方法中的打印的執行先後順序是沒有聯繫的
myThread.start();
//這是一個很簡單的對象調用方法,此時run()方法的執行是在main()線程中的,要按照正常的執行順序執行
myThread.run();
System.out.println("這是main()方法的打印");
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("這是一個線程的run()方法");
}
}
2.2.2 Runnable接口創建線程
我們查看Thread類的構造方法,可以看到這樣一個構造方法:
public Thread(Runnable target)
Runnable是一個接口,實際上Thread類就是一個實現了Runnable接口的類。所以Runnable接口中也有run()方法,又由於Java支持向上轉型,所以我們可以創建一個類實現Runnable接口並且實現run()方法,然後給調用Thread類的構造方法,以這個類的實例化對象來作爲參數,由此創建線程。
如下代碼:
public class MyRunnableTest {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.start();
thread2.start();
thread3.start();
Thread thread4 = new Thread(new Runnable() {
public void run() {
Thread.currentThread().setName("Thread-Runnable匿名對象線程");
System.out.println("這是"+Thread.currentThread().getName()+"線程的run方法");
}
});
thread4.start();
Runnable runnable1 = () -> {
Thread.currentThread().setName("Thread-Runnable使用Lamdba表達式實例化線程");
System.out.println("這是"+Thread.currentThread().getName()+"線程的run方法");
};
Thread thread5 = new Thread(()->{
Thread.currentThread().setName("Thread-Runnable使用Lamdba表達式實例化線程");
System.out.println("這是"+Thread.currentThread().getName()+"線程的run方法");
});
// Thread thread5 = new Thread(runnable1);
thread5.start();
}
}
class MyRunnable implements Runnable {
public void run() {
String name = Thread.currentThread().getName();
System.out.println("這是" + name + "線程的run方法");
}
}
2.2.3 Callable接口創建線程
以上兩種方式創建的線程最終的代碼都是在run()方法中的,而run()方法沒有返回值。但是在一些情況下我們是需要有一個返回值的,這是上面的兩種創建線程方式就不能滿足我們的要求了。此時我們就可以使用Callable接口,它創建的現成的最終的代碼是在call()方法中的,而Call方法是可以有返回值的。但是Thread類並沒有一個接收Callable類對象的構造方法,此時我們就要藉助一個類---FutureTask。我們來看一看FutureTask這個類,它實現了RunnableFuture這個接口,而RunnableFuture接口又繼承了Runnable接口。而且,FutureTask這個類又有一個接收Callable接口的構造方法。我們在創建一個類實現Callable接口並且實現call()方法添加我們的代碼。那麼根據Java向上轉型,我們就能夠爲FutureTask類實例化一個接收實現Callable接口的類對象的對象,最終在將這個對象傳入Thread類的構造方法創建線程。
用文字描述可能比較難以理解,我們看一下這個圖:
如下代碼:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyCallableTest {
public static void main(String[] args) {
MyCallable callable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(callable);
new Thread(futureTask,"Thread-1").start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("線程"+Thread.currentThread().getName()+"的打印");
return "線程"+Thread.currentThread().getName()+"返回的數據";
}
}
以上代碼都在我的github上,github鏈接:https://github.com/nodonotnodo/java/tree/master/thread