一、Java 執行緒實作/創建方式
注意:
? 新建的執行緒不會自動開始運行,必須通過start( )方法啟動
? 不能直接呼叫run()來啟動執行緒,這樣run()將作為一個普通方法立即執行,執行完畢前其他執行緒無法并發執行? Java程式啟動時,會立刻創建主執行緒,main就是在這個執行緒上運行,當不再產生新執行緒時,程式是單執行緒的
1.1 繼承Thread 類
Thread 類本質上是實作了 Runnable 介面的一個實體,代表一個執行緒的實體,啟動執行緒的唯一方法就是通過 Thread 類的 start()實體方法,start()方法是一個 native 方法,它將啟動一個新執行緒,并執行 run()方法,
? 優勢:撰寫簡單
? 劣勢:無法繼承其它父類1.1.1 創建:繼承Thread+重寫run
1.1.2 啟動:創建子類物件+呼叫start
public class StartThread extends Thread{
//執行緒入口點
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println("listen music");
}
}
public static void main(String[] args) {
//創建子類物件
StartThread st=new StartThread();
//呼叫start方法
st.start();//開啟新執行緒交于cpu決定執行順序
for(int i=0;i<10;i++) {
System.out.println("coding");
}
}
}
1.2 實作runnable介面
如果自己的類已經 extends 另一個類,就無法直接 extends Thread,此時,可以實作一個Runnable 介面,
? 優勢:可以繼承其它類,多執行緒可共享同一個Runnable物件
? 劣勢:編程方式稍微復雜,如果需要訪問當前執行緒,需要呼叫Thread.currentThread()方法1.2.1 創建:實作runnable介面+重寫run
1.2.2 啟動:創建實作類物件+Thread類物件+呼叫start
public class StartRun implements Runnable{
//執行緒入口點
@Override
public void run() {
for(int i=0;i<10;i++) {
System.out.println("listen music");
}
}
public static void main(String[] args) {
//創建實作類物件
StartRun st=new StartRun();
//創建代理類物件
//啟動 MyThread,需要首先實體化一個 Thread,并傳入自己的 MyThread 實體:
Thread t=new Thread(st);
//事實上,當傳入一個 Runnable target 引數給 Thread 后,Thread 的 run()方法就會呼叫target.run()
//呼叫start方法
t.start();//開啟新執行緒交于cpu決定執行順序
//匿名法
// new Thread(new StartRun()).start();
for(int i=0;i<10;i++) {
System.out.println("coding");
}
}
}
1.3 實作Callable介面
1.3.1 創建:實作callable介面+重寫call
1.3.2 啟動:創建Callable實作類的實作,使用FutureTask類包裝Callable物件,該FutureTask物件封裝了Callable物件的Call方法的回傳值
1.3.3 使用FutureTask物件作為Thread物件的target創建并啟動執行緒
1.3.4 呼叫FutureTask物件的get()來獲取子執行緒執行結束的回傳值
有回傳值的任務必須實作 Callable 介面,類似的,無回傳值的任務必須 Runnable 介面,執行Callable 任務后,可以獲取一個 Future 的物件,在該物件上呼叫 get 就可以獲取到 Callable 任務回傳的 Object 了,再結合執行緒池介面 ExecutorService 就可以實作傳說中有回傳結果的多執行緒了,
? 與實行Runnable相比, Callable功能更強大些
? 方法不同
? 可以有回傳值,支持泛型的回傳值
? 可以拋出例外? 需要借助FutureTask,比如獲取回傳結果
Future介面
? 可以對具體Runnable、Callable任務的執行結果進行取消、查詢是否完成、獲取結果等,? FutrueTask是Futrue介面的唯一的實作類
? FutureTask 同時實作了Runnable, Future介面,它既可以作為Runnable被執行緒執行,又可以作為Future得到Callable的回傳值 簡單實作Callable介面public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 創建MyCallable物件
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask來包裝MyCallable物件
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread thread = new Thread(ft); //FutureTask物件作為Thread物件的target創建新的執行緒
thread.start(); //執行緒進入到就緒狀態
}
}
System.out.println("主執行緒for回圈執行完畢..");
try {
int sum = ft.get(); //取得新創建的新執行緒中的call()方法回傳的結果
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = 0;
// 與run()方法不同的是,call()方法具有回傳值
@Override
public Integer call() {
int sum = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}
ExecutorService、Callable<Class>、Future 有回傳值執行緒
//創建一個執行緒池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
//創建多個有回傳值的任務
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
//執行任務并獲取 Future 物件
Future f = pool.submit(c);
list.add(f);
}
//關閉執行緒池
pool.shutdown();
//獲取所有并發任務的運行結果
for (Future f : list) {
//從 Future 物件上獲取任務的回傳值,并輸出到控制臺
System.out.println("res:" + f.get().toString());
}
1.4 基于執行緒池的方式
執行緒和資料庫連接這些資源都是非常寶貴的資源,那么每次需要的時候創建,不需要的時候銷毀,是非常浪費資源的,那么我們就可以使用快取的策略,也就是使用執行緒池,
// 創建執行緒池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
while(true) {
threadPool.execute(new Runnable() { // 提交多個執行緒任務,并執行
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running ..");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
二、四種執行緒池
執行緒組
? 執行緒組表示一個執行緒的集合,
? 執行緒組也可以包含其他執行緒組,執行緒組構成一棵樹,在樹中,除了初始執行緒組外,每個執行緒組都有一個父執行緒組,? 頂級執行緒組名system,執行緒的默認執行緒組名稱是main
? 在創建之初,執行緒被限制到一個組里,而且不能改變到一個不同的組執行緒組的作用
? 統一管理:便于對一組執行緒進行批量管理執行緒或執行緒組物件? 安全隔離:允許執行緒訪問有關自己的執行緒組的資訊,但是不允許它訪問有關其執行緒組的父執行緒組或其他任何執行緒組的資訊
? 查看ThreadGroup、Thread構造方法代碼,觀察默認執行緒組的情況執行緒池(JDK1.5起,提供了內置執行緒池)
? 創建和銷毀物件是非常耗費時間的? 創建物件:需要分配記憶體等資源
? 銷毀物件:雖然不需要程式員操心,但是垃圾回收器會在后臺一直跟蹤并銷毀? 對于經常創建和銷毀、使用量特別大的資源,比如并發情況下的執行緒,對性能影響很大,
? 思路:創建好多個執行緒,放入執行緒池中,使用時直接獲取參考,不使用時放回池中,可以避免頻繁創建銷毀、實作重復利用 執行緒池作用:
? 提高回應速度(減少了創建新執行緒的時間)
? 降低資源消耗(重復利用執行緒池中執行緒,不需要每次都創建)
? 提高執行緒的可管理性:避免執行緒無限制創建、從而銷耗系統資源,降低系統穩定性,甚至記憶體溢位或者CPU耗盡執行緒池應用場合
? 需要大量執行緒,并且完成任務的時間端? 對性能要求苛刻
? 接受突發性的大量請求
執行緒池的組成一般的執行緒池主要分為以下 4 個組成部分:
1. 執行緒池管理器:用于創建并管理執行緒池 2. 作業執行緒:執行緒池中的執行緒 3. 任務介面:每個任務必須實作的介面,用于作業執行緒調度其運行 4. 任務佇列:用于存放待處理的任務,提供一種緩沖機制Java 里面執行緒池的頂級介面是 Executor,但是嚴格意義上講 Executor 并不是一個執行緒池,而只是一個執行執行緒的工具,真正的執行緒池介面是 ExecutorService,

圖解:
? Executor:執行緒池頂級介面,只有一個方法
? ExecutorService:真正的執行緒池介面? void execute(Runnable command) :執行任務/命令,沒有回傳值,一般用來執行Runnable
? <T> Future<T> submit(Callable<T> task):執行任務,有回傳值,一般又來執行Callable
? void shutdown() :關閉連接池
? AbstractExecutorService:基本實作了ExecutorService的所有方法
? ThreadPoolExecutor:默認的執行緒池實作類
? ScheduledThreadPoolExecutor:實作周期性任務調度的執行緒池
? Executors:工具類、執行緒池的工廠類,用于創建并回傳不同型別的執行緒池2.1 Executors.newCachedThreadPool
創建一個可根據需要創建新執行緒的執行緒池,但是在以前構造的執行緒可用時將重用它們,對于執行很多短期異步任務的程式而言,這些執行緒池通常可提高程式性能,呼叫 execute 將重用以前構造的執行緒(如果執行緒可用),如果現有執行緒沒有可用的,則創建一個新執行緒并添加到池中,終止并從快取中移除那些已有 60 秒鐘未被使用的執行緒,因此,長時間保持空閑的執行緒池不會使用任何資源,2.2 Executors.newFixedThreadPool
創建一個可重用固定執行緒數的執行緒池,以共享的無界佇列方式來運行這些執行緒,在任意點,在大多數 Threads 執行緒會處于處理任務的活動狀態,如果在所有執行緒處于活動狀態時提交附加任務,則在有可用執行緒之前,附加任務將在佇列中等待,如果在關閉前的執行期間由于失敗而導致任何執行緒終止,那么一個新執行緒將代替它執行后續的任務(如果需要),在某個執行緒被顯式地關閉之前,池中的執行緒將一直存在,2.3 Executors.newScheduledThreadPool
創建一個執行緒池,它可安排在給定延遲后運行命令或者定期地執行,
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3);
scheduledThreadPool.schedule(new Runnable(){
@Override
public void run() {
System.out.println("延遲三秒");
}
}, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
System.out.println("延遲 1 秒后每三秒執行一次");
}
},1,3,TimeUnit.SECONDS);
2.4 Executors.newSingleThreadExecutor
Executors.newSingleThreadExecutor()回傳一個執行緒池(這個執行緒池只有一個執行緒),這個執行緒池可以在執行緒死后(或發生例外時)重新啟動一個執行緒來替代原來的執行緒繼續執行下去!
執行緒池引數:
? corePoolSize:核心池的大小
? 默認情況下,創建了執行緒池后,執行緒數為0,當有任務來之后,就會創建一個執行緒去執行任務,
? 但是當執行緒池中執行緒數量達到corePoolSize,就會把到達的任務放到佇列中等待,
? corePoolSize和maximumPoolSize之間的執行緒數會自動釋放,小于等于corePoolSize的不會釋放,當大于了這個值就會將任務由一個丟棄處理機制來處理,
? keepAliveTime:執行緒沒有任務時最多保持多長時間后會終止
? 默認只限于corePoolSize和maximumPoolSize之間的執行緒
? BlockingQueue:存盤等待執行的任務的阻塞佇列,有多中選擇,可以是順序佇列、鏈式佇列等,
? workQueue:任務佇列,被提交但尚未被執行的任務,? ThreadFactory:執行緒工廠,默認是DefaultThreadFactory,Executors的靜態內部類
? RejectedExecutionHandler: ? 拒絕處理任務時的策略,如果執行緒池的執行緒已經飽和,并且任務佇列也已滿,對新的任務應該采取什么策略, ? 比如拋出例外、直接舍棄、丟棄佇列中最舊任務等,默認是直接拋出例外, ? 1、CallerRunsPolicy:只要執行緒池未關閉,該策略直接在呼叫者執行緒中,運行當前被丟棄的任務,顯然這樣做不會真的丟棄任務,但是,任務提交執行緒的性能極有可能會急劇下降, ? 2、DiscardOldestPolicy:丟棄最老的一個請求,也就是即將被執行的一個任務,并嘗試再次提交當前任務, ? 3、DiscardPolicy:該策略默默地丟棄無法處理的任務,不予任何處理,如果允許任務丟失,這是最好的一種方案, ? 4、AbortPolicy:java默認,拋出一個例外 以上內置拒絕策略均實作了 RejectedExecutionHandler 介面,若以上策略仍無法滿足實際需要,完全可以自己擴展 RejectedExecutionHandler 介面,三、執行緒的生命周期(狀態)

