多線程相關的三組概念
程序和進程
-
程序(program):一個固定的運行邏輯和數據的集合,是一個靜態的狀態,一般存儲在硬盤中。簡單來說就是我們編寫的代碼
-
進程(process):一個正在運行的程序,一個程序的一次運行,是一個動態的概念般存儲在內存中。 例如:command + option + esc,打開任務管理器可以看到所有進程
-
進程是正在運行的一個程序:
程序是靜態的:QQ這個程序,如果不運行就是存在磁盤中,不占內存,所以是靜態的。
進程是動態的:QQ這個程序,如果在運行就運行在內存中,占用內存,所以是動態的。
進程和線程
-
進程(process):一個正在運行的程序,一個程序的一次運行,是一個動態的概念存儲在內存中。
-
比如我們使用QQ,就啟動了一個進程,操作系統就會為該進程分配內存空間,比如我們再啟動迅雷,就會又啟動一個進程,操作系統會給迅雷分配新的內存空間
-
進程是程序的一次執行過程,或是正在運行的一個程序,是一個動態的過程,有它自身的產生,存在和消亡的過程
-
-
線程(thread):一條獨立的執行路徑。多線程,在執行某個程序的時候,該程序可以有多個子任務,每個線程都可以獨立的完成其中一個任務。在各個子任務之間,沒有什么依賴關系,可以單獨執行。
-
線程是由進程創建的,是進程的一個實體
-
一個進程可以擁有多個線程
-
-
進程和線程的關系: 進程是用于分配資源的單位 一個進程中,可以有多條線程;但是一個進程中,至少有一條線程
比如當我們啟動迅雷時,就開啟一個進程,使用迅雷時我們可以同時下載5個電影資源,每一個下載任務就是一個線程
線程不會獨立的分配資源,一個進程中的所有線程,共享同一個進程中的資源
并行和并發
-
并行(parallel):多個任務(進程、線程)同時運行。在某個確定的時刻,有多個任務在執行,多個cpu可以實現并行。 條件:有多個cpu,多核編程
-
并發(concurrent):多個任務(進程、線程)同時發起。不能同時執行的(只有一個cpu),只能是同時要求執行。就只能在某個時間片內,將多個任務都有過執行。一個cpu在不同的任務之間,來回切換,只不過每個任務耗費的時間比較短,cpu的切換速度比較快,所以可以讓用戶感覺就像多個任務在同時執行。并發就是同一時刻,多個任務交替執行,造成一種,“貌似同時”的錯覺,簡單來說,單核cpu實現的多任務就是并發
-
舉例:
-
并發
2.并行
-
一個人可以同時做多件事兒,就是并發
多個人可以同時做多件事兒,就是并行
并發是多個任務之間互相搶占資源
并行是多個任務之間不互相搶占資源
并發的關鍵是你有處理多個任務的能力,不一定要同時。 并行的關鍵是你有同時處理多個任務的能力。
最關鍵的點就是:是否是『同時』。
但是我們的電腦也會存在并發與并行同時存在的情況
并行和并發的圖示
并發的本質:不同任務來回切換:
并行和并發的比較:
問&答:
問:既然只有一個cpu,還需要在不同的任務之間來回切換,那么效率到底是提升了還是降低了?
答:
1、提升了:整個系統的效率提升了(cpu的使用率提升了),對于某個具體的任務而言,效率是降低了
2、并發技術,解決的是不同的設備之間速率不同的問題 cpu: 10^-9秒 大概是千分之一毫秒 執行一次. 0.000000001 內存:10^-6秒 0.000001 磁盤:10^-3秒 0.001 人:10^0秒.
3、cpu 的效率非常高,給磁盤一個指令,讓磁盤尋找某個字節,cpu就趕緊切換到其它的任務,執行其它的
計算、存儲的操作。這樣子就可以讓計算機系統中的所有設備都運轉起來,提升了cpu的使用率。絕不能出現
cpu這種昂貴的設備等待磁盤這種廉價設備的情況。
多線程的實現方式
多線程實現方式一:繼承方式
-
繼承Thread
-
步驟:
1、定義一個類,繼承Thread類
2、重寫自定義類中的run方法,用于定義新線程要運行的內容
3、創建自定義類型的對象
4、調用線程啟動的方法:start()方法
代碼示例:
/*** 多線程實現方式一:實現方式*/
public class Simple01 {public static void main(String[] args) {// 創建自定義類型的對象MyThread mt = new MyThread();// 調用對象的start方法,啟動線程mt.start(); // 用主線程 調用自定義的線程
?// 讓主線程(主方法本來就所在的線程)繼續運行for (int i = 1; i <= 100; i++) {System.out.println(i);}// 現象是主線程內容和新線程內容交替執行,兩條線程相互沒有依賴關系}
?
}
?
// 定義一個類,繼承Thread類
class MyThread extends Thread {//重寫run方法,定義線程的運行內容@Overridepublic void run() {for (int i = 0; i <=100; i++) {System.err.println(i);}}
}
多線程實現方式二:實現方式
-
實現Runnable接口:
Runnable接口的實現類對象,表示一個具體的任務,將來創建一個線程對象之后,讓線程執行這個任務
-
步驟: 1、定義一個任務類,實現Runnable接口 2、重寫任務類中的run方法,用于定義任務的內容 3、創建任務類對象,表示任務 4、創建一個Thread類型的對象,用于執行任務類對象 5、調用線程對象的start方法,開啟新線程
代碼示例:
/*** 多線程實現方式二:實現方式*/
public class Simple02 {public static void main(String[] args) {// 創建任務類對象MyTask myTask = new MyTask();// 創建線程對象,綁定要執行的任務Thread thread = new Thread(myTask);// 啟動新線程thread.start();
?for (int i = 1; i <= 10000; i++) {System.out.println(i);}}
}
// 定義一個任務類
class MyTask ?implements ?Runnable{@Override// 重寫run方法,定義任務的內容public void run() {for (int i = 1; i <= 10000; i++) {System.err.println(i);}}
}
兩種方式的比較
-
代碼復雜程度:
繼承Thread方式簡單
實現Runnable接口的方式比較復雜
-
設計:java中只支持單繼承、不支持多繼承
-
繼承方式:
某個類繼承了Thread類,那么就無法繼承其他業務中需要的類型,就限制了我們的設計。所以擴展性較差。
-
實現方式:
某個類通過實現Runnable的方式完成了多線程的設計,仍然可以繼承當前業務中的其它類型,擴展性較強。
-
-
靈活性:
-
繼承方式:
將線程對象和任務內容綁定在了一起,耦合性較強、靈活性較差
-
實現方式:
將線程對象和任務對象分離,耦合性就降低,靈活性增強:同一個任務可以被多個線程對象執行,某個線程對象也可以執行其它的任務對象。并且將來還可以將任務類對象,提交到線程池中運行;任務類對象可以被不同線程運行,方便進行線程之間的數據交互。
-
使用匿名內部類創建線程對象
代碼示例:
/*** 使用匿名內部類創建線程對象*/
public class Simple03 {public static void main(String[] args) {
?//第一條線程打印1-100,使用繼承的方式new Thread() {@Overridepublic void run() {for(int i = 1; i <= 100; i++) {System.out.println(i + "...extends");}}}.start();
?//第二條線程打印1-100,使用實現的方式new Thread(new Runnable() {@Overridepublic void run() {for(int i = 1; i <= 100; i++) {System.out.println(i + "...implements" );}}}).start();}
}
Thread類中的常用方法
獲取線程名稱
-
getName():獲取線程的名稱
-
注意事項: 1、如果沒有給線程命名,那么線程的默認名字就是Thread-x,x是序號,從0開始
代碼示例:
/*** 獲取線程名稱*/
public class Simple04 {public static void main(String[] args) {Thread thread = new Thread() {@Override// 繼承方式 可以加this關鍵字找到getNamepublic void run() {System.out.println("name:"+this.getName() );}};thread.setName("線程0");thread.start();Thread thread1 = new Thread() {@Overridepublic void run() {System.out.println("name1:"+this.getName() );}};thread1.setName("線程1");thread1.start();}
}
設置線程名稱
-
setName(String name) 使用線程對象的引用,調用該方法給線程起名字
-
構造方法: Thread(String name):給線程通過構造方法起名字 Thread(Runnable target, String name):在給線程target對象賦值的同時,也給線程起名
-
注意事項: 線程設置名字,既可以在啟動之前設置,也可以在啟動之后設置
代碼名稱:
/*** 設置 線程名稱*/
public class Simple05 {public static void main(String[] args) {Thread thread = new Thread("線程0") {@Override// 繼承方式 可以加this關鍵字找到getNamepublic void run() {System.out.println("name:"+this.getName() );}};thread.start();
?new Thread(new Task(),"線程1") {@Overridepublic void run() {System.out.println("name:"+this.getName() );}}.start();}
}
class Task implements Runnable{@Overridepublic void run() {}
}
獲取當前線程對象
1、作用: 某段代碼只要是在執行,就一定是在某個方法中執行的,只要在某個方法中,代碼就一定是被某個線程執行的。所以任意一句代碼,都有執行這句代碼的線程對象。 可以在任意位置,獲取當前正在執行這段代碼的線程對象 2、方法: Thread Thread.currentThread(); 返回當前正在執行這段代碼的線程對象的引用 哪條線程在執行這段代碼,返回的就是哪條線程
代碼示例:
/*** 獲取當前線程對象*/
public class Simple06 {public static void main(String[] args) {Thread thread = new Thread("線程0") {@Override// 繼承方式 可以加this關鍵字找到getNamepublic void run() {System.out.println("name:"+this.getName() );}};thread.start();
// ? ? ? System.out.println(thread.getName());
?new Thread(new Task(),"線程1").start();}
}
class Task implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread());}
}
線程休眠
-
Thread.sleep(long time) 讓當前線程休眠 time:休眠的時間,單位是毫秒
-
作用: 當某段代碼在某個位置需要休息的時候,就使用線程的休眠 無論哪個線程在運行這段代碼,碰到sleep方法都需要停下休息
-
注意事項: 有一個異常,中斷異常:InterruptedException 在普通方法中,可以聲明拋出 在重寫的run方法中,必須只能處理,不能聲明
代碼示例:
/*** 線程休眠*/ public class Simple07 {public static void main(String[] args) throws InterruptedException { // ? ? ? InterruptedException 中斷異常 // ? ? ? System.out.println(1); // ? ? ? Thread.sleep(1000); // ? ? ? System.out.println(2); ?MyCountDown myCountDown = new MyCountDown();Thread thread = new Thread(myCountDown);thread.start();} } class MyCountDown implements Runnable {@Overridepublic void run() {// 在run方法中,異常只能處理,不能拋出for (int i = 5; i >= 1; i--) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(i);}} }
守護線程
-
setDaemon(boolean flag):每條線程默認不是守護線程,只有設定了flag為true之后,該線程才變成守護線程
-
isDaemon():判斷某條線程是否是守護線程
-
守護線程的特點: 守護線程就是用于守護其它線程可以正常運行的線程,在為其它的核心線程準備良好的運行環境。
如果非守護線程全部死亡,守護線程就沒有存在的意義了,一段時間之后,虛擬機也一同結束。
虛擬機結束了,所有線程都結束了。
-
別名: 后臺線程
好比前臺的表演都是需要后臺工作人員的準備支持,前臺沒有人表演了,那么后臺工作人員就沒有存在的意義了。
在jvm中,gc就是守護線程,作用就是當用戶自定義的線程以及主線程執行完畢后,gc才停止工作,
不然的話,當主線程沒有執行結束,gc就結束了,這樣就會造成gc垃圾還沒有清理完,主線程早就掛了。
(注意:主線程不是守護線程,gc才是守護線程)。
我們只需要了解什么是守護線程的概念,即可。
代碼示例:
/*** 線程守護*/
public class Simple08 {public static void main(String[] args) {
?Thread thread = new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println(i+"...守護線程");}});// 設置為守護線程thread.setDaemon(true);System.out.println(thread.isDaemon());// 啟動之后,系統中就有兩條線程:主線程和守護線程thread.start();
?for (int i = 1; i <= 50; i++) {System.out.println(i + "...main");}//主線程運行完成之后,系統中就只剩下了守護線程//如果非守護線程全部死亡,守護線程就沒有存在的意義了,//一段時間后,虛擬機也一同結束。//當主線程執行結束,jvm還沒有立即結束,所以守護線程還會執行一段時間}
}
在實際開發中,守護線程(Daemon Thread)有以下主要作用:
-
后臺任務執行:守護線程通常用于執行后臺任務,這些任務不需要對用戶可見,且不影響主線程的執行。比如,在 Web 服務器中,可以使用守護線程處理一些定時任務、日志記錄等后臺操作,而不會影響用戶請求的處理。
-
資源回收:守護線程在 JVM 關閉時會自動終止,并且它們的執行不會阻止 JVM 退出。因此,守護線程通常用于執行資源回收工作,如垃圾回收器(Garbage Collector)線程就是守護線程,負責回收不再使用的對象。
-
定時任務:守護線程可以用于執行定時任務,如定時更新緩存、定時發送心跳包等。這樣可以在后臺自動執行這些任務,而不需要手動觸發。
-
后臺服務:某些后臺服務可能需要一直運行,并隨著應用的啟動而啟動。這些服務可以作為守護線程運行,以確保在應用退出時能夠自動終止。
多線程中的線程安全問題
問題描述
某段代碼在沒有執行完成的時候,cpu就可能被其它線程搶走,結果導致當前代碼中的一些數據發生錯誤
原因:沒有保證某段代碼的執行的完整性、原子性
希望:這段代碼要么全都執行,要么全都沒有執行
代碼示例:
/*** 多線程中的線程安全問題* 模擬出票系統*/
public class Simple01 {public static void main(String[] args) {Ticket ticket = new Ticket(); // 堆中只有一個對象Thread thread1 = new Thread(ticket, "窗口1");Thread thread2 = new Thread(ticket, "窗口2");Thread thread3 = new Thread(ticket, "窗口3");thread1.start();thread2.start();thread3.start();}
}
?
class Ticket implements Runnable {
?private int ticktNum = 0;//票號
?// 線程售票public void run() {while (ticktNum < 10) { // 模擬售10張票// 1.模擬出票時間try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 2.打印進程號和票號String name = Thread.currentThread().getName();ticktNum = ++ticktNum;System.out.println("線程" + name + "售票:票號" + ticktNum);}// 出現的問題,某票號有可能被賣了多次// 不存在的票10號也有可能出現出現了// 出現不存在的票號原因:線程1,2在tickNum++ 時候被其他線程搶走了。倒置被累加多次
?}
}
多個線程對全局變量只讀,不寫,那么一般這個變量是安全的,
多個線程同時執行寫的操作,那么就需要考慮線程同步的問題,否則會影響線程安全問題
綜上所述,線程安全問題根本原因:
-
多個線程在操作共享的數據;
-
多個線程對共享數據有寫的操作;
問題解決:
要解決以上線程問題,只要在某個線程修改共享資源的時候,其它線程不能修改該資源,等待修改完畢同步之后,才能去搶奪CPU資源,完成對應的操作,保證了數據的同步性,解決了線程不安全的現象。
為了保證每個線程都能正常執行共享資源操作,Java引入了線程同步機制:
構造代碼塊
1) 同步代碼塊(synchronized)
2) 同步方法 (synchronized)
同步代碼塊
-
同步代碼塊: 使用一種格式,達到讓某段代碼執行的時候,cpu不要切換到影響當前代碼的代碼上去 這種格式,可以確保cpu在執行A線程的時候,不會切換到其它線程上去
-
使用格式 synchronized (鎖對象) { 需要保證完整性、原子性的一段代碼(需要同步的代碼) }
-
使用同步代碼塊之后的效果: 當cpu想去執行同步代碼塊的時候,需要先獲取到鎖對象,獲取之后就可以運行代碼塊中的內容;
當cpu正在執行當前代碼塊內容時,cpu可以切換到其它代碼,但是不能切換到具有相同鎖對象上;
當cpu執行完當前代碼塊中代碼之后, 會釋放鎖對象,cpu就可以運行其它具有當前鎖對象的同步代碼塊了;
代碼示例:
/*** 同步代碼塊*/
public class Simple02 {public static void main(String[] args) {Printer p = new Printer();
?Thread t1 = new Thread() {@Overridepublic void run() {while (true) {p.print1();}}};
?Thread t2 = new Thread() {@Overridepublic void run() {while (true) {p.print2();}}};
?t1.start();t2.start();}
}
?
class Printer {Object object = new Object();public void print1() {// 線程1在獲取了obj鎖之后,線程2就無法獲取obj鎖,// 就只能等待線程1運行完這個方法,兩條線程才能再次共同爭奪鎖對象synchronized (object) {System.out.print("碼");System.out.print("上");System.out.print("未");System.out.println("來");}
?
?}
?public void print2() {synchronized (object) {System.err.print("線");System.err.print("程");System.err.println("鎖");}
?
?}
}
// 不加鎖的話,會出現:碼上未來碼 ? 線程鎖碼 等數據不準確現象
// A線程執行print1完畢 釋放obj對象,然后cpu被B線程搶去執行print2方法,
// B線程執行print2完畢,釋放obj對象,然后cpu又被A或者B 線程搶去執行對應方法,
同步方法
1、同步代碼塊:在某段代碼執行的時候,不希望cpu切換到其他影響當前線程的線程上去,就在這段代碼上加上同步代碼塊,至于該方法中一些只讀的成員變量就不需要放在同步代碼塊中了。
所以同步代碼塊中,只存放需要保證完整性、原子性的一段代碼。
2、如果某個方法中,所有的代碼都需要加上同步代碼塊,使用同步方法這種【簡寫格式】來替代同步代碼塊
3、同步方法的格式:
權限修飾符 [靜態修飾符] synchronized 返回值類型 方法名稱(參數列表) {
需要同步的方法體
}
4、同步方法的鎖對象
如果是非靜態的方法,同步方法的鎖對象就是this,當前對象,哪個對象調用這個同步方法,這個同步方法使用的鎖就是哪個對象
5、如果是靜態的方法,同步方法的鎖對象就是當前類的字節碼對象,類名.class(在方法區的一個對象),哪個類在調用這個同步方法,這個同步方法使用的鎖就是哪個類的字節碼對象
代碼示例:
/*** 同步方法*/
public class Simple03 {public static void main(String[] args) {Printer p = new Printer();Thread t1 = new Thread() {@Overridepublic void run() {while (true) {p.print1();}}};
?Thread t2 = new Thread() {@Overridepublic void run() {while (true) {p.print2();}}};
?t1.start();t2.start();}
}
?
class Printer {
?public static synchronized void print1() {System.out.print("碼");System.out.print("上");System.out.print("未");System.out.println("來");}
?public static ?synchronized void print2() {System.err.print("線");System.err.print("程");System.err.println("鎖");}
}
6、特別注意,如果是靜態方法,在靜態方法內部使用同步代碼塊,同步代碼塊鎖對象也是當前類的字節碼對象
/*** 靜態方法中使用同步代碼塊*/
public class Simple04 {public static void main(String[] args) {Printer p = new Printer();Thread t1 = new Thread() {@Overridepublic void run() {while (true) {p.print1();}}};
?Thread t2 = new Thread() {@Overridepublic void run() {while (true) {p.print2();}}};
?t1.start();t2.start();}
}
?
class Printer {public static void print1() {synchronized (Printer.class) {System.out.print("碼");System.out.print("上");System.out.print("未");System.out.println("來");}}public static void print2() {synchronized (Printer.class) {System.err.print("線");System.err.print("程");System.err.println("鎖");}}
}
鎖對象的說明
-
線程加上同步是為了讓兩條線程相互不要影響,如果相互影響,影響的是數據的正確性
-
使用鎖對象,其實主要是為了鎖數據。當某條線程在操作這個數據時,其它線程不能操作當前的數據。
-
如果兩條線程要操作相同的數據,那么這兩條線程就需要在操作數據的部分都加上同步代碼塊,并且使用相同的鎖對象。
死鎖
-
A線程需要甲資源,同時擁有乙資源;B線程需要乙資源,同時擁有甲資源,兩條線程都不肯釋放自己擁有的資源,同時也需要其它的資源時,就都無法進行運行。形成“死鎖”現象。
-
代碼表現: 有了同步代碼塊的嵌套,就可能發生死鎖。某條線程獲取了外層鎖對象A,需要內層鎖對象B,等待;
因為另一條線程此時已經獲取了外層的鎖對象B,需要內層的鎖對象A,等待。兩條線程就會形成死鎖。
代碼示例:
/*** 死鎖*/
public class Simple05 {public static void main(String[] args) {
?Thread t1 = new Thread("蘇格拉底") {public void run() {while (true) {synchronized ("筷子A") {System.out.println("蘇格拉底爭取到了筷子A,等待筷子B");
?synchronized ("筷子B") {System.out.println("蘇格拉底獲取了所有的筷子,狂吃狂吃");}}}}};
?Thread t2 = new Thread("柏拉圖") {public void run() {while (true) {synchronized ("筷子B") {System.out.println("柏拉圖爭取到了筷子B,等待筷子A");synchronized ("筷子A") {System.out.println("柏拉圖獲取了所有的筷子,狂吃狂吃");}}}}};t1.start();t2.start();}
}
?
// 開發盡量避免鎖的嵌套以此 來避免死鎖現象發生。
// 公平鎖 非公平鎖 表鎖 行鎖 自旋鎖
火車票案例
/*** 火車票案例*/
public class Simple06 {/*** 三個窗口,同時售賣100張火車票* 打印某個窗口賣出了1張票,還剩x張* 窗口是隨機的** @param args*/public static void main(String[] args) {Tickets t1 = new Tickets();Tickets t2 = new Tickets();Tickets t3 = new Tickets();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
?
class Tickets extends Thread {
?//為了讓多個線程多個對象 共享同一個數據 我們加上靜態private static int tickets = 100;
?public void run() {while (true) {// 多個線程 使用同一個鎖對象 使用xx.classsynchronized (Tickets.class) {if (tickets <= 0) {break;}try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//線程1等待 線程2等待//線程3 把ticket-- 變成0 這時候cpu切換到線程1 線程1執行--操作變為-1tickets--;System.out.println(getName() + "賣出了1張票, 還剩" + tickets + "張票");}}}
}
線程生命周期
概述
-
線程是一個動態的概念,有創建的時候,也有運行和變化的時候,必須也就有消亡的時候,所以從生到死就是一個生命周期。在生命周期中,有各種各樣的狀態,這些狀態可以相互轉換。
-
狀態羅列: 新建態:剛創建好對象的時候,剛new出來的時候 就緒態:線程準備好了所有運行的資源,只差cpu來臨 運行態:cpu正在執行的線程的狀態 阻塞態:線程主動休息、或者缺少一些運行的IO資源,即使cpu來臨,也無法運行 死亡態:線程運行完成、出現異常、調用方法結束
狀態轉換圖
Java中線程狀態的描述
-
線程狀態只是理論上的狀態,在計算機系統中有更加精確的描述,可以將各種不同的狀態,封裝成對象,這些對象可能和理論狀態不完全相同。
-
Java語言中,獲取線程狀態的方法: getState():返回當前線程對象的狀態對象;返回值類型是Thread.State,Thread的內部類
-
Thread.State:Java語言中,描述線程狀態的類型 說明:由于狀態時有限個的,所以該類的對象,不需要手動創建,直接在類中創建好了,獲取即可,
這種類型就是枚舉類型。每個對象,稱為枚舉項。
Thread.State中,枚舉項:6個 NEW:新建態,沒有開啟線程 RUNNABLE:就緒態和運行態 BLOCKED:阻塞態 (等待鎖、I\O,等待鎖對象釋放了,IO資源準備完畢了,就不阻塞了) WAITING:阻塞態 (調用了wait方法,等待其它線程喚醒的狀態,沒人喊它,它就一直阻塞) TIMED_WAITING:阻塞態(調用了有時間限制的wait方法、sleep方法) TERMINATED:死亡態
代碼示例:
public class Simple01 {
?public static void main(String[] args) throws InterruptedException {//test1_new_timedWaiting_terminated_runnable();test2_blocked_TERMINATED();
?}
?//演示blocked阻塞狀態public static void test2_blocked_TERMINATED() throws InterruptedException {
?Thread t1 = new Thread("t1線程") {public void run() {synchronized ("abc") {for (int i = 0; i <= 3; i++) {//打印數字,和線程名稱System.out.println(i + "..." + getName());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}};Thread t2 = new Thread("t2線程") {public void run() {//沒有獲取到鎖的線程對象的狀態是:BLOCKEDsynchronized ("abc") {for (int i = 0; i <= 3; i++) {System.out.println(i + "..." + getName());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}};t1.start();t2.start();
?while (true) {System.out.println("t1...." + t1.getState());System.out.println("t2...." + t2.getState());Thread.sleep(50);//t1 和 t2 判斷線程是否結束if (t1.getState().equals(Thread.State.TERMINATED) &&t2.getState().equals(Thread.State.TERMINATED)) {System.out.println("兩條線程都結束了");break;}}}
?private static void test1_new_timedWaiting_runnable() throws InterruptedException {Thread t1 = new Thread() {public void run() {for (int i = 1; i < 100; i++) {System.out.println(i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}
?}};System.out.println("A:" + t1.getState());//NEWt1.start();System.out.println("B:" + t1.getState());//RUNNABLEThread.sleep(10);//0.01System.out.println("C:" + t1.getState());//TIMED_WAITING}
}
線程池
概述
-
沒有線程池的狀態:
當使用一條線程的時候,先將線程對象創建出來,啟動線程,在運行過程中,正常完成任務之后,線程對象就結束了,就變成了垃圾對象,需要被垃圾回收器回收如果在系統中,大量的任務都是小任務,任務消耗時間較短、線程對象的創建和消亡耗費的時間比較多,
結果:大部分的時間都浪費在了線程對象的創建和死亡上。
-
有線程池的狀態 在沒有任務的時候,先把線程對象準備好,存儲到一個容器中,一旦有任務來的時候,就不需要創建
對象,而是直接將對象獲取出來執行任務如果任務破壞力較小,任務可以直接完成,這個線程對象不
會進入死亡狀態,而是被容器回收,繼續活躍。如果任務破壞力較大,把線程任務搞死了,那么線程池
也會繼續提供下一個線程,繼續完成這個任務。
線程池使用
-
步驟:獲取線程池對象;創建任務類對象;將任務類對象提交到線程池中
-
獲取線程池對象:
工具類:
//Executors:生成線程池的工具類,根據需求生成指定大小的線程池 //ExecutorService Executors.newSingleThreadExecutor():創建一個有單個線程的線程池 //ExecutorService Executors.newFixedThreadPool(int nThreads):創建一個指定線程數量的線程池
-
創建任務類對象:Runnable的實現類對象,用于定義任務內容
-
將任務類對象提交到線程池中 ExecutorService:是一個接口,不需要手動創建這個接口的實現類對象,使用方法獲取到的就是這個接口的實現類對象,就可以調用這個接口中的方法
-
submit(Runnable r):可以將一個任務類對象,提交到線程池中,如果有空閑的線程,就可以馬上運行這個任務,如果沒有空閑線程,那么這個任務就需要等待。
-
shutDown():結束線程池,已經提交的全部保證完成,在此代碼后就不準繼續提交任務了
-
shutDownNow():結束線程池,已經開始運行的,保證完成,等待的任務不保證完成
但是已經提交的,還沒有運行的,就不給運行了,作為返回值范圍;對于沒有提交的,不準提交。
代碼示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
?
public class Simple02 {public static void main(String[] args) {// 準備一個線程池對象 1個// ExecutorService es = Executors.newSingleThreadExecutor();// 準備一個線程池對象 2個ExecutorService es = Executors.newFixedThreadPool(2);Object obj = new Object();Runnable r1 = new Runnable() {@Overridepublic void run() {synchronized (obj) {for (int i = 1; i <= 10; i++) {System.out.println(i + "..." + Thread.currentThread().getName());try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}}}}};Runnable r2 = new Runnable() {@Overridepublic void run() {synchronized (obj) {for (int i = 11; i <= 20; i++) {System.out.println(i + "..." + Thread.currentThread().getName());try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}}}}};Runnable r3 = new Runnable() {@Overridepublic void run() {synchronized (obj) {for (int i = 21; i <= 30; i++) {System.out.println(i + "..." + Thread.currentThread().getName());try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}}}}};
?es.submit(r1);es.submit(r2);es.submit(r3);// 已經提交的全部保證完成,不準繼續提交了
?// es.shutdown();// 已經提交的 前三個任務 保證完成,在shutdown之后就沒法添加任務了// es.submit(r3);// es.shutdownNow();// shutDownNow():結束線程池,已經開始運行的,保證完成,等待的任務不保證完成// 另外結束線程池這句話遇到任務中有sleep執行的時候就會有問題,會拋異常// 可以把任務sleep的代碼注釋上 就不報錯了}
}
?