瘋狂java講義:多執行緒(一)
第十六章 多執行緒(一)
並行:在同一時刻,有多條指令在多個處理器上同時執行
併發:同一時刻只有一個指令被處理器執行,但多個程序指令快速輪換執行,使得巨集觀好像多個指令在同時執行
作業系統可以同時執行多個任務,每個任務就是程序;程序可以同時執行多個任務,每個任務就是執行緒
執行緒的建立和啟動
1.繼承Thread類建立執行緒
步驟:1.定義Thread子類,重寫run(),run()即為執行緒執行體
2.建立Thread子類例項,即建立執行緒物件
3.呼叫執行緒物件的start()方法啟動執行緒
執行緒之間無法共享執行緒類的例項變數
2.實現Runnable藉口建立執行緒類
步驟:1.定義Runnable介面,重寫run()方法
2.建立Runnable例項,以此例項作為Thread的target來建立Thread物件,該Thread才是真正的執行緒物件
可用Lambda表示式建立例項
3.呼叫執行緒物件的start()方法來啟動執行緒
eg: new Thread(RunnableThread, RunnableName).start()
多執行緒共享實現執行緒類的例項變數
3.使用Callable和Future建立執行緒
步驟:1.建立Callable實現類,並實現call()方法
call()是隻執行體,且有返回值
可使用Lambda表示式建立Callable物件
2.使用FutureTask類包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的返回值
3.使用FutureTask物件作為Thread物件的target建立並啟動新執行緒
4.使用FutureTask物件的get()方法來獲得子執行緒執行結束後返回值
eg:
FutureTask<Integer> task = new FutureTask<Integer>(CallableThread);
new Thread(task, taskName).start()
call()方法可以有返回值
call()方法可以丟擲異常
Future介面定義如下方法;
boolbean cancel(boolbean mayInterrupIfRunning) 取消Future關聯的callable任務
get() 獲取執行緒返回值,或阻塞。直到拿到返回值
isCancelled() 如果任務正常完成前被取消,返回true
isDone() 任務完成返回true
4.建立執行緒的三種方式的對比
使用Runnable、Callable介面的優缺點:
優點:1.執行緒類只是實現Runnable介面和callable介面,還可以繼承其他類
2.多個執行緒可以共享一個target物件,非常適合多個相同執行緒處理同一份資源的情況下。從而可以將CPU、程式碼、和資料分開。
劣勢:程式設計複雜,如果需要訪問當前執行緒,則必須使用Thread.currentThread()方法
使用Thread類建立執行緒優缺點:
優點:編寫簡單,如果需要訪問當前執行緒直接使用This
缺點:已經繼承Thread,無法再繼承其他父類
執行緒的生命週期:
新建new - 就緒Runnable - 執行Running - 阻塞Blocked - 死亡Dead
1.new一個執行緒之後,是新建狀態
2.start()執行後,就緒狀態
3.如果就緒狀態的執行緒獲得CPU,開始執行run(),則是出於執行狀態
4.如下情況會進入阻塞狀態:
1.執行緒呼叫sleep()
2.呼叫了阻塞式IO
3.執行緒試圖獲得一個同步監視器,但是該同步監視器正在被其他執行緒佔用
4.執行緒在等待某個通知notify
5.程式呼叫了執行緒的suspend()方法將該執行緒掛起 / 可通過resume()恢復
被阻塞的執行緒阻塞結束後,會再次進入就緒狀態,再次等待CPU,然後進入執行狀態
yield()方法可使執行狀態轉為就緒狀態
5.執行緒會以一下三種方式結束,結束後處於死亡狀態
1.run()、call()方法執行完畢,執行緒正常結束
2.執行緒丟擲未捕獲異常和錯誤
3.直接呼叫stop()方法結束執行緒
可通過isAlive()方法,判斷執行緒是否死亡
Note:程式只能對新建狀態的執行緒執行start()方法。只能執行一次。
控制執行緒:
join執行緒:當程式呼叫其他執行緒的join()時,呼叫執行緒將會被阻塞,直到join()方法加入的join執行緒執行完畢,呼叫執行緒才會繼續執行。
後臺執行緒:在後臺執行,為其他執行緒提供服務。如果所有執行緒都死亡,後臺執行緒會自動死亡。
Thread物件的setDaemon(true)方法可將制定執行緒設定為後臺執行緒
isDaemon()方法用於判斷執行緒是否為後臺執行緒。
前臺執行緒建立的執行緒預設為前臺執行緒,後臺執行緒建立的執行緒預設為後臺執行緒。
要將某個執行緒設定為後臺執行緒,必須在該執行緒啟動前設定,setDaemon(true)必須在start()方法前呼叫
執行緒睡眠:sleep(),讓目前正在執行的執行緒暫停一段時間,執行緒進入阻塞狀態,時間到了之後,進入就緒狀態
sleep()和yield()的區別:1.sleep()方法執行後,執行緒阻塞,然後讓其他執行緒執行;yield()方法只會給優先順序相同,或者優先順序更高的執行緒執行機會。
2.sleep(),讓目前正在執行的執行緒暫停一段時間,執行緒進入阻塞狀態,時間到了之後,進入就緒狀態;yield()方法不會將執行緒進入阻塞狀態,直接進入就緒狀態。
3.sleep()方法宣告丟擲InterruptedException異常,所以sleep需要捕捉該異常,或者顯示宣告丟擲異常;而yield()沒有宣告丟擲任何異常
4.sleep()方法比yield()方法更具有移植性
設定執行緒優先順序:
每個執行緒優先順序預設與父執行緒優先順序相同。
main執行緒具有普遍優先順序,main建立的子執行緒具有普遍優先順序。
setPriority()設定並返回執行緒優先順序,引數整型,1~10; MAX_PRIORITY 10;MIN_PRIORITY 1; NORM_PRIORITY 5
執行緒同步:
同步程式碼塊:
synchronized(obj)
{
同步程式碼塊
}
程式執行行,必須先獲得對同步監視器的鎖定;任何時刻只有一個執行緒可以獲得對同步監視器的鎖定。
在加鎖期間其他執行緒無法修改該資源。
同步方法:
public/private/protect synchronized void mothodname()
{
方法體
}
synchronized不可以修飾static方法
無需顯示指定同步監視器,同步方法的同步監視器是this,也就是呼叫該方法的物件。
synchronized可以修飾程式碼塊和方法但是不可以修飾構造器,成員變數。
同步鎖:顯示定義同步鎖物件實現同步。
1.定義鎖物件:private final ReentrantLook lock = new ReentrantLock();
2.加鎖:lock.lock()
3.執行方法體
4.釋放鎖:lock.unlock()
Lock控制多個執行緒對共享資源進行訪問。通常所提供獨佔訪問,每次只能有一個執行緒對Lock物件加鎖,執行緒開始訪問共享資源前需要先獲得Lock物件。
ThreadLocal執行緒區域性變數
ThreadLocal:執行緒區域性變數,為每一個使用該變數的執行緒都提供一個變數值的副本,使得每一個執行緒都可以獨立的改變自己的副本,而不會與其他執行緒的副本產生衝突。
T get(); 返回此區域性變數在該執行緒中的副本值
void remove():刪除此區域性變數在該執行緒中的值
void set(T value):設定此區域性變數在該執行緒中的值
eg: private ThreadLocal<String> name = new ThreadLocal();
name.get();
name.set("nameA");
name.remove();
死鎖
當兩個執行緒互相等待釋放同步監視器時就會發生死鎖。
執行緒通訊
1.synchronized關鍵字
對於使用synchronized修飾的同步方法,該類預設例項(this)是同步監視器,可以直接呼叫這三個方法。
對於使用synchronized修飾的同步程式碼塊,synchronized後的物件是同步監視器,該物件呼叫這三個方法。
wait():導致當前執行緒等待,直到其他執行緒呼叫該同步監視器的notify()或者notifyAll()方法喚醒該執行緒
notify():喚醒此同步監視器上等待的單個執行緒
notifyAll():喚醒此同步監視器上等待的所有執行緒
2.lock()
await():類似wait()。導致當前執行緒等待,直到其他執行緒呼叫該Condition的signal()或者signalAll()方法喚醒該執行緒
signal():喚醒此Lock物件上等待的單個執行緒
signalAll():喚醒此Lock物件上等待的所有執行緒
使用阻塞佇列(BlockingQueue)控制執行緒通訊
BlockingQueue繼承自Queue介面
當生產者執行緒試圖向BlockingQueue放入元素,如果佇列已經滿了,則該執行緒被阻塞;當消費者執行緒試圖從BlockingQueue取出元素,如果佇列是空的,則該執行緒被阻塞。
put(E e):嘗試把E元素放入BlockingQueue,如果佇列已經滿了,則該執行緒被阻塞
take():從BlockingQueue取出元素,如果佇列是空的,則該執行緒被阻塞
有以下五個實現類:
ArrayBlockingQueue:基於陣列實現BlockingQueue
LinkedBlockingQueue:基於連結串列實現BlockingQueue
PriorityBlockingQueue:
SynchronousQueue:同步佇列。對該佇列的存取操作必須交替進行
DelayQueue:
執行緒組
ThreadGroup表示執行緒組,可以對一批執行緒分類處理
使用者建立的執行緒都屬於指定執行緒組,如果沒有顯示確定是哪個執行緒組,則屬於預設執行緒組。預設情況下,子執行緒與父執行緒屬於同一執行緒組。
一旦執行緒加入指定執行緒組,執行緒執行中不可以改變執行緒組,直到死亡
顯式指定執行緒組:
Thread(ThreadGroup group,Runnable target)
Thread(ThreadGroup group,Runnable target,String name) //指定執行緒名字
Thread(ThreadGroup group,String name)
ThreadGroup建立例項
ThreadGroup(String name)
ThreadGroup(ThreadGroup parent, String name)
int activeCount() //返回此執行緒中活動執行緒的數目
interrupt() //中斷該執行緒組所有執行緒
isDaemon() //判斷該執行緒是否是後臺執行緒組
setDaemon(boolean daemon) //把該執行緒設定為後臺執行緒組
setMaxPriority(int pri) //設定執行緒組的最高優先順序
執行緒池
返回ExecutorService物件,該物件代表一個執行緒池:
newCachedThreadPool()建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
newFixedThreadPool(int nThreads) 建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
newSingleThreadExecutor() 建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。
返回ScheduledExecutorService物件,該物件代表一個執行緒池:
newScheduledThreadPool(int corePoolSize) 建立一個定長執行緒池,支援定時及週期性任務執行。
newSingleThreadScheduledExecutor 建立只有一個執行緒的執行緒池,可以在指定延時後執行執行緒任務
ExecutorService newWorkStealingPool(int parallelism) 建立持有足夠執行緒的執行緒池來支援給定的並行級別,該方法還會使用多個佇列來減少競爭
ExecutorService newWorkStealingPool(int parallelism) 前一個簡化版本,例如4個cpu,則並行級別設定為4
ExecutorService代表儘快執行的執行緒池,只要有空閒執行緒就執行任務。
Future<?> submit(Runnable task):將一個Runnable物件提交給指定執行緒池,在有空閒執行緒時執行。因為run()方法沒有返回值,所有該方法返回null
<T>Future<T> submit(Runnable task, T result):將一個Runnable物件提交給指定執行緒池,在有空閒執行緒時執行。指定返回值,在執行結束後返回result
<T>Future<T> submit(Callable<T> task):將一個Callable物件提交給指定執行緒池,在有空閒執行緒時執行,返回Future
ScheduledExecutorService代表可在指定延遲後或者週期性執行執行緒任務的執行緒池。
ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit):指定Callable任務在delay延時後執行
ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit):指定command任務在delay延時後執行
ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit):指定command任務在delay延時後執行,以設定頻率重複執行
執行:initialDelay initialDelay+peroid initialDelay+2 * peroid ....
ScheduledFuture<?> scheduleAtFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit):建立並執行一個在給定初始延遲後首次啟用的定期操作,隨後一次執行結束下次開始前都存在給定延時
包裝執行緒不安全的集合:
使用Collections提供的類方法可以把執行緒不安全的集合包裝為執行緒安全
<T> Collection<T> synchronizedCollections(Collections<T> c): 返回指定集合對應的執行緒安全的集合
static <T> List<T> synchronizedList(List<T> list): 返回指定list物件對應的執行緒安全的list物件
static <K,V> Map<K,V> synchronizedMap(Map<K,V> map): 返回指定Map物件對應的執行緒安全的Map物件
static <T> Set<T> synchronizedSet(Set<T> set): 返回指定Set物件對應的執行緒安全的Set物件
static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m): 返回指定SortedMap物件對應的執行緒安全的SortedMap物件
static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s): 返回指定SortedSet物件對應的執行緒安全的SortedSet物件
eg:HashMap m = Collections.synchronizedMap(new HashMap()); //把一個普通的HashMap包裝為執行緒安全的HashMap物件
執行緒安全的集合類:
分為兩類:
Concurrent開頭的:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue、ConcurrentLinkedDeque
CopyOnWrite開頭的:CopyOnWriteArrayList、CopyOnWriteArraySet
Concurrent開頭的表示支援併發訪問的集合,可以支援多執行緒併發寫入訪問,並且都是安全的,但讀取操作不必鎖定;CopyOnWrite開頭的底層通過複製實現,當對它操作時,實際是對陣列副本進行操作,因此執行緒安全。CopyOnWrite由於寫入需要頻繁複制陣列,效能比較差,但是在讀操作時,不需加鎖,操作很快很安全,適合讀取操作遠遠多於寫入操作的情況,比如快取。