3.1 新生狀態(NEW)
? 用new關鍵字建立一個執行緒物件后,該執行緒物件就處于新生狀態,
? 處于新生狀態的執行緒有自己的記憶體空間,,此時僅由 JVM 為其分配記憶體,并初始化其成員變數的值,通過呼叫start進入就緒狀態 ,3.2 就緒狀態(RUNNABLE)
? 當執行緒物件呼叫了 start()方法之后,該執行緒處于就緒狀態,Java 虛擬機會為其創建方法呼叫堆疊和程式計數器,等待調度運行,
? 處于就緒狀態執行緒具備了運行條件,但還沒分配到CPU,處于執行緒就緒佇列,等待系統為其分配CPU
? 當系統選定一個等待執行的執行緒后,它就會從就緒狀態進入執行狀態,該動作稱之為“cpu調度”,3.3 運行狀態(RUNNING)
? 如果處于就緒狀態的執行緒獲得了 CPU,開始執行 run()方法的執行緒執行體,則該執行緒處于運行狀態,
? 在運行狀態的執行緒執行自己的run方法中代碼,直到等待某資源而阻塞或完成任務而死亡,
? 如果在給定的時間片內沒有執行結束,就會被系統給換下來回到等待執行狀態,3.4 阻塞狀態(BLOCKED)
阻塞狀態是指執行緒因為某種原因放棄了 cpu 使用權,也即讓出了 cpu timeslice,暫時停止運行進入阻塞狀態,在阻塞狀態的執行緒不能進入就緒佇列,只有當引起阻塞的原因消除時,如睡眠時間已到,或等待的I/O設備空閑下來,執行緒便轉入就緒狀態,重新到就緒佇列中排隊等待,才有機會再次獲得 cpu timeslice 轉到運行(running)狀態,被系統選中后從原來停止的位置開始繼續運行,阻塞的情況分三種:
? 等待阻塞(o.wait->等待對列):運行(running)的執行緒執行 o.wait()方法,JVM 會把該執行緒放入等待佇列(waitting queue)中,
? 同步阻塞(lock->鎖池) :運行(running)的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒占用,則 JVM 會把該執行緒放入鎖池(lock pool)中,
? 其他阻塞(sleep/join) :運行(running)的執行緒執行 Thread.sleep(long ms)或 t.join()方法,或者發出了 I/O 請求時,JVM 會把該執行緒置為阻塞狀態,當 sleep()狀態超時、join()等待執行緒終止或者超時、或者 I/O處理完畢時,執行緒重新轉入可運行(runnable)狀態,3.5 死亡狀態(DEAD)
死亡狀態是執行緒生命周期中的最后一個階段,執行緒會以下面三種方式結束,結束后就是死亡狀態,? 正常結束 : run()或 call()方法執行完成,執行緒正常運行結束,
? 例外結束 :執行緒拋出一個未捕獲的 Exception 或 Error,
? 呼叫 stop強制終止 :直接呼叫該執行緒的 stop()方法來結束該執行緒—該方法通常容易導致死鎖,不推薦使用,四、終止執行緒的四種方式
4.1 正常運行結束
程式運行結束,執行緒自動結束,4.2 使用退出標志退出執行緒
一般 run()方法執行完,執行緒就會正常結束,然而,常常有些執行緒是伺服執行緒,它們需要長時間的運行,只有在外部某些條件滿足的情況下,才能關閉這些執行緒,使用一個變數來控制回圈,例如:最直接的方法就是設一個 boolean 型別的標志,并通過設定這個標志為 true 或 false 來控制 while回圈是否退出,代碼示例:public class ThreadSafe extends Thread {
public volatile boolean exit = false;
public void run() {
while (!exit){
//do something
}
}
}
定義了一個退出標志 exit,當 exit 為 true 時,while 回圈退出,exit 的默認值為 false.在定義 exit時,使用了一個 Java 關鍵字 volatile(保證可見性但是不保證原子性,執行緒不安全),這個關鍵字的目的是使 exit 同步,也就是說在同一時刻只能由一個執行緒來修改 exit 的值,
4.3 Interrupt 方法結束執行緒
使用 interrupt()方法來中斷執行緒有兩種情況:
4.3.1 執行緒處于阻塞狀態:如使用了 sleep,同步鎖的 wait,socket 中的 receiver,accept 等方法時,會使執行緒處于阻塞狀態,當呼叫執行緒的 interrupt()方法時,會拋出 InterruptException 例外,阻塞中的那個方法拋出這個例外,通過代碼捕獲該例外,然后 break 跳出回圈狀態,從而讓我們有機會結束這個執行緒的執行,通常很多人認為只要呼叫 interrupt 方法執行緒就會結束,實際上是錯的, 一定要先捕獲 InterruptedException 例外之后通過 break 來跳出回圈,才能正常結束 run 方法,4.3.2 執行緒未處于阻塞狀態:使用 isInterrupted()判斷執行緒的中斷標志來退出回圈,當使用interrupt()方法時,中斷標志就會置 true,和使用自定義的標志來控制回圈是一樣的道理,
public class ThreadSafe extends Thread {
public void run() {
while (!isInterrupted()){ //非阻塞程序中通過判斷中斷標志來退出
try{
Thread.sleep(5*1000);//阻塞程序捕獲中斷例外來退出
}catch(InterruptedException e){
e.printStackTrace();
break;//捕獲到例外之后,執行 break 跳出回圈
}
}
}
}
4.4 stop 方法終止執行緒(執行緒不安全)
程式中可以直接使用 thread.stop()來強行終止執行緒,但是 stop 方法是很危險的,就象突然關閉計算機電源,而不是按正常程式關機一樣,可能會產生不可預料的結果,不安全主要是:thread.stop()呼叫之后,創建子執行緒的執行緒就會拋出 ThreadDeatherror 的錯誤,并且會釋放子執行緒所持有的所有鎖,一般任何進行加鎖的代碼塊,都是為了保護資料的一致性,如果在呼叫thread.stop()后導致了該執行緒所持有的所有鎖的突然釋放(不可控制),那么被保護資料就有可能呈現不一致性,其他執行緒在使用這些被破壞的資料時,有可能導致一些很奇怪的應用程式錯誤,因此,并不推薦使用 stop 方法來終止執行緒,五、執行緒控制方法

