Java中的多線程——學習小結

簡要:
1.什麼是進程與線程?
2.多線程
3.線程安全問題和解決方法
4.守護線程
5.線程狀態

1.進程與線程?

進程:

windows電腦中,打開任務管理器,可看到電腦中執行的每一個程序,這就是【進程】

線程:

如電腦管家是一個程序,但電腦可同時做病毒查殺,垃圾清理,深度加速等功能,每一個功能就是【線程】

注意:
(1)線程使用的是系統資源,該系統資源是操作系統分配給當前進程使用的。
(2)多個線程的情況下,同時【搶佔執行】會導致資源緊缺。類似於進程的搶佔過程。
(3)一個Java程序,最少有2個線程。main線程、JVM的GC機制,守護線程。
併發和並行
【併發】:兩個或兩個以上的事物在同一個時間段發生
【並行】:兩個或兩個以上的事物在同一個時刻發生。宏觀並行,微觀串行。
【高併發】:同時在一個時間段內,很多事情都發生了,這就是高併發。

2.多線程

2.1多線程的優缺點

優點:
	1.提升資源利用率
	2. 提高用戶體驗
缺點:
	1. 降低了其他線程的執行概率
	2. 用戶會感受到軟件的卡頓問題
	3. 增加的系統,資源壓力
	4. 多線程情況下的共享資源問題,線程衝突,線程安全問題

2.2 創建自定義線程類的兩種方式

class Thread類
	Java中的一個線程類
	Thread類是Runnable接口的實現類,同時提供了很多線程的操作使用的方法。
interface Runnable接口
	這裏規定了what will be run?
	裏面只有一個方法 run方法

【方式一】

自定義線程類,繼承Thread類,重寫run方法
創建自定義線程對象,直接調用start方法,開啓線程

【方式二】

自定義線程類,遵從Runnable接口
使用自定義遵從接口Runnable實現類對象,作爲Thread構造方法參數
藉助於Thread類對象和start方法,開啓線程

【推薦】

以上兩種方式,推薦使用方拾二,遵從Runnable接口來完成自定義線程,不影響正常的繼承邏輯,
並且可以使用匿名內部類來完成線程代碼塊的書寫

【代碼演示】

/*
 * 自定義線程類MyThread1繼承Thread類
 */
class MyThread1 extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println("繼承Thread類自定義線程類");
		}
	}
}

/*
 * 自定義線程類MyThread2遵從Runnable接口
 */
class MyThread2 implements Runnable {
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println("遵從Runnable接口實現自定義線程類");
		}
	}
}

public class Demo1 {
	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 100; i++) {
					System.out.println("匿名內部類方式創建對象,作爲線程執行代碼");
				}
			}
		}).start();
		// 創建一個繼承Thread類自定義線程類對象
		MyThread1 myThread1 = new MyThread1();
		// 這裏不是啓動線程,而且將run方法做出一個普通方法執行。
		// myThread1.run();
		myThread1.start();
		
		// 創建一個Thread類對象,使用遵從Runnable接口的實現類作爲構造方法參數
		Thread thread = new Thread(new MyThread2());
		// 藉助於Thread類內的start方法開啓線程
		thread.start();

		for (int i = 0; i < 100; i++) {
			System.out.println("main線程");
		}
	}
}

2.3自定義線程執行流程

執行流程圖:

2.4 Thread類需要了解的方法

2.4.1 Constructor
Thread();
	分配一個新的線程對象,無目標,無指定名字
Thread(Runnable target);
	創建一個新的線程對象,並且在創建線程對象的過程中,使用Runnable接口的
	實現類對象作爲執行的線程代碼塊目標
Thread(String name);
	創建一個新的線程,無指定目標,但是指定當前線程的名字是什麼
Thread(Runnable target, String name);
	創建一個線程的線程對象,使用Runnable接口實現類對象,作爲執行目標,並且指定name作爲線程名
2.4.2 Method
void setName(String name);
String getName();
	以上兩個是name屬性setter和getter方法
void setPriority(int Priority);
	設置線程的優先級,非一定執行要求,只是增加執行的概率
	優先級數值範圍 [1 - 10] 10最高 1最低 5默認
int getPriority();
	獲取線程優先級
void start();
	啓動線程對象

public static void sleep(int ms);
	當前方法是靜態方法,通過Thread類調用,要求是當前所在線程代碼塊對應的線程,進行休眠操作,休眠指定的毫秒數
public static Thread currentThread();
	當前方法是靜態方法,通過Thread類調用,獲取當前所處代碼塊對應的線程對象。

3. 線程安全問題和解決辦法

3.1共享資源使用問題

如:
出售< <我和我的祖國>>的電影票100張
出售方式:
淘票票、美團、貓眼
問題一:100張電影票用什麼保存?
【靜態成員變量】
問題二:資源衝突問題
在這裏插入圖片描述

