
由上圖中程序的調用流程可知,這個程序只有一個執行流程,所以這樣的程序就是單線程程序。假如一個程序有多條執行流程,那么,該程序就是多線程程序。
進程就是正在運行的程序,是系統進行資源分配和調用的獨立單位。每一個進程都有它自己的內存空間和系統資源。
單進程的計算機只能做一件事情,而我們現在的計算機都可以做多件事情。舉例:一邊玩游戲(游戲進程),一邊聽音樂(音樂進程)。也就是說現在的計算機都是支持多進程的,可以在一個時間段內執行多個任務。并且呢,可以提高CPU的使用率,解決了多部分代碼同時運行的問題。 其實,多個應用程序同時執行都是CPU在做著快速的切換完成的。這個切換是隨機的。CPU的切換是需要花費時間的,從而導致了效率的降低。
線程是程序的執行單元,執行路徑;是進程中的單個順序控制流,是一條執行路徑;一個進程如果只有一條執行路徑,則稱為單線程程序。一個進程如果有多條執行路徑,則稱為多線程程序。
多線程的存在,不是提高程序的執行速度。其實是為了提高應用程序的使用率。程序的執行其實都是在搶CPU的資源,CPU的執行權。多個進程是在搶這個資源,而其中的某一個進程如果執行路徑比較多,就會有更高的幾率搶到CPU的執行權。我們是不敢保證哪一個線程能夠在哪個時刻搶到,所以線程的執行有隨機性。
前者是邏輯上同時發生,指在某一個時間內同時運行多個程序;后者是物理上同時發生,指在某一個時間點同時運行多個程序。那么,我們能不能實現真正意義上的并發呢?答案是可以的,多個CPU就可以實現,不過你得知道如何調度和控制它們。
PS:
一個進程中可以有多個執行路徑,稱之為多線程。一個進程中至少要有一個線程。開啟多個線程是為了同時運行多部分代碼,每一個線程都有自己運行的內容,這個內容可以稱為線程要執行的任務。java 命令會啟動 java 虛擬機,啟動 JVM,等于啟動了一個應用程序,也就是啟動了一個進程。該進程會自動啟動一個 “主線程” ,然后主線程去調用某個類的 main 方法。所以 main方法運行在主線程中。在此之前的所有程序都是單線程的。
思考:JVM虛擬機的啟動是單線程的還是多線程的?
答案:JVM啟動時啟動了多條線程,至少有兩個線程可以分析的出來
執行main函數的線程,該線程的任務代碼都定義在main函數中。
負責垃圾回收的線程。System類的gc方法告訴垃圾回收器調用finalize方法,但不一定立即執行。
由于線程是依賴進程而存在的,所以我們應該先創建一個進程出來。而進程是由系統創建的,所以我們應該去調用系統功能創建一個進程。Java是不能直接調用系統功能的,所以,我們沒有辦法直接實現多線程程序。但是呢?Java可以去調用C/C++寫好的程序來實現多線程程序。由C/C++去調用系統功能創建進程,然后由Java去調用這樣的東西,然后提供一些類供我們使用。我們就可以實現多線程程序了。
運行結果:

Thread類用于描述線程,線程是需要任務的。所以Thread類也有對任務的描述。這個任務就是通過Thread類中的run方法來體現。也就是說,run方法就是封裝自定義線程運行任務的函數,run方法中定義的就是線程要運行的任務代碼。所以只有繼承Thread類,并復寫run方法,將運行的代碼定義在run方法中即可。
啟動線程調用的是start()方法,不是run()方法,run()方法只是封裝了被線程執行的代碼,調用run()只是普通方法的調用,無法啟動線程。
不能,會出現IllegalThreadStateException非法的線程狀態異常。
思考:如何獲取main方法所在的線程名稱呢?
public static Thread currentThread() // 獲取任意方法所在的線程名稱運行結果:

運行結果:

實現Callable的優缺點
好處:可以有返回值;可以拋出異常。弊端:代碼比較復雜,所以一般不用假如我們的計算機只有一個 CPU,那么 CPU 在某一個時刻只能執行一條指令,線程只有得到 CPU時間片,也就是使用權,才可以執行指令。那么Java是如何對線程進行調用的呢?
分時調度模型:所有線程輪流使用 CPU 的使用權,平均分配每個線程占用 CPU 的時間片 搶占式調度模型:優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那么會隨機選擇一個,優先級高的線程獲取的 CPU 時間片相對多一些。 Java使用的是搶占式調度模型。
注意:線程默認的優先級是5;線程優先級的范圍是:1-10;線程優先級高僅僅表示線程獲取CPU的時間片的幾率高,但是要在次數比較多,或者多次運行的時候才能卡到比較好的結果。



基本思想:讓程序沒有安全問題的環境。 解決辦法:同步機制:同步代碼塊、同步方法。把多個語句操作共享數據的代碼給鎖起來,讓任意時刻只能有一個線程執行即可。
運行結果:

同步方法:就是把同步關鍵字加到方法上
package cn.itcast;//賣票程序的同步代碼塊實現示例 class Ticket implements Runnable { // 定義100張票 private static int tickets = 10; Object obj = new Object(); public void run() { while (true) { sellTicket(); } } private synchronized void sellTicket() { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票 "); } }}class TicketDemo { public static void main(String[] args) { // 通過Thread類創建線程對象,并將Runnable接口的子類對象作為Thread類的構造函數的參數進行傳遞。 Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); // 調用線程對象的start方法開啟線程。 t1.start(); t2.start(); t3.start(); t4.start(); }}運行結果:

PS:
同步方法的鎖對象是什么呢?this對象如果是靜態方法,同步方法的鎖對象又是什么呢?該類的字節碼文件(類的class文件)那么,我們到底使用同步方法還是同步代碼塊?如果鎖對象是this,就可以考慮使用同步方法。否則能使用同步代碼塊的盡量使用同步代碼塊。
某些線程安全的類:StringBuffer、Vector、HashTable,雖然線程安全,但是效率較低,我們一般不用,而用Collections工具類解決線程安全的問題。 List list = Colletions.syncrinizedList(new ArrayList()):獲取線程安全的List集合
雖然我們可以理解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪里加上了鎖,在哪里釋放了鎖,為了更清晰的表達如何加鎖和釋放鎖,JDK5以后提供了一個新的鎖對象Lock Lock接口:Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作,此實現允許更靈活的結構。
void lock():獲取鎖
void unlock():釋放鎖
ReentrantLock:Lock的實現類
package cn.itcast;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;class SellTicket implements Runnable { // 定義票 private int tickets = 100; // 定義鎖對象 private Lock lock = new ReentrantLock(); public void run() { while (true) { try { // 加鎖 lock.lock(); if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票"); } } finally { // 釋放鎖 lock.unlock(); } } }}/* * 雖然我們可以理解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪里加上了鎖,在哪里釋放了鎖, * 為了更清晰的表達如何加鎖和釋放鎖,JDK5以后提供了一個新的鎖對象Lock。 * * Lock: void lock(): 獲取鎖。 void unlock():釋放鎖。 ReentrantLock是Lock的實現類. */public class SellTicketDemo { public static void main(String[] args) { // 創建資源對象 SellTicket st = new SellTicket(); // 創建三個窗口 Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); // 啟動線程 t1.start(); t2.start(); t3.start(); }}運行結果:

同步弊端:效率低,如果出現了同步嵌套,就容易產生死鎖問題。 死鎖問題:是指兩個或者兩個以上的線程在執行的過程中,因爭奪資源產生的一種互相等待現象
package cn.itcast;class Ticket implements Runnable { private static int num = 100; Object obj = new Object(); boolean flag = true; public void run() { if (flag) { while (true) { synchronized (obj) { show(); } } } else while (true) show(); } public synchronized void show() { synchronized (obj) { if (num > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "...function..." + num--); } } }}class DeadLockDemo { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } t.flag = false; t2.start(); }}運行結果:

原因分析:
由上圖可以看到程序已經被鎖死,無法向下執行。run方法中的同步代碼塊需要獲取obj對象鎖,才能執行代碼塊中的show方法。而執行show方法則必須獲取this對象鎖,然后才能執行其中的同步代碼塊。當線程t1獲取到obj對象鎖執行同步代碼塊,線程t2獲取到this對象鎖執行show方法。同步代碼塊中的show方法因無法獲取到this對象鎖無法執行,show方法中的同步代碼塊因無法獲取到obj對象鎖無法執行,就會產生死鎖。
多個線程在處理統一資源,但是任務卻不同,這時候就需要線程間通信。為了實現線程間的通信Java提供了等待喚醒機制。 等待/喚醒機制涉及的方法:
wait():讓線程處于凍結狀態,被wait的線程會被存儲到線程池中。
notify():喚醒線程池中的一個線程(任何一個都有可能)。
notifyAll():喚醒線程池中的所有線程。
PS:
這些方法都必須定義在同步中,因為這些方法是用于操作線程狀態的方法。
必須要明確到底操作的是哪個鎖上的線程!
wait和sleep區別?
4、為什么操作線程的方法wait、notify、notifyAll定義在了object類中,因為這些方法是監視器的方法,監視器其實就是鎖。鎖可以是任意的對象,任意的對象調用的方式一定在object類中。 生產者-消費者問題:
package cn.itcast;class Student { String name; int age; boolean flag;}class SetThread implements Runnable { private Student s; private int x = 0; public SetThread(Student s) { this.s = s; } public void run() { while (true) { synchronized (s) { // 判斷有沒有 if (s.flag) { try { s.wait(); // t1等著,釋放鎖 } catch (InterruptedException e) { e.printStackTrace(); } } if (x % 2 == 0) { s.name = "張三"; s.age = 15; } else { s.name = "李四"; s.age = 16; } x++; // x=1 // 修改標記 s.flag = true; // 喚醒線程 s.notify(); // 喚醒t2,喚醒并不表示你立馬可以執行,必須還得搶CPU的執行權。 } // t1有,或者t2有 } }}class GetThread implements Runnable { private Student s; public GetThread(Student s) { this.s = s; } public void run() { while (true) { synchronized (s) { if (!s.flag) { try { s.wait(); // t2就等待了。立即釋放鎖。將來醒過來的時候,是從這里醒過來的時候 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(s.name + "---" + s.age); // 林青霞---27 // 劉意---30 // 修改標記 s.flag = false; // 喚醒線程 s.notify(); // 喚醒t1 } } }}public class StudentDemo { public static void main(String[] args) { // 創建資源 Student s = new Student(); // 設置和獲取的類 SetThread st = new SetThread(s); GetThread gt = new GetThread(s); // 線程類 Thread t1 = new Thread(st); Thread t2 = new Thread(gt); // 啟動線程 t1.start(); t2.start(); }}運行結果:

Java中使用ThreadGroup來表示線程組,它可以對一批線程進行分類管理,Java允許程序直接對線程組進行控制。默認情況下,所有的線程都屬于主線程組。 public final ThreadGroup getThreadGroup():返回該線程所屬的線程組。 Thread(ThreadGroup group,Runnable target, String name):給線程設置分組
程序啟動一個新線程成本是比較高的,因為它涉及到要與操作系統進行交互。而使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,更應該考慮使用線程池。
線程池里的每一個線程代碼結束后,并不會死亡,而是再次回到線程池中成為空閑狀態,等待下一個對象來使用。在JDK5之前,我們必須手動實現自己的線程池,從JDK5開始,Java內置支持線程池。JDK5新增了一個Executors工廠類來產生線程池,有如下幾個方法 public static ExecutorService newCachedThreadPool(): 創建一個線程池對象public static ExecutorService newFixedThreadPool(int nThreads): 創建一個線程池對象,控制要創建幾個線程public static ExecutorService newSingleThreadExecutor(): 創建一個使用單個 worker 線程的 Executor 這些方法的返回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。它提供了如下方法:Future定時器是一個應用十分廣泛的線程工具,可用于調度多個定時任務以后臺線程的方式執行。在Java中,可以通過Timer和TimerTask類來實現定義調度的功能 Timer:一種工具,線程用其安排以后在后臺線程中執行的任務。可安排任務執行一次,或者定期重復執行。

實際開發中使用的是Quartz:一個完全由java編寫的開源調度框架。
將線程的任務從線程的子類中分離出來,進行了單獨的封裝,實現數據和程序分離,按照面向對象的思想將任務封裝成對象。
避免了Java單繼承的局限性。所以,創建線程的第二種方式較為常用。
新建,就緒,運行,阻塞(同步阻塞,等待阻塞,其他阻塞),死亡
新聞熱點
疑難解答