5.1 優先級控制
Java提供一個執行緒調度器來監控程式中啟動后進入就緒狀態的所有執行緒,執行緒調度器按照執行緒的優先級決定應調度哪個執行緒來執行,執行緒的優先級用數字表示,范圍從1到10:
? Thread.MIN_PRIORITY = 1
? Thread.MAX_PRIORITY = 10? Thread.NORM_PRIORITY = 5
使用下述方法獲得或設定執行緒物件的優先級,
? int getPriority();? void setPriority(int newPriority);
注意:優先級低只是意味著獲得調度的概率低,并不是絕對先呼叫優先級高后呼叫優先級低的執行緒,5.2 執行緒啟動(start)
執行緒由新生態進入就緒態,等待cpu調度運行,
5.3 執行緒等待(wait)
呼叫該方法的執行緒進入 WAITING 狀態,只有等待另外執行緒的通知或被中斷才會回傳,需要注意的是呼叫 wait()方法后,會釋放物件的鎖,因此,wait 方法一般用在同步方法或同步代碼塊中,
5.4 執行緒睡眠(sleep)
sleep 導致當前執行緒休眠,與 wait 方法不同的是 sleep 不會釋放當前占有的鎖,sleep(long)會導致執行緒進入 TIMED-WATING 狀態,而 wait()方法會導致當前執行緒進入 WATING 狀態,
5.5 執行緒讓步(yield)
yield 會使當前執行緒讓出 CPU 執行時間片(進入就緒態),與其他執行緒一起重新競爭 CPU 時間片,一般情況下,優先級高的執行緒有更大的可能性成功競爭得到 CPU 時間片,但這又不是絕對的,有的作業系統對執行緒優先級并不敏感,5.6 執行緒中斷(interrupt)
中斷一個執行緒,其本意是給這個執行緒一個通知信號,會影響這個執行緒內部的一個中斷標識位,這個執行緒本身并不會因此而改變狀態(如阻塞,終止等),1. 呼叫 interrupt()方法并不會中斷一個正在運行的執行緒,也就是說處于 Running 狀態的執行緒并不會因為被中斷而被終止,僅僅改變了內部維護的中斷標識位而已,
2. 若呼叫 sleep()而使執行緒處于 TIMED-WATING 狀態,這時呼叫 interrupt()方法,會拋出InterruptedException,從而使執行緒提前結束 TIMED-WATING 狀態,3. 許多宣告拋出 InterruptedException 的方法(如 Thread.sleep(long mills 方法)),拋出例外前,都會清除中斷標識位,所以拋出例外后,呼叫 isInterrupted()方法將會回傳 false,
4. 中斷狀態是執行緒固有的一個標識位,可以通過此標識位安全的終止執行緒,比如,你想終止一個執行緒 thread 的時候,可以呼叫 thread.interrupt()方法,在執行緒的 run 方法內部可以根據 thread.isInterrupted()的值來優雅的終止執行緒,5.7 插隊執行緒(join)
join() 方法,等待其他執行緒終止,在當前執行緒中呼叫另一個執行緒的 join() 方法,則當前執行緒轉為阻塞狀態,直到另一個執行緒結束,當前執行緒再由阻塞狀態變為就緒狀態,等待 cpu 的寵幸, 為什么要用 join()方法? 很多情況下,主執行緒生成并啟動了子執行緒,需要用到子執行緒回傳的結果,也就是需要主執行緒需要在子執行緒結束后再結束,這時候就要用到 join() 方法,System.out.println(Thread.currentThread().getName() + "執行緒運行開始!");
Thread6 thread1 = new Thread6();
thread1.setName("執行緒 B");
thread1.join();
System.out.println("這時 thread1 執行完畢之后才能執行主執行緒");
5.8 設定為守護執行緒(setDaemon)
? 可以將指定的執行緒設定成后臺執行緒? 創建后臺執行緒的執行緒結束時,后臺執行緒也隨之消亡
? 只能在執行緒啟動之前把它設為后臺執行緒5.9 執行緒喚醒(notify)
Object 類中的 notify() 方法,喚醒在此物件監視器上等待的單個執行緒,如果所有執行緒都在此物件上等待,則會選擇喚醒其中一個執行緒,選擇是任意的,并在對實作做出決定時發生,執行緒通過呼叫其中一個 wait() 方法,在物件的監視器上等待,直到當前的執行緒放棄此物件上的鎖定,才能繼續執行被喚醒的執行緒,被喚醒的執行緒將以常規方式與在該物件上主動同步的其他所有執行緒進行競爭,類似的方法還有 notifyAll() ,喚醒再次監視器上等待的所有執行緒,
5.10 終止執行緒(stop)
結束執行緒,不推薦使用
5.11 其他方法
5.11.1 isAlive(): 判斷一個執行緒是否存活,5.11.2 activeCount(): 程式中活躍的執行緒數,
5.11.3 enumerate(): 列舉程式中的執行緒,5.11.4 currentThread(): 得到當前執行緒,
5.11.5 sDaemon(): 一個執行緒是否為守護執行緒,5.11.6 setName(): 為執行緒設定一個名稱,
5.11.7 setPriority(): 設定一個執行緒的優先級,5.11.8 getPriority():獲得一個執行緒的優先級,
六、執行緒背景關系切換
巧妙地利用了時間片輪轉的方式, CPU 給每個任務都服務一定的時間,然后把當前任務的狀態保存下來,在加載下一任務的狀態后,繼續服務下一任務,任務的狀態保存及再加載, 這段程序就叫做背景關系切換,時間片輪轉的方式使多個任務在同一顆 CPU 上執行變成了可能,
6.1 行程
(有時候也稱做任務)是指一個程式運行的實體,在 Linux 系統中,執行緒就是能并行運行并且與他們的父行程(創建他們的行程)共享同一地址空間(一段記憶體區域)和其他資源的輕量級的行程,
6.2 背景關系
是指某一時間點 CPU 暫存器和程式計數器的內容,
6.3 暫存器
是 CPU 內部的數量較少但是速度很快的記憶體(與之對應的是 CPU 外部相對較慢的 RAM 主記憶體),暫存器通過對常用值(通常是運算的中間值)的快速訪問來提高計算機程式運行的速度,
6.4 程式計數器
是一個專用的暫存器,用于表明指令序列中 CPU 正在執行的位置,存的值為正在執行的指令的位置或者下一個將要被執行的指令的位置,具體依賴于特定的系統,6.5 PCB-“切換楨”
背景關系切換可以認為是內核(作業系統的核心)在 CPU 上對于行程(包括執行緒)進行切換,背景關系切換程序中的資訊是保存在行程控制塊(PCB, process control block)中的,PCB 還經常被稱作“切換楨”(switchframe),資訊會一直保存到 CPU 的記憶體中,直到他們被再次使用,6.6 背景關系切換的活動:
1. 掛起一個行程,將這個行程在 CPU 中的狀態(背景關系)存盤于記憶體中的某處, 2. 在記憶體中檢索下一個行程的背景關系并將其在 CPU 的暫存器中恢復, 3. 跳轉到程式計數器所指向的位置(即跳轉到行程被中斷時的代碼行),以恢復該行程在程式中,6.7 引起執行緒背景關系切換的原因
1. 當前執行任務的時間片用完之后,系統 CPU 正常調度下一個任務; 2. 當前執行任務碰到 IO 阻塞,調度器將此任務掛起,繼續下一任務; 3. 多個任務搶占鎖資源,當前任務沒有搶到鎖資源,被調度器掛起,繼續下一任務; 4. 用戶代碼掛起當前任務,讓出 CPU 時間; 5. 硬體中斷;七、Java后臺執行緒
1. 定義:守護執行緒--也稱“服務執行緒”,他是后臺執行緒,它有一個特性,即為用戶執行緒 提供 公共服務,在沒有用戶執行緒可服務時會自動離開,2. 優先級:守護執行緒的優先級比較低,用于為系統中的其它物件和執行緒提供服務,
3. 設定:通過 setDaemon(true)來設定執行緒為“守護執行緒”;將一個用戶執行緒設定為守護執行緒的方式是在 執行緒物件創建 之前 用執行緒物件的 setDaemon 方法,4. 在 Daemon 執行緒中產生的新執行緒也是 Daemon 的,
5. 執行緒則是 JVM 級別的,以 Tomcat 為例,如果你在 Web 應用中啟動一個執行緒,這個執行緒的生命周期并不會和 Web 應用程式保持同步,也就是說,即使你停止了 Web 應用,這個執行緒依舊是活躍的,
6. example: 垃圾回收執行緒就是一個經典的守護執行緒,當我們的程式中不再有任何運行的Thread,程式就不會再產生垃圾,垃圾回收器也就無事可做,所以當垃圾回收執行緒是 JVM 上僅剩的執行緒時,垃圾回收執行緒會自動離開,它始終在低級別的狀態中運行,用于實時監控和管理系統中的可回收資源,
7. 生命周期:守護行程(Daemon)是運行在后臺的一種特殊行程,它獨立于控制終端并且周期性地執行某種任務或等待處理某些發生的事件,也就是說守護執行緒不依賴于終端,但是依賴于系統,與系統“同生共死”,當 JVM 中所有的執行緒都是守護執行緒的時候,JVM 就可以退出了;如果還有一個或以上的非守護執行緒則 JVM 不會退出,八、同步鎖與死鎖
同步鎖:當多個執行緒同時訪問同一個資料時,很容易出現問題,為了避免這種情況出現,我們要保證執行緒同步互斥,就是指并發執行的多個執行緒,在同一時間內只允許一個執行緒訪問共享資料, Java 中可以使用 synchronized 關鍵字來取得一個物件的同步鎖,
死鎖:何為死鎖,就是多個執行緒同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放,8.1 執行緒同步
當多個執行緒訪問同一個資料時,容易出現執行緒安全問題,需要讓執行緒同步,保證資料安全,當兩個或兩個以上執行緒訪問同一資源時,需要某種方式來確保資源在某一時刻只被一個執行緒使用---執行緒同步,
執行緒同步的實作方案: ? 同步代碼塊 ? synchronized (obj){ } ? 同步方法 ? private synchronized void makeWithdrawal(int amt) {}8.2 Synchronized 同步鎖
synchronized 它可以把任意一個非 NULL 的物件當作鎖,他屬于獨占式的悲觀鎖,同時屬于可重入鎖,
Synchronized 作用范圍:
1. 作用于方法時,鎖住的是物件的實體(this); 2. 當作用于靜態方法時,鎖住的是Class實體,又因為Class的相關資料存盤在永久帶PermGen(jdk1.8 則是 metaspace),永久帶是全域共享的,因此靜態方法鎖相當于類的一個全域鎖,會鎖所有呼叫該方法的執行緒; 3. synchronized 作用于一個物件實體時,鎖住的是所有以該物件為鎖的代碼塊,它有多個佇列,當多個執行緒一起訪問某個物件監視器的時候,物件監視器會將這些執行緒存盤在不同的容器中,Synchronized 核心組件:
1) Wait Set:哪些呼叫 wait 方法被阻塞的執行緒被放置在這里; 2) Contention List:競爭佇列,所有請求鎖的執行緒首先被放在這個競爭佇列中; 3) Entry List:Contention List 中那些有資格成為候選資源的執行緒被移動到 Entry List 中; 4) OnDeck:任意時刻,最多只有一個執行緒正在競爭鎖資源,該執行緒被成為 OnDeck; 5) Owner:當前已經獲取到所資源的執行緒被稱為 Owner; 6) !Owner:當前釋放鎖的執行緒,Synchronized 實作:

1. JVM 每次從佇列的尾部取出一個資料用于鎖競爭候選者(OnDeck),但是并發情況下,ContentionList 會被大量的并發執行緒進行 CAS 訪問,為了降低對尾部元素的競爭,JVM 會將一部分執行緒移動到 EntryList 中作為候選競爭執行緒,
2. Owner 執行緒會在 unlock 時,將 ContentionList 中的部分執行緒遷移到 EntryList 中,并指定EntryList 中的某個執行緒為 OnDeck 執行緒(一般是最先進去的那個執行緒),
3. Owner 執行緒并不直接把鎖傳遞給 OnDeck 執行緒,而是把鎖競爭的權利交給 OnDeck,OnDeck 需要重新競爭鎖,這樣雖然犧牲了一些公平性,但是能極大的提升系統的吞吐量,在JVM 中,也把這種選擇行為稱之為“競爭切換”,4. OnDeck 執行緒獲取到鎖資源后會變為 Owner 執行緒,而沒有得到鎖資源的仍然停留在 EntryList中,如果 Owner 執行緒被 wait 方法阻塞,則轉移到 WaitSet 佇列中,直到某個時刻通過 notify或者 notifyAll 喚醒,會重新進去 EntryList 中,
5. 處于 ContentionList、EntryList、WaitSet 中的執行緒都處于阻塞狀態,該阻塞是由作業系統來完成的(Linux 內核下采用 pthread_mutex_lock 內核函式實作的),
6. Synchronized 是非公平鎖, Synchronized 在執行緒進入 ContentionList 時,等待的執行緒會先嘗試自旋獲取鎖,如果獲取不到就進入 ContentionList,這明顯對于已經進入佇列的執行緒是不公平的,還有一個不公平的事情就是自旋獲取鎖的執行緒還可能直接搶占 OnDeck 執行緒的鎖資源, 參考:https://blog.csdn.net/zqz_zqz/article/details/702337677. 每個物件都有個 monitor 物件,加鎖就是在競爭 monitor 物件,代碼塊加鎖是在前后分別加上 monitorenter 和 monitorexit 指令來實作的,方法加鎖是通過一個標記位來判斷的
8. synchronized 是一個重量級操作,需要呼叫作業系統相關介面,性能是低效的,有可能給執行緒加鎖消耗的時間比有用操作消耗的時間更多,
9. Java1.6,synchronized 進行了很多的優化,有適應自旋、鎖消除、鎖粗化、輕量級鎖及偏向鎖等,效率有了本質上的提高,在之后推出的 Java1.7 與 1.8 中,均對該關鍵字的實作機理做了優化,引入了偏向鎖和輕量級鎖,都是在物件頭中有標記位,不需要經過作業系統加鎖,10. 鎖可以從偏向鎖升級到輕量級鎖,再升級到重量級鎖,這種升級程序叫做鎖膨脹;
11. JDK 1.6 中默認是開啟偏向鎖和輕量級鎖,可以通過-XX:-UseBiasedLocking 來禁用偏向鎖,8.3 同步監視器
? synchronized (obj){ }中的obj稱為同步監視器? 同步代碼塊中同步監視器可以是任何物件,但是推薦使用共享資源作為同步監視器
? 同步方法中無需指定同步監視器,因為同步方法的同步監視器是this,也就是該物件本事同步監視器的執行程序
? 第一個執行緒訪問,鎖定同步監視器,執行其中代碼 ? 第二個執行緒訪問,發現同步監視器被鎖定,無法訪問 ? 第一個執行緒訪問完畢,解鎖同步監視器 ? 第二個執行緒訪問,發現同步監視器未鎖,鎖定并訪問8.4 Lock鎖
? JDK1.5后新增功能,與采用synchronized相比,lock可提供多種鎖方案,更靈活
? java.util.concurrent.lock 中的 Lock 框架是鎖定的一個抽象,它允許把鎖定的實作作為 Java 類,而不是作為語言的特性來實作,這就為 Lock 的多種實作留下了空間,各種實作可能有不同的調度演算法、性能特性或者鎖定語意,? ReentrantLock 類實作了 Lock ,它擁有與 synchronized 相同的并發性和記憶體語意, 但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性,此外,它還提供了在激烈爭用情況下更佳的性能,
? 注意:如果同步代碼有例外,要將unlock()寫入finally陳述句塊8.5 Lock和synchronized的區別
? Lock是顯式鎖(手動開啟和關閉鎖,別忘記關閉鎖),synchronized是隱式鎖? Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
? 使用Lock鎖,JVM將花費較少的時間來調度執行緒,性能更好,并且具有更好的擴展性(提供更多的子類) ? 優先使用順序: ? Lock----同步代碼塊(已經進入了方法體,分配了相應資源)----同步方法(在方法體之外)8.6 執行緒同步的優缺點
執行緒同步的好處:? 解決了執行緒安全問題
執行緒同步的缺點
? 性能下降 ? 會帶來死鎖死鎖
? 當兩個執行緒相互等待對方釋放“鎖”時就會發生死鎖 ? 出現死鎖后,不會出現例外,不會出現提示,只是所有的執行緒都處于阻塞狀態,無法繼續 ? 多執行緒編程時應該注意避免死鎖的發生九、volatile 關鍵字的作用(變數可見性、禁止重排序)
Java 語言提供了一種稍弱的同步機制,即 volatile 變數,用來確保將變數的更新操作通知到其他執行緒,volatile 變數具備兩種特性,volatile 變數不會被快取在暫存器或者對其他處理器不可見的地方,因此在讀取 volatile 型別的變數時總會回傳最新寫入的值,變數可見性:其一是保證該變數對所有執行緒可見,這里的可見性指的是當一個執行緒修改了變數的值,那么新的值對于其他執行緒是可以立即獲取的,
禁止重排序:volatile 禁止了指令重排,9.1 是比 sychronized 更輕量級的同步鎖
在訪問 volatile 變數時不會執行加鎖操作,因此也就不會使執行執行緒阻塞,因此 volatile 變數是一種比 sychronized 關鍵字更輕量級的同步機制,volatile 適合這種場景:一個變數被多個執行緒共 享,執行緒直接給這個變數賦值,
9.2 適用場景
值得說明的是對 volatile 變數的單次讀/寫操作可以保證原子性的,如 long 和 double 型別變數,但是并不能保證 i++這種操作的原子性,因為本質上 i++是讀、寫兩次操作,在某些場景下可以代替 Synchronized,但是,volatile 的不能完全取代 Synchronized 的位置,只有在一些特殊的場景下,才能適用 volatile,總的來說,必須同時滿足下面兩個條件才能保證在并發環境的執行緒安全:(1)對變數的寫操作不依賴于當前值(比如 i++),或者說是單純的變數賦值(boolean flag = true),
(2)該變數沒有包含在具有其他變數的不變式中,也就是說,不同的 volatile 變數之間,不能互相依賴,只有在狀態真正獨立于程式內其他內容時才能使用 volatile,十、執行緒通信
10.1 Java提供了3個方法解決執行緒之間的通信問題
均是java.lang.Object類的方法都只能在同步方法或者同步代碼塊中使用,否則會拋出例外
10.2 兩個執行緒之間共享資料
Java 里面進行多執行緒通信的主要方式就是共享記憶體的方式,共享記憶體主要的關注點有兩個:可見性和有序性原子性,Java 記憶體模型(JMM)解決了可見性和有序性的問題,而鎖解決了原子性的 問題,理想情況下我們希望做到“同步”和“互斥”,有以下常規實作方法: 將資料抽象成一個類,并將資料的操作作為這個類的方法:將資料抽象成一個類,并將對這個資料的操作作為這個類的方法,這么設計可以和容易做到同步,只要在方法上加”synchronized“public class MyData {
private int j=0;
public synchronized void add(){
j++;
System.out.println("執行緒"+Thread.currentThread().getName()+"j 為:"+j);
}
public synchronized void dec(){
j--;
System.out.println("執行緒"+Thread.currentThread().getName()+"j 為:"+j);
}
public int getData(){
return j;
}
}
public class AddRunnable implements Runnable{
MyData data;
public AddRunnable(MyData data){
this.data= https://www.cnblogs.com/firecode7/p/data;
}
public void run() {
data.add();
}
}
public class DecRunnable implements Runnable {
MyData data;
public DecRunnable(MyData data){
this.data = data;
}
public void run() {
data.dec();
}
}
public static void main(String[] args) {
MyData data = new MyData();
Runnable add = new AddRunnable(data);
Runnable dec = new DecRunnable(data);
for(int i=0;i<2;i++){
new Thread(add).start();
new Thread(dec).start();
}
}
Runnable 物件作為一個類的內部類:將 Runnable 物件作為一個類的內部類,共享資料作為這個類的成員變數,每個執行緒對共享資料的操作方法也封裝在外部類,以便實作對資料的各個操作的同步和互斥,作為內部類的各個 Runnable 物件呼叫外部類的這些方法,
public class MyData {
private int j=0;
public synchronized void add(){
j++;
System.out.println("執行緒"+Thread.currentThread().getName()+"j 為:"+j);
}
public synchronized void dec(){
j--;
System.out.println("執行緒"+Thread.currentThread().getName()+"j 為:"+j);
}
public int getData(){
return j;
}
}
public class TestThread {
public static void main(String[] args) {
final MyData data = https://www.cnblogs.com/firecode7/p/new MyData();
for(int i=0;i<2;i++){
new Thread(new Runnable(){
public void run() {
data.add();
}
}).start();
new Thread(new Runnable(){
public void run() {
data.dec();
}
}).start();
}
}
}
十一、ThreadLocal 作用(執行緒本地存盤)
ThreadLocal,很多地方叫做執行緒本地變數,也有些地方叫做執行緒本地存盤,ThreadLocal 的作用是提供執行緒內的區域變數,這種變數在執行緒的生命周期內起作用,減少同一個執行緒內多個函式或者組件之間一些公共變數的傳遞的復雜度,
ThreadLocalMap(執行緒的一個屬性):1. 每個執行緒中都有一個自己的 ThreadLocalMap 類物件,可以將執行緒自己的物件保持到其中,各管各的,執行緒可以正確的訪問到自己的物件,
2. 將一個共用的 ThreadLocal 靜態實體作為 key,將不同物件的參考保存到不同執行緒的ThreadLocalMap 中,然后在執行緒執行的各處通過這個靜態 ThreadLocal 實體的 get()方法取得自己執行緒保存的那個物件,避免了將這個物件作為引數傳遞的麻煩,3. ThreadLocalMap 其實就是執行緒里面的一個屬性,它在 Thread 類中定義ThreadLocal.ThreadLocalMap threadLocals = null;

