爲何使用多線程
- 單核環境下,I/O阻塞時可有效利用CPU資源
- 多核環境下,可以有效利用各個cpu資源。
如何創建線程
- 繼承Thread類,重寫run方法。
- 實現Runnable接口,實現run方法,new Thread(runnable)。
如何啓動一個線程
- 調用Thread對象的start()方法。
- 錯誤的方式:直接調用run()方法,只是在當前線程裏同步的運行了業務邏輯而已。
使用過程中面臨的問題
線程安全
多個線程在讀寫共享變量時,就可能會發生讀取非最新,寫入被覆蓋等情況。可以不使用共享變量來規避這種問題,如果一定要使用共享變量,那麼就需要額外做一些同步控制,才能保證邏輯正確。
共享變量
- 加鎖同步
- sychronized關鍵字
- 可以標記在方法上,針對方法調用進行同步。
- 針對實例方法,鎖定的就是實例對象。
- 針對靜態方法,鎖定的就是所在類的class對象。
- 可以包圍一塊代碼,進行塊內代碼進行同步。
- 需要藉助於一個對象,可以是this。也可以new Object()。
- 不要使用String/Integer/Long等簡單類型。主要是怕(字符串字面量/數值常量池)這些共享的變量,被不同的業務邏輯(其他人也這麼用)使用時,會發生鎖放大的情況。
- 可以標記在方法上,針對方法調用進行同步。
- Lock類
- 控制的粒度更細。
- 需要顯示的調用lock()和unlock(),unlock()必須放到finally中。
- 鎖定的就是lock和unlock之間的邏輯。
- 可以用tryLock實現超時放棄機制,超過一定時間獲取不到鎖,則放棄。
- 可以實現讀寫鎖,在讀多寫少的情況下,可以有效提升性能。
- 可以在構造時傳入ture,設置爲公平模式(fifo,不是完全公平,儘可能而已)。
- 可以通過lockInterupptly(),設置鎖可以被中斷,具體的中斷要通過持有鎖的線程調用interrupt()。
- 底層實現用到了cas。
- sychronized關鍵字
- 原子類
- 基於樂觀鎖的實現思想。
- 大多數採用cas方式實現。
- 基本類型對應的原子類比較常用:AtomicInteger/AtomicLong。
- 引用類型對應的原子類AtomicReference
- 只能保證引用地址的併發安全。
- 不能保證內部引用類型的屬性的併發安全。
- 同步集合
- Collections.synchronizedXXX(xxx)
不共享變量
- 利用不可變類、不可變屬性,變量不可變就不存在併發寫問題。
- 利用TreadLocal模式,爲了每個線程擁有各自獨立的變量。
死鎖
表現就是兩個線程互相持有對方等待獲取的鎖,如何避免呢?
- 同步塊儘可能的小(鎖粒度要細)。
- 儘量不要在一個同步塊內鎖定多個對象。
- 如果確實要鎖定多個對象,那麼確保每個線程都以相同的順序獲取共享資源。
線程調度
線程優先級
- 高優先級的線程更有可能先獲取CPU資源(不嚴格保證)
- 可以通過Thread對象的setPriority方法設置優先級
- 建議設置優先級只在三個常量值(1,5,10)中選擇
線程調度器
- 協作式:你情我願
- 搶佔式:弱肉強食(大部分虛擬機都採用這種方式)
讓出執行權
- 阻塞
- I/O阻塞(寫等待、讀等待)
- 不再佔用cpu資源
- 不會讓出持有的鎖。
- 鎖阻塞(等待獲取鎖)
- I/O阻塞(寫等待、讀等待)
- 放棄(謙讓)
- 調用Thread.yield()方法。
- 放棄(讓出)cpu的執行權。
- 不會讓出持有的鎖。
- 睡眠
- 調用Thread.sleep方法(可設置睡眠時間)。
- 不會讓出持有的鎖。
- 可以被中斷(調用某個Thread對象.interrupt()方法)。
- 避免在同步方法或同步塊內調用。(自己持有了鎖,自己不幹活還睡覺,會導致其他線程等待鎖時間過長)
- 連接(意思是,好吧,你先來執行,你完事,我再接着執行。)
- 調用某個Thread對象.join()方法(可設置超時時間)。
- 放棄當前線程持有的鎖。
- join可以被中斷。
- 等待
- 首先要通過synchronize方式獲取某個對象的鎖。
- 調用該對象的wait方法(可設置等待時間),既可讓當前線程進入等待狀態
- 放棄當前線程持有的鎖。
- 可以被中斷。
- 其他線程通過調用該對象的notify或者nodityAll方法,喚醒等待的線程。
- 喚醒後,只是說你不要睡了,起來幹活,但是想幹活,還要重新獲取鎖,獲取到了。才能繼續執行。
- 結束
- 線程run方法執行完畢,線程會被銷燬,其他線程可以接管CPU,自然就等於讓出了執行權。