程序在沒有流程控制的前提下,代碼都是從上而下逐行依次執行的。基於這樣的機制,若是咱們使用程序來實現邊打遊戲,邊聽音樂的需求時,就會很困難;由於按照執行順序,只能從上往下依次執行;同一時刻,只能執行聽音樂和打遊戲的其中之一。爲了解決這樣的問題,在程序設計中引入了多線程併發。本文中的知識對windows、mac、linux
系統都適用,但展現界面和功能名稱上不太同樣;相關的截圖這裏以windows
爲例。java
並行和併發是兩個很容易混淆的概念,他們在字面上理解起來可能沒有很大的差別,但要放在計算機運行環境中來解釋,二者是有很大區別的:linux
CPU
在多個事件上來回切換,但切換的時間很快,並不能被人眼捕獲,所以在一段人類能夠觀察到的時間內,多個事件是同時發生的; 操做系統的運行環境中,併發指的就是一段時間內宏觀上多個程序在同時運行;在單CPU
的環境中,微觀上每個時刻僅有一個程序被CPU
執行(也就是僅有一個程序得到了CPU時間片
),CPU
是在多個程序之間來回交替執行,也就是給每一個程序的運行時間進行調度,從而實現多個程序的併發運行。隨着計算機硬件的不斷髮展,現現在的計算機通常都是有多個CPU
的,在這樣的多個CPU
的環境中,本來由單個處理器運行的這些程序就能夠被分配給多個CPU
來運行,從而實現真程序的並行運行,不管從宏觀上,仍是微觀上,程序都是同時運行的。這樣,程序的運行效率就會大大提升。程序員
PS:CPU
時間片就是CPU
分配給每一個程序的運行時間。windows
在買電腦的時候,電腦廠商宣傳的「幾核處理器」,其中「核」表示的是CPU
有幾個物理核心,可以並行處理幾個程序的調用。想要知道本身電腦是幾核的,能夠打開「任務管理器」來查看。安全
也能夠經過計算機屬性、設備管理器來查看。服務器
因此,單核處理器是不能並行運行多個任務的,只能是多個任務在單核處理器中併發運行,咱們把每一個任務用一個線程來表示,多個線程在單個處理器中的併發運行咱們稱之爲線程調度。從宏觀上講,多個線程是並行運行的;從微觀上講,多個線程是串行運行的,也就是一個線程一個線程的運行;若是對這裏的宏觀和微觀不太好理解的話,能夠把宏觀看做是站在人的角度看待程序運行,把微觀看做是站在CPU
的角度看待程序運行,這樣就好理解多了。多線程
進程:進程是指一個在內存中運行的應用程序,每一個進程在內存中都有一塊獨立的內存空間。每一個軟件均可以啓動多個進程。併發
線程:線程指的是進程中的一個控制單元,也就是進程中的每一個單元任務,一個進程中能夠有多個線程同時併發運行。ide
多進程指的是操做系統中同時運行的多個程序,多線程指的是同一個進程中同時運行的多個任務。操做系統中運行的每一個任務就是一個進程,進程中的每一個任務就是一個線程;操做系統就是一個多任務系統,它能夠有多個進程,每一個進程又能夠有多個線程。函數
線程和進程的區別:
CPU
調度器來決定的,程序員沒法控制;線程調度:
計算機單個CPU
在任意時刻只能執行一條計算機指令,每一個進程只有得到CPU
使用權才能執行相關指令;多線程併發,其實就是運行中各個進程輪流獲取CPU
的使用權來分別執行各自的任務;在多進程的環境中,會有多個線程處於等待獲取CPU
使用權的狀態中,爲這些等待中的線程分配CPU
使用權的操做就成爲線程調度。線程調度分爲搶佔式調度和分時調度。
CPU
資源,誰搶到誰就運行,有更多的隨機性;CPU時間片
;Java的多線程中線程調度就是使用搶佔式調度的。
多線程和單線程,就比如多行道和單行道,多行道能夠有多輛車同時行駛經過,而單行道只能是多輛車按順序依次行駛經過;多線程同時有多個線程併發運行,單線程只有單個線程對多個任務按順序依次執行。
若是以下載文件爲例:單線程就是隻有一個文件下載的通道,多線程則是同時有多個下載通道在下載文件。當服務器提供下載服務時,下載程序是共享服務器帶寬的,在優先級相同的狀況下,服務器會對下載中的全部線程平均分配帶寬:
寬帶帶寬是以位(bit)
來計算的,而下載速度是以字節(byte)
計算的,1 byte = 8 bit
,故1024KB/s
表明的是上網寬帶爲1M(1024千位)
,而下載速度須要用1024KB/s
除去8
,得出128KB/s
。
多線程是爲了同步完成多項任務,是爲了提升系統總體的效率,而不能提升程序代碼自身的運行效率。
多線程的優點:多線程做爲一種多任務、高併發的運行機制,有其獨到的優點所在:
Java
語言內置了對多線程的支持,而不只僅是簡單的調用底層操做系統的調度;Java
對多線程的支持也很友好,能大大簡化開發成本;在Java
中建立進程可經過兩種方式來實現:
1.經過java.lang.Runtime來實現,示例代碼以下:
public static void main(String []args) throws IOException { // 方式一:經過經過java.lang.Runtime來實現打開 cmd Runtime runtime = Runtime.getRuntime(); runtime.exec("cmd"); }
public static void main(String []args) throws IOException { // 方式二:經過經過java.lang.ProcessBuilder來實現打開 cmd ProcessBuilder pb = new ProcessBuilder("cmd"); pb.start(); }
1、經過繼承Thread類建立線程;須要注意的是:只有Thread
的子類纔是線程類;
Thread
子類中重寫Thread
類中的run
方法,在run
方法中編寫線程邏輯;public class ExtendsThreadDemo { public static void main(String []args) { for (int i = 0; i < 50; i++) { System.out.println("主線程" + i); if (i == 13) { NewThread newThread = new NewThread(); newThread.start(); } } } } // 新線程類 class NewThread extends Thread { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("新線程" + i); } } }
2、經過實現Runnable接口建立線程;須要注意,這裏的Runnable
實現類並非線程類,因此啓動方式和Thread
子類會有所不一樣;
run
方法,在run
方法中編寫線程邏輯;Thread
對象,傳入Runnable
實現類對象,執行線程邏輯;示例代碼以下:
public class ImplementsRunnableDemo { public static void main(String []args) { for (int i = 0; i < 50; i++) { System.out.println("主線程" + i); if (i == 13) { Runnable runnable = new NewRunnableImpl(); Thread thread = new Thread(runnable); thread.start(); } } } } // 新線程類 class NewRunnableImpl implements Runnable { @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println("新線程" + i); } } }
3、使用匿名內部類建立線程
使用接口的匿名內部類來建立線程,示例代碼以下:
// 使用接口的匿名內部類 public class AnonymousInnerClassDemo { public static void main(String []args) { for (int i = 0; i < 50; i++) { System.out.println("主線程" + i); if (i == 13) { new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 50; j++) { System.out.println("新線程" + j); } } }).start(); } } } }
固然了,也能夠使用Thread類的匿名內部類建立線程,不過這樣的方式不多使用;示例代碼以下:
// 使用Thread類的匿名內部類 public class AnonymousInnerClassDemo { public static void main(String []args) { for (int i = 0; i < 50; i++) { System.out.println("主線程" + i); if (i == 13) { new Thread() { @Override public void run() { for (int j = 0; j < 50; j++) { System.out.println("新線程" + j); } } }.start(); } } } }
案例需求:六一兒童節,設置了搶氣球比賽節目,共有50
個氣球,三個小朋友小紅、小強、小明
來搶;請使用多線程技術來實現上述比勝過程。
1、使用繼承Thread類的方式來實現上述案例;示例代碼以下:
public class ExtendsDemo { public static void main(String []args) { new Student("小紅").start(); new Student("小強").start(); new Student("小明").start(); } } class Student extends Thread { private int num = 50; private String name; public Student(String name) { super(name); this.name = name; } @Override public void run() { for (int i = 0; i < 50; i++) { if (num > 0) { System.out.println(this.name + "搶到了" + num + "號氣球"); num--; } } } }
經過查看輸出結果,發現一個問題:每一個小朋友都搶到了50個氣球,這和本來只有50個氣球相矛盾了;不過別急,咱們可使用第二種方式:使用實現接口的方式來實現上述案例 來解決。
2、使用實現接口的方式來實現上述案例;示例代碼以下:
public class ImplementsDemo { public static void main(String []args) { Balloon balloon = new Balloon(); new Thread(balloon, "小紅").start(); new Thread(balloon, "小強").start(); new Thread(balloon, "小明").start(); } } // 氣球 class Balloon implements Runnable { private int num = 50; @Override public void run() { for (int i = 0; i < 50; i++) { if (num > 0) { System.out.println(Thread.currentThread().getName() + "搶到了" + num + "號氣球"); num--; } } } }
在該案例中咱們是用了Thread.currentThread()
方法,該方法的做用是返回當前正在執行的線程對象的引用,因此當前正在執行的線程對象的名稱就能夠這樣來獲取:String name = Thread.currentThread().getName();
。
經過查看該案例的打印結果,不難發現:三個小朋友一共搶到了50
個氣球,符合了需求中規氣球總共有50
個的要求。咱們再來分析主函數中的代碼,發現是由於3
個線程共享了一個Balloon
對象,該對象中的氣球數量就在50
個。
按照這樣的思路,上述使用繼承Thread
類的方式中出現的問題就能夠解決了。接下來就對上述兩種實現多線程的方式進行分析和總結:
使用繼承Thread類的方式:
super.getName()
來直接獲取當前線程對象的名稱;Java
是單繼承的,因此若是繼承了Thread
,該類就不能再有其餘的父類了;使用實現接口的方式:
Thread.currentThread().getName();
來獲取;Java
是支持多實現的,因此除了Runnable
接口以外,還能夠實現其餘的接口,繼承另外的類;在下一篇文章中,我會繼續使用上述的案例來分析線程不安全以及相關的解決方法,敬請期待。
完結。老夫雖不正經,但老夫一身的才華