3.2同步代碼塊

synchronized (/* 鎖對象 */) {
    
}

/*
特徵:
 	1. synchronized 小括號裏面的對象是鎖對象,並且要求如果是多線程的情況下,鎖對象必須是同一個對象。
 	2. synchronized 大括號中的代碼塊就是需要進行同步的代碼,或者說是加鎖的代碼,大括號裏面的內容,有且只允許一個線程進入。
 	3. 同步代碼塊越短越好,在保證安全的情況下,提高性能
 
問題:
	1. 目前鎖對象感覺很隨意,存在一定的隱患
	2. 代碼層級關係很複雜,看着有點麻煩
*/

3.3同步方法

synchronized 作爲關鍵字來修飾方法,修飾的方法就是對應的同步方法,有且只允許一個線程進入,到底是誰來完成的加鎖操作?

1. 靜態成員方法
鎖對象,是當前類對應的字節碼文件.class 類名.class
2. 非靜態成員方法
鎖對象就是當前類對象 this

選擇同步方法是否使用static修飾問題?

1. 如果非static修飾,要保證執行的線程對象有且只有一個,因爲鎖對象就是當前線程對象	
2. 如果是static修飾,鎖對象具有唯一性,多個線程使用的鎖是同一個鎖。

3.4Lock鎖

Java提供了一個對於線程安全問題,加鎖操作相對於同步代碼塊和同步方法更加廣泛的一種操作方式。

1. 對象化操作。
	創建Lock構造方法
		Lock lock = new ReentrantLock();
2. 方法化操作。
	開鎖:
		unlock();
	加鎖:
		lock();

3.5三種加鎖方式的總結

1. 一鎖一線程,一鎖多線程問題。
使用對應的鎖操作對應的線程,考慮靜態和非靜態問題。	同步方法和Lock鎖使用。
靜態是一鎖多目標,非靜態是一鎖一目標

2. 涉及到同步問題時,要考慮好鎖對象的選擇問題
同步代碼塊,同步方法,Lock對象。

4.守護線程

守護線程,也稱之爲後臺線程,如果當前主線程崩潰,守護線程也就崩潰。

守護線程一般用於:
	1. 自動下載
	2. 操作日誌
	3. 操作監控

守護方法是通過線程對象
setDeamon(boolean flag);
true爲守護線程
false缺省屬性,正常線程

5. 線程狀態

5.1 六種線程狀態

線程如果按照java.lang.Thread.State枚舉方式來考慮,一共提供了6中狀態


狀態 導致狀態的條件
NEW(新建) 線程剛剛被創建,沒有啓動,沒有調用start方法
RUNNABLE(可運行) 線程已經可以在JVM中運行,但是是否運行不確定,看當前線程是否擁有CPU執行權
BLOCKED(鎖阻塞) 當前線程進入一個同步代碼需要獲取對應的鎖對象,但是發現當前鎖對象被其他線程持有,當前線程會進入一個BLOCKED。如果佔用鎖對象的線程打開鎖對象,當前線程持有對應鎖對象,進入Runnable狀態
WAITING(無限等待) 通過一個wait方法線程進入一個無限等待狀態,這裏需要另外一個線程進行喚醒操作。進入無限等待狀態的線程是無法自己回到Runnable狀態,需要其他線程通過notify或者notifyAll方法進行喚醒操作
TIMED_WAITING(計時等待) 當前線程的確是等待狀態,但是會在一定時間之後自動回到Runnable狀態,例如Thread.sleep()或者是Object類內的wait(int ms);
TERMINATED(被終止) 因爲Run方法運行結束正常退出線程,或者說在運行的過程中因爲出現異常導致當前線程崩潰

5.2TIMED_WAITING(計時等待)

Thread.sleep(int ms);
	在對應線程代碼塊中,當前線程休眠指定的時間。
Object類內  wait(int ms);
	讓當前線程進入一個計時等待狀態
		1. 規定的時間及時完畢,線程回到可運行狀態
		2. 在等待時間內,通過其他線程被notify或notifyAll喚醒
Sleep方法
	1. 調用之後休眠指定時間
	2. sleep方法必須執行在run方法內,纔可以休眠線程
	3. sleep不會打卡當前線程佔用的鎖對象。

在這裏插入圖片描述

5.3BLOCKED(鎖阻塞)

線程中有鎖存在,線程需要進入帶有鎖操作的同步代碼,如果鎖對象被別人持有,只能在鎖外等待

鎖阻塞狀態的線程是否能夠搶到鎖對象有很多因素
	1. 優先級問題,非決定因素
	2. CPU執行概率問題。

後期高併發一定會存在多線程操作鎖對象問題,秒殺,搶購...
用隊列方式來處理

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章