一 概述 1.什麼是進程? 進程是一個相對獨立的執行單位。 2.什麼是線程? 進程的一部分,進程中實際的任務執行者,必須依附於進程。線程對進程的依賴主要體現在: 線程不能脫離進程開啟,必須在進程開啟的前提下開啟。 線程有時必須從進程中獲取數據。 3.線程與進程的區別? 線程與進程是兩個相對的概念,一 ...
一 概述
1.什麼是進程?
進程是一個相對獨立的執行單位。
2.什麼是線程?
進程的一部分,進程中實際的任務執行者,必須依附於進程。線程對進程的依賴主要體現在:
- 線程不能脫離進程開啟,必須在進程開啟的前提下開啟。
- 線程有時必須從進程中獲取數據。
3.線程與進程的區別?
線程與進程是兩個相對的概念,一個對象相對於它擁有的執行單位被稱為進程,從自身所屬的上級執行者來看,又被稱作線程。
4.多線程的設計目的、用途、意義
CUP在任何一個時間點都只能執行一個線程,多線程的本質是多個任務高速交替執行。如果多個線程間不存在數據交換,可以單獨執行,採用多線程並不能減少總的執行時間。
多線程設計的主要目的不是為了提高執行速度,而是相對平均地執行每一個線程,不致使某一個線程長時間持有CPU時間片,其他線程長時間處於等待狀態。由於CPU時間片在多個線程間切換迅速,超出了人類感官所能察覺的範圍,所以感覺多個任務都是執行。
例如,當多個人訪問同一個網站,每一個人都需要5分鐘,如果不採用多線程,同時只允許一個人進入網站,其他多數人都要等待5分鐘,用戶體驗很差。這是採用多線程,一個人進入以後,CPU轉向其他用戶,讓其他用戶陸續進入,用戶體驗就提高了,儘管總的執行時間並沒有減少。
5.CPU調度模式
- 分時調度模式:系統平均地為各個線程分配CPU時間片。
- 搶占式調度模式:各個線程搶奪CPU時間片,CPU時間片線上程間不均勻分配。
二 線程創建
1.Java SE API 提供了兩種創建線程的方式:
- 實現Runnable介面,將實現類的對象作為參數傳入Thread的構造器中。
- 直接繼承Thread類。
2.無論採用哪種方式,需要執行的任務都必須放在run方法中。
3.兩種創建方式的區別:
⑴Java採用單繼承,即一個類只能繼承一個父類,而允許一個類實現多個介面,採用繼承Thread的方式創建線程,就使本類失去了唯一的一次繼承機會。
⑵實現資源共用的方式不同
- 首先需要明確的一點,通過繼承Thread創建線程的方式也可以實現資源共用,只是由於通過new關鍵字創建的多個線程是不同的對象,那麼共用資源只能來自於外部,通常通過構造器註入。
- 而通過實現Runnable介面的方式創建線程,可以利用同一個實現類對象創建多個線程,實現了資源共用,共用資源來自線程內部。
4.採用實現Runnable介面的方式創建線程,不僅保留了唯一的繼承機會,而且在實現資源共用的操作相對簡單,所以一般採用該方式創建線程。
5.通過繼承Thread的方式實現資源共用:
提供共用資源的外部類
package com.test.thread.extendsThread; public class MyClass { public int count; }
Thread線程子類
package com.test.thread.extendsThread; public class MyThread extends Thread { private MyClass obj; public MyThread() { super(); } public MyThread(MyClass obj) { super(); this.obj = obj; } @Override public void run() { System.out.println("obj=" + obj); while (true) { synchronized (obj) { if (obj.count > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "----當前數量=" + obj.count--); } else return; } } } }
測試類
package com.test.thread.extendsThread; import org.junit.Test; import com.test.thread.synchronizedTest.demo02.MyTestRunnable; import net.sourceforge.groboutils.junit.v1.MultiThreadedTestRunner; import net.sourceforge.groboutils.junit.v1.TestRunnable; public class ThreadExtendsTest { /** * JUnit單元測試不支持多線程測試,使用GroboUtils進行多線程測試(導入架包) * * @throws Throwable */ @Test public void test01() throws Throwable { MyClass obj = new MyClass(); obj.count = 10; MyThread myth01 = new MyThread(obj); MyThread myth02 = new MyThread(obj); MyThread myth03 = new MyThread(obj); MyTestRunnable t01 = new MyTestRunnable(myth01); MyTestRunnable t02 = new MyTestRunnable(myth02); MyTestRunnable t03 = new MyTestRunnable(myth03); TestRunnable[] tr = new TestRunnable[3]; tr[0] = t01; tr[1] = t02; tr[2] = t03; MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(tr); mttr.runTestRunnables(); } // 放在主線程中測試 public static void main(String[] args) { MyClass obj = new MyClass(); obj.count = 10; MyThread t01 = new MyThread(obj); MyThread t02 = new MyThread(obj); MyThread t03 = new MyThread(obj); t01.setName("t01"); t02.setName("t02"); t03.setName("t03"); t01.start(); t02.start(); t03.start(); } }
三 線程生命周期
1.什麼是線程的生命周期?
由不同階段構成的線程從出生到死亡的整個過程,叫做線程的生命周期。
2.線程生命周期的意義
瞭解線程的生命周期能夠更好地掌握線程的運行情況,比如線程的就緒狀態,意味著不是調用start方法之後,線程立即執行。
3.生命周期的幾個階段:
- 出生狀態:線程創建完成,尚未開啟前的狀態。
- 就緒狀態:調用start方法開啟線程,線程尚未運行的狀態。
- 運行狀態:線程獲取CPU時間片執行時的狀態。
- 休眠狀態:線程調用sleep方法後進入指定時長的休眠狀態,時間結束進入就緒狀態。
- 等待狀態:監聽對象線上程內部調用wait方法後,線程失去對象鎖,進入等待狀態。
- 阻塞狀態:線程發出輸入或者輸出請求後進入阻塞狀態。
- 死亡狀態:run方法執行完畢,線程死亡。
四 線程的加入
一個線程A在另一個線程B內部調用join方法,B線程中止,A線程開始執行,A線程執行完畢,B線程才開始執行。
五 線程優先順序
線程優先順序設定了線程獲取CPU時間片的概率,僅僅是一種概率,不能保證優先順序高的線程一定優先獲得CPU時間片。
線程優先順序分為10個等級,從1-10,數值越大,優先順序越高,通過setProprity(int)方法設置。
六 線程禮讓
Thread.yield,線程禮讓只是通知當前線程可以將資源禮讓給其他線程,並不能保證當前線程一定讓出資源。
七 同步機制
1.什麼是線程同步機制?
使得同一資源同一時刻只能有一個線程訪問的安全機制,即一個線程訪問完畢,其他線程才能訪問。
2.同步機制的目的
由於目標資源同一時刻只有一個線程訪問,解決了線程安全問題。
3.什麼是線程安全問題?
⑴線程安全問題產生條件
- 多線程併發訪問。
- 存在可修改的共用數據。
⑵第一個線程獲取了共用數據,操作結束前,第二個線程修改了該數據,導致第一個線程運算時採用的不是獲取時的數據。
4.同步機制解決線程安全問題的原理
synchronized(共用對象){ 修改共用數據的代碼 }
上述操作給修改共用數據的代碼加了一把對象鎖。任何一個對象只有一把對象鎖,線程只有獲得了對象鎖才能訪問加鎖的資源。一個線程獲取了對象鎖,執行加鎖的代碼,執行完畢,歸還對象鎖,其他線程開始爭奪對象鎖,訪問資源。
5.類鎖
synchronized關鍵字加到靜態方法上時,形成類鎖,執行該方法上必須獲取類鎖。
類鎖與對象鎖是兩種不同的鎖,允許一個線程持有類鎖,另一個線程持有對象鎖。
6.synchronized關鍵字
synchronized關鍵字加在成員方法,該方法成為同步成員方法,由於一個對象只有一把對象鎖,一個線程訪問了一個同步成員方法,其他線程不能訪問其他同步成員方法。
同步方法不可以被繼承,同步方法在子類中失去同步機制。
7.判斷條件的設置
在同步機制中,如果同步代碼的執行需要滿足一定條件,那麼將判斷條件放在鎖內,保證當前獲取了鎖的線程在執行同步代碼時滿足執行條件。如果放在鎖外,有可能出現當前線程獲取了鎖以後不滿足執行條件的情況。
不存線上程安全問題的做法:
public void run() { System.out.println("obj=" + obj); while (true) { synchronized (obj) { if (obj.count > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "----當前數量=" + obj.count--); } else return; } } }
如果將判斷條件obj.count>0放在while語句中,可能出現某個線程進入while語句時count為1,滿足條件,進入,等待獲取對象鎖。當前持有對象鎖的線程執行完畢,count變為0,等待線程獲取對象鎖,在count=0的情況下執行同步塊,判斷條件失效。
八 死鎖
1.什麼是死鎖?
線程A需要多把鎖,線程B持有A缺少的鎖,缺少A持有的鎖,由於線程在獲取到全部的鎖之前不會釋放持有的鎖,這使得線程A與線程B陷入僵持,整個進程處於停滯狀態。
2.怎麼避免死鎖?
減少同步機制中鎖的數目,儘量避免同一把鎖出現在多處。
九 守護線程
1.用戶線程?
一般情況下創建的線程都是用戶線程,即該線程未被顯式設定為守護線程,未在守護線程內部創建。
2.主線程屬於用戶線程。
3.什麼是守護線程?
運行在後臺、為用戶線程提供服務的線程。
4.守護線程創建
用戶線程調用setDaemon(true)方法,或者在守護線程內部創建線程。
5.守護線程的作用
守護線程用於為用戶線程提供服務,如垃圾回收器。
6.JVM在所有的用戶線程執行完畢後終止,無論此時守護線程是否執行完畢。
7.守護線程運行在後臺,所有用戶線程結束後,自動結束。
十 wait與sleep方法對比
1.存在範圍
- wait方法是Object級的,即java中的任何一個對象都擁有該方法,像toString一樣。
- sleep方法只在Thread及其子類中存在。
2.作用
- sleep使當前線程休眠,釋放CPU時間片,不會釋放持有的鎖。
- wait用於線程間通信,由對象管理所有以該對象為鎖的全部線程。在同步代碼中由鎖對象調用,使當前線程釋放持有的對象鎖。
3.使用方法
- sleep方法是一個靜態方法,直接通過Thread調用,Thread.sleep。
- 用在同步代碼中,由鎖對象調用。
4.相關方法
- obj.notify():隨機喚醒對象監聽器上的一個線程,該線程進入就緒狀態,一旦獲得對象鎖與CPU時間片,從等待處接著執行,不是重新進入run方法或者同步代碼中。
- obj.notifyAll():喚醒對象監聽器上所有的等待線程,使它們全部進入就緒狀態。
十一 ThreadLocal
1.線程局部變數,為每一個線程提供一個變數的副本,使得各個線程相對獨立地操作變數,避免線程安全問題。
2.首先必須明確一點,ThreadLocal.get()獲取的變數副本必須手動傳入:
ThreadLocal.set(Object obj)
初次獲取時,判斷線程局部變數中是否保存有變數副本,如果沒有則手動傳入,在該線程中下次獲取的就是初次傳入的對象。
3.ThreadLocal的目的是保證在一個線程內部,一次創建,多次獲取。
4.基本原理:
將初次傳入的變數與線程綁定,線程不變,變數不變。
十二 GroboUtils多線程測試
1.JUnit測試不支持多線程,GroboUtils提供了對多線程測試的支持,使用時需要導入架包。
2.幾個比較重要的類:
- TestRunnable:實現了Runnable介面,run方法中運行的是runTest方法,runTest方法是一個抽象方法。
- MultiThreadedTestRunner:負責管理並開啟多個線程。
3.測試步驟
⑴繼承TestRunnable,實現其中的抽象方法runTest,將需要運行的代碼放入該方法中。通常為子類定義一個有參構造方法,方法形參為需要測試的線程,在runTest方法中調用測試線程的run方法,從而將將需要執行的代碼註入runTest方法中。
⑵創建測試線程數組,將需要測試的TestRunnable實現類傳入其中:
TestRunnable[] tr=new TestRunnable[len];
⑶根據測試線程數組創建線程管理與運行對象並開啟多線程:
MultiThreadedTestRunner mttr=new MultiThreadedTestRunner(tr); mttr.runTestRunnables();