Java 多線程
多線程
進程:程序的基本執(zhí)行實體
線程:操作系統(tǒng)能夠進行運算調(diào)度的最小單位.它被包含在進程之中,是進程中的實際運作單位 簡單理解:應用軟件中相互獨立,可以同時運行的功能
有了多線程,我們就可以讓程序同時做多件事情,提高效率
并發(fā):同一時刻有,多個指令在單個CPU上交替執(zhí)行
并行:在同一時刻,多個指令在多個CPU上同時執(zhí)行
多線程的實現(xiàn)方式
繼承Thread類的方式
自己定義一個類繼承Thread
重寫run方法
創(chuàng)建子類的對象,并啟動線程
實現(xiàn)Runnable接口的方式
自己定義一個類實現(xiàn)Runnable接口
重寫里面的run方法
創(chuàng)建自己的類的對象
創(chuàng)建一個Thread類的對象并開啟線程
利用Callable接口和Future接口
前兩種方法都沒有返回值,如果想獲取多線程運行的結果:
創(chuàng)建一個類實現(xiàn)Callable接口
重寫call(是有返回值的,表示多線程運行的結果)
創(chuàng)建MyCallable的對象(表示多線程要執(zhí)行的任務)
創(chuàng)建FutureTask的對象(作用為管理多線程運行的結果)
創(chuàng)建Thread類的對象,并啟動(表示線程)
三種方式對比

常見的成員方法

細節(jié):
如果我們沒有給線程設置名字,其默認名字為Thread-X(序號從0開始)
如果要給線程設置名字,可以用set方法進行設置,也可以用構造方法
當JVM虛擬機啟動時候,會自動地啟動多條線程,其中有一條線程就叫做main線程,作用就是去調(diào)用main方法,并執(zhí)行里面的代碼.
sleep方法哪條線程執(zhí)行到了,哪條線程就會在這里停留對應的時間,時間到了之后就會自動醒來,繼續(xù)執(zhí)行下面的其他代碼
Java采用搶占式調(diào)度的方法,也就是隨機
最小優(yōu)先級為1,最大為10,默認為5,優(yōu)先級越高搶到CPU的概率越高
守護線程: 當其他非守護線程執(zhí)行完畢后,守護線程會陸續(xù)結束 應用場景:如QQ聊天窗口與傳文件,如果聊天窗口關閉了傳文件就沒必要了,所以可以把傳文件設為守護線程
yield方法可以讓線程分配盡可能均勻一點,較少使用
join方法表示將該線程插到當前線程之前,該線程結束后才會繼續(xù)執(zhí)行當先線程
線程的生命周期
創(chuàng)建線程對象:新建 調(diào)用start()后:有執(zhí)行資格,沒有執(zhí)行權,需要不停地搶CPU:就緒 搶到CPU的執(zhí)行權:執(zhí)行資格,沒有執(zhí)行權:運行(可能又被其他線程搶走CPU的執(zhí)行權回到上一個狀態(tài) run()執(zhí)行完畢:線程死亡,變成垃圾:死亡
sleep()或其他阻塞式方法:沒有執(zhí)行資格沒有執(zhí)行權,等到時間到進入就緒狀態(tài)爭搶CPU
所以:sleep方法睡眠時間到后不會立即執(zhí)行下面的代碼,只有搶到CPU后才能執(zhí)行
線程的安全問題
需求:100張票3個窗口售票
上述代碼運行的結果有問題:
會出現(xiàn)同一張票賣給多個人
會出現(xiàn)超出100張票,如101,102
原因:線程執(zhí)行時有隨機性,某個線程執(zhí)行到一半就會被其他線程搶走,加入剛好在ticket++執(zhí)行完被搶走,別的線程執(zhí)行完ticket++再回到原線程時,sout打印的ticket就變了
同步代碼塊
把操作共享數(shù)據(jù)的代碼鎖起來
特點1:鎖默認打開,有一個線程進去了,鎖自動關閉
特點2:里面代碼全部執(zhí)行完畢,線程出來,鎖自動打開
同步方法
就是把synchronized關鍵字加到方法上
格式:
特點1:同步方法是鎖住方法里面所有的代碼
特點2:鎖對象不能自己制定
非靜態(tài):this
靜態(tài):當前類的字節(jié)碼文件對象
StringBuilder與StringBuffer:后者每個方法都加了synchronized,是線程安全的
Lock鎖
雖然我們可以理解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪里加上了鎖,在哪里釋放了鎖,為了更清晰地表達如何枷鎖和釋放鎖,JDK5以后提供了一個新的鎖對象Lock
Lock實現(xiàn)提供比使用synchronized方法和語句可以獲得更廣泛的鎖定操作 Lock中提供了獲得鎖和釋放鎖的方法 void lock()獲得鎖 void unlock()釋放鎖
Lock是接口不能直接實例化,這里采用它的實現(xiàn)類ReentrantLock來實例化 ReentrantLock的構造方法 ReentrantLock()創(chuàng)建一個ReentrantLock的實例
上述代碼有問題,在19:break;行,假設線程1拿到了CPU的執(zhí)行權,此時線程1調(diào)出了循環(huán),沒有執(zhí)行22:lock.unlock();,此時程序不會結束,鎖沒有關上
正確寫法:使用try{}catch(){}finally{}
死鎖
死鎖是一個錯誤,我們應該避免發(fā)生
千萬不要讓兩個鎖嵌套起來
生產(chǎn)者和消費者(等待喚醒機制)
生產(chǎn)者和消費者是一個十分經(jīng)典的多線程協(xié)作的模式
生產(chǎn)者:生產(chǎn)數(shù)據(jù) 消費者:消費數(shù)據(jù)
常見方法:

阻塞隊列
繼承結構
接口:Iterable,Collection,Queue,BlockingQueue 實現(xiàn)類:
ArrayBlockingQueue(底層是數(shù)組,有界)
LinkedBlockingQueue(底層是鏈表,無界但不是真正的無界,最大值為int的最大值)
線程的六種狀態(tài)
新建狀態(tài)(new)全部代碼運行完畢
線程池
核心原理
創(chuàng)建一個池子,池子中是空的
提交任務時,池子會創(chuàng)建新的線程對象,任務執(zhí)行完畢,線程歸還給池子,下回再次提交任務時,不需要創(chuàng)建新的線程,直接復用已有的線程即可
但是如果提交任務時,池子中沒有空閑線程,也無法創(chuàng)建新的線程,任務就會排隊等待
代碼實現(xiàn):
創(chuàng)建線程池
提交任務
所有任務全部執(zhí)行完畢,關閉線程池
Executors:線程池的工具類,通過調(diào)用方法返回不同類型的線程池對象

自定義線程池

線程池多大合適?
CPU密集型運算:最大并行數(shù)+1 +1的目的是當前面線程出問題時可以頂上去
IO密集型運算:最大并行數(shù)$\times$期望CPU利用率$\times\frac{總時間(CPU計算時間+等待時間)}{CPU計算時間}$
4核8線程最大并行數(shù)為8
多線程的額外擴展
見阿瑋老師的多線程(額外擴展).md,含樂觀鎖,悲觀鎖,volatile關鍵字,原子性,一些并發(fā)工具類