1. 多線程
1.1 多線程的優缺點
優點
- 提升資源利用率
- 提高用戶體驗
缺點:
- 降低了其他線程的執行概率
- 用戶會感受到軟件的卡頓問題
- 增加的系統,資源壓力
- 多線程情況下的共享資源問題,線程衝突,線程安全問題
1.2 創建自定義線程類的兩種方式
class Thread類
Java中的一個線程類
Thread類是Runnable接口的實現類,同時提供了很多線程的操作使用的方法。
interface Runnable接口
這裏規定了what will be run?
裏面只有一個方法 run方法
方式一:
自定義線程類,繼承Thread類,重寫run方法
創建自定義線程對象,直接調用start方法,開啓線程
方式二:
自定義線程類,遵從Runnable接口
使用自定義遵從接口Runnable實現類對象,作爲Thread構造方法參數
藉助於Thread類對象和start方法,開啓線程
【推薦】
以上兩種方式,推薦使用方拾二,遵從Runnable接口來完成自定義線程,不影響正常的繼承邏輯,並且可以使用匿名內部類來完成線程代碼塊的書寫
package com.qfedu.a_thread;
/*
* 自定義線程類MyThread1繼承Thread類
*/
class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; 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線程");
}
}
}
1.3 自定義線程執行流程簡述
1.4 Thread類需要了解的方法
構造方法 Constructor
Thread();
分配一個新的線程對象,無目標,無指定名字
Thread(Runnable target);
創建一個新的線程對象,並且在創建線程對象的過程中,使用Runnable接口的實現類
對象作爲執行的線程代碼塊目標
Thread(String name);
創建一個新的線程,無指定目標,但是指定當前線程的名字是什麼
Thread(Runnable target, String name);
創建一個線程的線程對象,使用Runnable接口實現類對象,作爲執行目標,並且指定
name作爲線程名
成員方法:
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類調用,獲取當前所處代碼塊對應的線程對象。
2. 線程安全問題和解決方案
2.1 線程安全問題–共享資源能使用問題
<<湄公河行動>>
100張票
淘票票CGV 美團 貓眼
三個銷售渠道,100張票是一個共享資源!!!
三個銷售渠道,可以認爲是三個銷售線程!!!
問題一:
100張票共享資源問題,選什麼來保存
局部變量:
在方法內,如果run方法執行,存在,run方法當前執行完畢,銷燬。
每一個線程對象中都有run方法,無法滿足共享問題
成員變量:
每一個線程對象中,都有一個對應的成員變量,非共享資源。
靜態成員變量:
屬於類變量,所有的當前類對象,使用的靜態成員變量都是一個,而且一處修改,處處
受影響。【共享資源】
問題二:
資源衝突問題
2.2 同步代碼塊
synchronized (/* 鎖對象 */) {
}
/*
特徵:
1. synchronized 小括號裏面的對象是鎖對象,並且要求如果是多線程的情況下,鎖對象必須是同一個對象。
2. synchronized 大括號中的代碼塊就是需要進行同步的代碼,或者說是加鎖的代碼,大括號裏面的內容,有且只允許一個線程進入。
3. 同步代碼塊越短越好,在保證安全的情況下,提高性能
問題:
1. 目前鎖對象感覺很隨意,存在一定的隱患
2. 代碼層級關係很複雜,看着有點麻煩
*/
2.3 同步方法
synchronized 作爲關鍵字來修飾方法,修飾的方法就是對應的同步方
有且只允許一個線程進入,到底是誰來完成的加鎖操作?
1. 靜態成員方法
鎖對象,是當前類對應的字節碼文件.class 類名.class
2. 非靜態成員方法
鎖對象就是當前類對象 this
選擇同步方法是否使用static修飾問題
1. 如果非static修飾,要保證執行的線程對象有且只有一個,因爲鎖對象就是當前線程對
象
2. 如果是static修飾,鎖對象具有唯一性,多個線程使用的鎖是同一個鎖。
2.4 Lock鎖
Java提供了一個對於線程安全問題,加鎖操作相對於同步代碼塊和同步方法更加廣泛的一種操作方式。
1. 對象化操作。
創建Lock構造方法
Lock lock = new ReentrantLock();
2. 方法化操作。
開鎖:
unlock();
加鎖:
lock();
2.5 三種加鎖方式的總結
-
一鎖一線程,一鎖多線程問題。
使用對應的鎖操作對應的線程,考慮靜態和非靜態問題。
同步方法和Lock鎖使用。
靜態是一鎖多目標,非靜態是一鎖一目標 -
涉及到同步問題時,要考慮好鎖對象的選擇問題
同步代碼塊,同步方法,Lock對象。 -
守護線程
守護線程,也稱之爲後臺線程,如果當前主線程GG思密達,守護線程也就GG思密達。
守護線程一般用於:
1. 自動下載
2. 操作日誌
3. 操作監控方法是通過線程對象
setDeamon(boolean flag);
true爲守護線程
false缺省屬性,正常線程 -
線程狀態
4.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方法運行結束正常退出線程,或者說在運行的過程中因爲出現異常導致當前線程GG思密達 |
4.2 TIMED_WAITING(計時等待)
Thread.sleep(int ms);
在對應線程代碼塊中,當前線程休眠指定的時間。
Object類內 wait(int ms);
讓當前線程進入一個計時等待狀態
- 規定的時間及時完畢,線程回到可運行狀態
- 在等待時間內,通過其他線程被notify或者notifyAll喚醒Sleep方法
- 調用之後休眠指定時間
- sleep方法必須執行在run方法內,纔可以休眠線程
- sleep不會打卡當前線程佔用的鎖對象。
4.3 BLOCKED(鎖阻塞)
線程中有鎖存在,線程需要進入帶有鎖操作的同步代碼,如果鎖對象被別人持有,只能在鎖外等待
鎖阻塞狀態的線程是否能夠搶到鎖對象有很多因素
- 優先級問題,非決定因素
- CPU執行概率問題。