在學習線程以前,首先要理解什麼是進程。打開你的任務管理器,導航欄第一個清清楚楚的寫着進程,點進去會發現是許許多多的你在運行的程序,這就是一個進程。html
like this:java
現代操做系統均可以同時執行多個程序,這就是多任務。線程時創建在進程的基礎上的,好比QQ音樂這個進程能夠同時在執行播放、下載、傳輸等動做。這就叫多線程,每一個線程在執行不一樣的功能。
在單核CPU系統中,也能夠同時運行多個程序,程序運行是搶佔式的,QQ
運行0.001S
,chrome
運行0.01s
,這個時間人是感知不出來的,咱們就會以爲在同時執行。因此爲了提升效率,如今的手機、電腦都是很是多核的。算法
進程和線程的關係就是:一個進程能夠包含一個或多個線程,但至少會有一個線程。chrome
操做系統調度的最小任務單位其實不是進程,而是線程。api
進程和線程是包含關係,可是多任務既能夠由多進程實現,也能夠由線程實現,還能夠混合多進程+多線程。安全
和多線程相比,多進程的缺點是:多線程
多進程的優勢:ide
例:函數
public class MyThread extends Thread { // 線程的主體類 @Override public void run(){ System.out.println("Thread is starting"); } }
上面的MyThread
類繼承Thread
,覆寫了run
方法。一個類只要繼承了此類,就表示這個類爲線程的主體類。run()
是線程的主方法,多線程要執行的方法都在這寫。
可是run()
方法是不能被直接調用的,這牽涉到系統的資源調度問題,想要啓動多線程,必須用start()
完成。學習
public class ThreadDemo { public static void main(String[] args) { new MyThread().start(); // 啓動新線程 }
java語言內置了多線程支持。當Java程序啓動的時候實際上是啓動了一個JVM進程。JVM啓動主線程來執行main()
方法,在main()
方法中能夠啓動其餘線程。
start()
只能由 Thread
類型實例調用,表示啓動一個線程。
"C:\Program Files\Java\jdk1.8.0_221\bin\java.exe" Thread is starting
因而可知,線程建立成功
那麼建立一個多線程呢?
// 多線程主體類 public class MyThread extends Thread { private String title; public MyThread(){ } MyThread(String title){ this.title = title; } @Override public void run(){ for (int i = 0; i<10;i++){ System.out.println(this.title + "is starting"); System.out.println(Thread.currentThread().getName()); } } } public static void main(String[] args) { new Thread(new MyThread("A"),"線程1").start(); new Thread(new MyThread("C"),"線程2").start(); new Thread(new MyThread("B")).start(); }
執行結果:
這個結果中有幾個關注點:
start()
方法,但執行的是run()
方法咱們來看一下源碼,分析一下
public synchronized void start() { if (threadStatus != 0) // 判斷線程狀態 // 每個線程的類的對象只容許啓動一次,重複啓動就會拋出這個異常,由run()拋出 throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { // 調用此方法 start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } private native void start0(); // 註釋部分被我刪掉了,太長了
咱們發現start()
方法調用的是start0()
,而start0()
並無實現,還被native
修飾,那麼native
是啥呢?
在Java程序執行的過程當中考慮到對於不一樣層次的開發者需求,支持了本地的操做系統函數調用。這項技術被稱爲JNI(Java Native Interface)
,但在Java開發過程當中並不推薦這樣使用。利用這項技術,能夠利用操做系統提供的的底層函數,操做一些特殊的處理。
不一樣的系統在進行資源調度的時候由本身的一套算法,要想調用start()
方法啓動線程,就要實現start0()
,這時候JVM
就會根據不一樣的操做系統來具體實現start0()
,總結就是一切的一切都是跨平臺帶來的。
這也規定了,啓動多線程只有一種方案,調用Thread
類中的start()
方法.
Thread.currentThread().getName()
就表明了獲取當前線程的名字。
在返回值中還出現了"Thread-3",這是因爲Thread會自動給沒有命名的線程分配一個不會重複的名字。
這種方式啓動多線程當然沒錯,但存在單繼承的隱患。下面就給出另外一種模式。
首先分別來看一下Thread
類的實現
public class Thread implements Runnable {}
原來Thread
是繼承了Runnable
再看一下Runnable
接口
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
再次驚訝,原來這個run方法也是從這裏繼承的。
那就清楚了,來試一下吧。
// 只須要實現 Runnable,重寫run()便可,其餘絲毫未變 public class MyThread implements Runnable { private String title; public MyThread(){ } MyThread(String title){ this.title = title; } @Override public void run(){ for (int i = 0; i<10;i++){ System.out.println(this.title + "is starting"); System.out.println(Thread.currentThread().getName()); } } } public class ThreadDemo { public static void main(String[] args) { new Thread(new MyThread("A線程"),"線程1").start(); new Thread(new MyThread("C線程"),"線程2").start(); new Thread(new MyThread("B線程")).start(); // lambda 語法實現 // new Thread(() -> { // System.out.println("啓動新的線程"); // }).start(); } }
結果:
徹底一致。
在之後的多線程設計實現,優先使用Runnable
接口實現。
還沒完,咱們依靠Runnable
接口實現的時候,會發現有一個缺陷,就是沒有返回值,那有沒有帶返回值的實現方式呢?有!繼續看
在Java1.5
以後爲了解決run()
方法沒有返回值的問題,引入了新的線程實現java.util.concurrent.Callable
接口.
咱們看一下Oracle的api文檔:
能夠看到Callable定義的時候利用了一個泛型,表明了返回數據的類型,避免了向下轉型帶來的安全隱患
瞭解向下轉型能夠看個人另外一篇文章:http://www.javashuo.com/article/p-vdtvwwnj-ce.html
可是問題又來了,咱們上面已經說過了,要想啓動多線程,必須使用Thread
類提供的
start()
方法調用Runnable
接口的 run()
方法,但是如今 Callable
中並無run()
方法,那怎麼辦呢?
再來找到一個FutureTask
類:
public class FutureTask<V> extends Object implements RunnableFuture<V>
構造方法:
它的構造方法能夠接收一個Callable
類型參數
它又繼承了RunnableFuture<V>
,那就繼續往上找
public interface RunnableFuture<V> extends Runnable, Future<V>
出現了,它出現了,Runnable
咱們知道了,是沒有返回值的,如今看看Future<V>
是個啥
它有一個get()
方法能夠獲得一個泛型返回值。
OK,如今咱們就能夠梳理一下找到的這些東西怎麼個關係:
public class CallableThread implements Callable<String> { // 繼承實現Callable<V> // 覆寫call()方法 @Override public String call() throws Exception{ for (int i = 0;i<10;i++){ System.out.println("*********線程執行、i="+ i); } return "線程執行完畢"; } } // 調用 FutureTask<String> task = new FutureTask<>(new CallableThread()); new Thread(task).start(); System.out.println("【線程返回數據】" + task.get());
結果:
爲了獲得一個返回值可真不容易,核心思想仍是實例化一個Thread
對象,可經過其構造方法接收一個Rannable
類型參數,調用start()
啓動線程.
總結:基原本說就這三種建立多線程模式,根據場景使用。
**純屬我的理解,但願指正錯誤,共同交流。