使用場景:最常見的 ThreadLocal 使用場景為 用來解決 資料庫連接、Session 管理等,
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
十二、Java 中用到的執行緒調度
12.1 搶占式調度:
搶占式調度指的是每條執行緒執行的時間、執行緒的切換都由系統控制,系統控制指的是在系統某種運行機制下,可能每條執行緒都分同樣的執行時間片,也可能是某些執行緒執行的時間片較長,甚至某些執行緒得不到執行的時間片,在這種機制下,一個執行緒的堵塞不會導致整個行程堵塞,
12.2 協同式調度
協同式調度指某一執行緒執行完后主動通知系統切換到另一執行緒上執行,這種模式就像接力賽一樣,一個人跑完自己的路程就把接力棒交接給下一個人,下個人繼續往下跑,執行緒的執行時間由執行緒本身控制,執行緒切換可以預知,不存在多執行緒同步問題,但它有一個致命弱點:如果一個執行緒撰寫有問題,運行到一半就一直堵塞,那么可能導致整個系統崩潰,
12.3 JVM 的執行緒調度實作(搶占式調度)
java 執行緒調度使用搶占式調度,Java 中執行緒會按優先級分配 CPU 時間片運行,且優先級越高越優先執行,但優先級高并不代表能獨自占用執行時間片,可能是優先級高得到越多的執行時間片,反之,優先級低的分到的執行時間少但不會分配不到執行時間,12.4 執行緒讓出 cpu 的情況
1. 當前運行執行緒主動放棄 CPU,JVM 暫時放棄 CPU 操作(基于時間片輪轉調度的 JVM 作業系統不會讓執行緒永久放棄 CPU,或者說放棄本次時間片的執行權),例如呼叫 yield()方法, 2. 當前運行執行緒因為某些原因進入阻塞狀態,例如阻塞在 I/O 上, 3. 當前運行執行緒結束,即運行完 run()方法里面的任務,十三、行程調度演算法
13.1 優先調度演算法
1. 先來先服務調度演算法(FCFS):當在作業調度中采用該演算法時,每次調度都是從后備作業佇列中選擇一個或多個最先進入該佇列的作業,將它們調入記憶體,為它們分配資源、創建行程,然后放入就緒佇列,在行程調度中采用 FCFS 演算法時,則每次調度是從就緒佇列中選擇一個最先進入該佇列的行程,為之分配處理機,使之投入運行,該行程一直運行到完成或發生某事件而阻塞后才放棄處理機,特點是:演算法比較簡單,可以實作基本上的公平,
2. 短作業(行程)優先調度演算法:短作業優先(SJF)的調度演算法是從后備佇列中選擇一個或若干個估計運行時間最短的作業,將它們調入記憶體運行,而短行程優先(SPF)調度演算法則是從就緒佇列中選出一個估計運行時間最短的行程,將處理機分配給它,使它立即執行并一直執行到完成,或發生某事件而被阻塞放棄處理機時再重新調度,該演算法未照顧緊迫型作業,13.2 高優先權優先調度演算法
為了照顧緊迫型作業,使之在進入系統后便獲得優先處理,引入了最高優先權優先(FPF)調度演算法,當把該演算法用于作業調度時,系統將從后備佇列中選擇若干個優先權最高的作業裝入記憶體,當用于行程調度時,該演算法是把處理機分配給就緒佇列中優先權最高的行程,1. 非搶占式優先權演算法:在這種方式下,系統一旦把處理機分配給就緒佇列中優先權最高的行程后,該行程便一直執行下去,直至完成;或因發生某事件使該行程放棄處理機時,這種調度演算法主要用于批處理系統中;也可用于某些對實時性要求不嚴的實時系統中,
2. 搶占式優先權調度演算法:在這種方式下,系統同樣是把處理機分配給優先權最高的行程,使之執行,但在其執行期間,只要又出現了另一個其優先權更高的行程,行程調度程式就立即停止當前行程(原優先權最高的行程)的執行,重新將處理機分配給新到的優先權最高的行程,顯然,這種搶占式的優先權調度演算法能更好地滿足緊迫作業的要求,故而常用于要求比較嚴格的實時系統中,以及對性能要求較高的批 處理和分時系統中,13.3.高回應比優先調度演算法
在批處理系統中,短作業優先演算法是一種比較好的演算法,其主要的不足之處是長作業的運行得不到保證,如果我們能為每個作業引入前面所述的動態優先權,并使作業的優先級隨著等待時間的增加而以速率 a 提高,則長作業在等待一定的時間后,必然有機會分配到處理機,該優先權的變化規律可描述為:
(1) 如果作業的等待時間相同,則要求服務的時間愈短,其優先權愈高,因而該演算法有利于短作業,
(2) 當要求服務的時間相同時,作業的優先權決定于其等待時間,等待時間愈長,其優先權愈高,因而它實作的是先來先服務,(3) 對于長作業,作業的優先級可以隨等待時間的增加而提高,當其等待時間足夠長時,其優先級便可升到很高,從而也可獲得處理機,簡言之,該演算法既照顧了短作業,又考慮了作業到達的先后次序,不會使長作業長期得不到服務,因此,該演算法實作了一種較好的折衷,當然,在利用該演算法時,每要進行調度之前,都須先做回應比的計算,這會增加系統開銷,
13.4 基于時間片的輪轉調度演算法
1. 時間片輪轉法:在早期的時間片輪轉法中,系統將所有的就緒行程按先來先服務的原則排成一個佇列,每次調度時,把 CPU 分配給隊首行程,并令其執行一個時間片,時間片的大小從幾 ms 到幾百 ms,當執行的時間片用完時,由一個計時器發出時鐘中斷請求,調度程式便據此信號來停止該行程的執行,并將它送往就緒佇列的末尾;然后,再把處理機分配給就緒佇列中新的隊首行程,同時也讓它執行一個時間片,這樣就可以保證就緒佇列中的所有行程在一給定的時間內均能獲得一時間片的處理機執行時間,2. 多級反饋佇列調度演算法:
(1) 應設定多個就緒佇列,并為各個佇列賦予不同的優先級,第一個佇列的優先級最高,第二個佇列次之,其余各佇列的優先權逐個降低,該演算法賦予各個佇列中行程執行時間片的大小也各不相同,在優先權愈高的佇列中,為每個行程所規定的執行時間片就愈小,例如,第二個佇列的時間片要比第一個佇列的時間片長一倍,……,第 i+1 個佇列的時間片要比第 i 個佇列的時間片長一倍,
(2) 當一個新行程進入記憶體后,首先將它放入第一佇列的末尾,按 FCFS 原則排隊等待調度,當輪到該行程執行時,如它能在該時間片內完成,便可準備撤離系統;如果它在一個時間片結束時尚未完成,調度程式便將該行程轉入第二佇列的末尾,再同樣地按 FCFS 原則等待調度執行;如果它在第二佇列中運行一個時間片后仍未完成,再依次將它放入第三佇列,……,如此下去,當一個長作業(行程)從第一佇列依次降到第 n 佇列后,在第 n 佇列便采取按時間片輪轉的方式運行,(3) 僅當第一佇列空閑時,調度程式才調度第二佇列中的行程運行;僅當第 1~(i-1)佇列均空時,才會調度第 i 佇列中的行程運行,如果處理機正在第 i 佇列中為某行程服務時,又有新行程進入優先權較高的佇列(第 1~(i-1)中的任何一個佇列),則此時新行程將搶占正在運行行程的處理機,即由調度程式把正在運行的行程放回到第 i 佇列的末尾,把處理機分配給新到的高優先權行程,在多級反饋佇列調度演算法中,如果規定第一個佇列的時間片略大于多數人機互動所需之處理時間時,便能夠較好的滿足各種型別用戶的需要,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/139922.html
標籤:Java
上一篇:面向物件的由來
