多線程相關知識
-
-
- 目錄
-
- 前言
- 一. 程序與線程的簡單了解
- 二. 線程排程
- 三. 建立線程的方式
- 四. 用Runnable還是Thread?
- 五. start()方法和run()方法的差別
- 六. Thread的實作涉及到哪種設計模式
- 七. 線程狀态
- 八. 線程相關方法
-
-
- 8.1 相關方法
- 8.2 例子
-
- 九. 線程安全與線程同步
- 十. 線程同步的方法
-
-
- 10.1 同步代碼塊
- 10.2 同步方法
- 10.3 Lock鎖
- 10.3 synchronized與Lock鎖對比
-
- 十一. 死鎖
- 十二. 線程通信
-
目錄
前言
本文隻是涉及到多線程的基本知識,未對線程池,并發做研究。
相關參部落格和視訊:
https://www.jianshu.com/p/512ab59ee584
https://www.cnblogs.com/hongmoshui/p/11109982.html
https://www.cnblogs.com/yangyongjie/p/11024712.html
https://blog.csdn.net/qq_36711757/article/details/82384356
https://www.bilibili.com/video/BV1V4411p7EF?from=search&seid=14826241534626431382
一. 程序與線程的簡單了解
一個程式就是一個程序,系統運作一個程式即使一個程序從建立,運作到消亡的過程。而一個程式中的多個任務則被稱為線程。
程序表示資源配置設定的基本機關,線程是程序執行運算的最小機關,也是排程運作的基本機關。
二. 線程排程
分時排程:所有線程輪流使用CPU的使用權,平均配置設定每個線程占用CPU的時間。
搶占式排程:優先級高的線程先使用CPU,如果優先級相同則随機選擇一個。
三. 建立線程的方式
繼承Thread類建立線程類
通過Runnable接口建立線程類
通過Callable和Future建立線程(不常用,少見)。
一般就是通過繼承Thread類或者調用Runnable接口來重寫run()方法實作線程,調用start()方法啟動線程。
四. 用Runnable還是Thread?
一般建議使用Runnable接口,因為java是單繼承的,實作Runnable接口可以避免這個單繼承的局限性
五. start()方法和run()方法的差別
這也是面試常見問題,涉及到對java線程模型的了解程度。start()方法被用來啟動新建立的線程,而且start()内部調用了run()方法,這和直接調用run()方法的效果不一樣。當你調用run()方法時,隻會在原來的線程中調用,沒有開啟新的線程,隻有shart()方法才會啟動新線程。
六. Thread的實作涉及到哪種設計模式
源代碼中Thread的實作是靜态代理。靜态代理就是比如,你結婚,你會和新娘宣讀誓言,但是除了這,還有布置現場,主持流程這些都是由婚慶公司來做,婚慶公司就是代理,來幫你處理結婚的事情。
而線程也是類似的,比如我們自己寫個搶票的線程,搶票這個類會實作Runnable接口,同時Thread類也是實作Runnable接口的,然後回調用Thread.start()來幫忙搶票,這個Thread就是代理,相當于婚慶公司。具體了解可以在以後寫線程代碼時細細體會。
七. 線程狀态
1,New初始狀态:線程對象被建立後,就進入了建立狀。例如,Thread thread=new Thread()。
2,Runnable就緒狀态:線程對象被建立後,該對象的start()方法被調用了,該線程啟動,處于就緒狀态,随時可被CPU排程執行。并不是調用start()方法後馬上線程就運作了,要等cpu來調用了,才會開始運作。
3,Running運作狀态:先後擷取CPU權限進行執行,需要注意的是,線程隻能從就緒狀态進入到與性能狀态。
4,阻塞狀态:阻塞狀态時線程因為某種原因放棄CPU使用權,暫時停止運作。直到線程進入就緒狀态,才有機會轉到阻塞狀态。阻塞的情況分三種:
1) 等待阻塞—通過調用線程wait()方法,讓線程等待某工作的完成。
2)同步阻塞—線程在擷取synchronized同步鎖失敗(因為鎖被其他線程所占用),它會進入阻塞狀态。
3)其他阻塞—通過調用線程sleep()或者join()或發出了I/O請求時,線程會進入阻塞狀态。當sleep()狀态逾時,join()等待線程中止或者逾時、或者I/O處理完畢時,線程重新轉入就緒狀态。
5,Dead死亡狀态:線程執行完了或者因異常退出了run()方法,該線程結束生命周期。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TP31UNrpmTwUkaNBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL4YTOyQjNxIjM0IjNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
八. 線程相關方法
8.1 相關方法
sleep()方法:在指定的毫秒數内讓目前正在執行的線程休眠。線程休眠不會釋放鎖。抱着鎖睡覺
join()方法:簡單了解就是插隊,vip,先完成這個線程,再執行其他線程,其他線程阻塞。
setPriority:設定線程優先級,優先級高的,被cpu調用的可能性增大(隻是可能性大)。
yield()方法:線程禮讓,讓當先正在執行的線程暫停,但不阻塞,将線程從運作狀态轉為就緒狀态,讓cpu重新調用。禮讓也是不一定成功的,看cpu心情。
線程停止方法:不推薦使用JDK提供的stop()、destory()方法,推薦讓線程自己停止下來。一般使用一個标志位來幫忙中止線程(while(flag)…)。
wait()方法,notify()方法:wait()方法的作用是讓需要鎖的線程等待,直到其他線程調用notify()或着notifyAll()喚醒方法。與sleep()方法的不同是,sleep()方法睡眠時,保持對象鎖,仍然占有該鎖,而wait()睡眠時,釋放對象鎖。
8.2 例子
/*
經典面試題:建立三個線程,A線程列印10次A,B線程列印10次B,C線程列印10次C,要求線程同時運作,交替列印10次ABC。
這個問題用Object的wait(),notify(),sleep()就可以很友善的解決
*/
class Thread1 implements Runnable {
private String name;
private Object prev;
private Object self;
private Thread1(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (prev) {
synchronized (self) {
System.out.print(name);
count--;
self.notify();
}
try {
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
Thread1 pa = new Thread1("A", c, a);
Thread1 pb = new Thread1("B", a, b);
Thread1 pc = new Thread1("C", b, c);
new Thread(pa).start();
Thread.sleep(100); //確定按順序A、B、C執行
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
}
}
/*結果:ABCABCABCABCABCABCABCABCABCABC*/
九. 線程安全與線程同步
當我們使用多個線程通路同一資源的時候,且多個線程對資源有寫的操作,那麼就容易出現線程安全問題。在Java中為解決線程提供了同步機制。
線程安全的核心理念是“要麼隻讀,要麼加鎖”
計算機的線程同步,是指線程之間按照某種機制協調先後執行次序,即當有一個線程再對記憶體操作時,其他線程都不可以對這個記憶體操作,一直等待直到該線程完成操作,其他線程才能對該記憶體進行操作。
實作線程同步的方式有很多,比如同步方法,鎖,阻塞隊列等。
十. 線程同步的方法
10.1 同步代碼塊
synchronized(['sɪŋkrənaɪzd] 同步)關鍵字可以用于方法中的某個區塊,表示隻對這個區塊的資源實行互斥通路。
synchronized(Obj){
需要同步操作的代碼
}
//這個Obj可以是任何對象,但是推薦共享資源作為Obj
可以了解為給對象上了個鎖。多個線程對象,要使用同一把鎖,任何時候,最多允許一個線程擁有同步鎖,誰拿到鎖誰就進入代碼塊,其他的線程隻能在外等待。
在多線稱競争情況下,加鎖,釋放鎖會導緻比較多的上下文切換和排程示範等問題,性能下降。
10.2 同步方法
使用synchronized修飾的方法就叫做同步方法,保證A線程在執行的時候,其他線程隻能在外面等着。
方法裡面又需要修改的内容才需要鎖,鎖的太多也容易浪費資源。
10.3 Lock鎖
JDK5.0後,提供了更加強大的線程同步機制——通過顯式定義同步鎖對象來實作同步。同步鎖使用Lock對象充當。
10.3 synchronized與Lock鎖對比
Lock是顯式鎖,需要手動的開啟和關閉,synchronized是隐式鎖,出了作用域就自動釋放。
Lock隻有代碼塊鎖,synchronized有代碼塊和方法鎖。
使用Lock鎖,JVM将花費較少的時間來排程線程,性能更好,并且具有更好的擴充性。
優先使用順序:Lock> 同步代碼塊 >同步方法。
十一. 死鎖
多個線程同時各自占有一些資源,并且互相等待着其他線程占有的資源才能允許,而導緻兩個或者多個線程都在等待着對方釋放資源,都停止執行的情況。
那麼如何避免死鎖呢:死鎖的産生有四個必要條件:互斥條件,請求與保持條件,不剝奪條件,循環等待條件。隻要破環任何一個就可以。比如我們可以指定擷取鎖的順序。
十二. 線程通信
具體的沒搞太明白,涉及到了線程池,生産者消費者模式(這不屬于設計模式的一種,隻是一種問題類型的描述)