Java多線程的具體介紹與使用筆記小結(jié)
一、基本概念:線程、進程
1.1、進程與線程的具體介紹
線程(thread),進程可進一步細化為線程,是一個程序內(nèi)部的一條執(zhí)行路徑。
- 若一個進程同一時間并行執(zhí)行多個線程,就是支持多線程的
- 線程作為調(diào)度和執(zhí)行的單位,每個線程擁有獨立的運行棧和程序計數(shù)器(pc),線程切換的開銷小
- 一個進程中的多個線程共享相同的內(nèi)存單元/內(nèi)存地址空間à它們從同一堆中分配對象,可以訪問相同的變量和對象。這就使得線程間通信更簡便、高效。但多個線程操作共享的系統(tǒng)資源可能就會帶來安全的隱患。
進程(process),是程序的一次執(zhí)行過程,或是正在運行的一個程序。是一個動態(tài)的過程:有它自身的產(chǎn)生、存在和消亡的過程?!芷?/p>
- 運行中的QQ,運行中的MP3播放器
- 程序是靜態(tài)的,進程是動態(tài)的進程作為資源分配的單位,系統(tǒng)在運行時會為每個
- 進程分配不同的內(nèi)存區(qū)域
1.2、對于CPU而言的理解
單核CPU和多核CPU的理解
- 單核CPU,其實是因為在一個時間單元內(nèi),也只能執(zhí)行一個線程的任務(wù)。例如:雖然有多車道,但是一種假的多線程,收費站只有一個工作人員在收費,只有收了費才能通過,那么CPU就好比收費人員。如果有某個人不想交錢,那么收費人員可以把他“掛起”(晾著他,等他想通了,準備好了錢,再去收費)。但是因為CPU時間單元特別短,因此感覺不出來。
- 如果是多核的話,才能更好的發(fā)揮多線程的效率。(現(xiàn)在的服務(wù)器都是多核的)
- 一個 Java 應(yīng)用程序 java.exe,其實至少有三個線程:main() 主線程,gc() 垃圾回收線程,異常處理線程。當然如果發(fā)生異常,會影響主線程。
并行與并發(fā)
- 并行:多個 CPU 同時執(zhí)行多個任務(wù)。比如:多個人同時做不同的事。
- 并發(fā):一個 CPU (采用時間片)同時執(zhí)行多個任務(wù)。比如:秒殺、多個人做同一件事。
1.3、為什么要使用多線程
- 背景:
以單核CPU 為例,只使用單個線程先后完成多個任務(wù)(調(diào)用多個方法),肯定比用多個線程來完成用的時間更短,為何仍需多線程呢?
多線程程序的優(yōu)點:
- 提高應(yīng)用程序的響應(yīng)。對圖形化界面更有意義,可增強用戶體驗。
- 提高計算機系統(tǒng) CPU 的利用率
- 改善程序結(jié)構(gòu)。將既長又復(fù)雜的進程分為多個線程,獨立運行,利于理解和修改
二、線程的創(chuàng)建與使用
2.1、如何去創(chuàng)建和啟動一個線程
- Java語言的 JVM 允許程序運行多個線程,它通過 java.lang.Thread 類來體現(xiàn)。
- Thread 類的特性:
每個線程都是通過某個特定 Thread 對象的 run() 方法來完成操作的,經(jīng)常把 run() 方法的主體稱為線程體通過該 Thread 對象的 start() 方法來啟動這個線程,而非直接調(diào)用 run()
2.2、Thread類的具體分析
構(gòu)造器:
Thread():創(chuàng)建新的 Thread 對象
Thread(String threadname):創(chuàng)建線程并指定線程實例名
Thread(Runnable target):指定創(chuàng)建線程的目標對象,它實現(xiàn)了 Runnable 接口中的 run 方法
Thread(Runnable target, String name):創(chuàng)建新的 Thread 對象
創(chuàng)建線程的兩種方式:
JDK1.5 之前創(chuàng)建新執(zhí)行線程有兩種方法:
繼承 Thread 類的方式
實現(xiàn) Runnable 接口的方式
方式一:繼承 Thread 類
定義子類繼承 Thread 類。
子類中重寫 Thread 類中的 run 方法。
創(chuàng)建 Thread 子類對象,即創(chuàng)建了線程對象。
調(diào)用線程對象 start 方法:啟動線程,調(diào)用 run 方法。
代碼演示:
* 多線程的創(chuàng)建,方式一:繼承于Thread類 * 1. 創(chuàng)建一個繼承于Thread類的子類 * 2. 重寫Thread類的run() --> 將此線程執(zhí)行的操作聲明在run()中 * 3. 創(chuàng)建Thread類的子類的對象 * 4. 通過此對象調(diào)用start() * 例子:遍歷100以內(nèi)的所有的偶數(shù) //1、創(chuàng)建一個繼承于Thread類的子類 class Thread01 extends Thread { //2、重寫Thread類的run() @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest { public static void main(String[] args) { //3、創(chuàng)建Thread類的子類的對象 Thread01 thread01 = new Thread01(); //4.通過此對象調(diào)用start() thread01.start(); //創(chuàng)建第二個線程 Thread01 thread02 = new Thread01(); //注意:這里我們不能直接手動調(diào)用 run()方法 //thread01.run(); //注意:當我們再次調(diào)用start()時會直接報錯:IllegalThreadStateException,所以一個線程只能用一次 //thread01.start(); thread02.start(); //當前操作仍然是在main線程中執(zhí)行 for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":" + i + "主線程被執(zhí)行了"); } } } }
方式二:實現(xiàn) Runnable 接口
- 定義子類,實現(xiàn) Runnable 接口。
- 子類中重寫 Runnable 接口中的 run 方法。
- 通過 Thread 類含參構(gòu)造器創(chuàng)建線程對象。
- 將 Runnable 接口的子類對象作為實際參數(shù)傳遞給 Thread 類的構(gòu)造器中。
- 調(diào)用 Thread 類的 start 方法:開啟線程,調(diào)用 Runnable 子類接口的 run 方法。
2.3、兩種實現(xiàn)方式的聯(lián)系與區(qū)別
聯(lián)系:
Thread 內(nèi)部其實也是實現(xiàn)了 Runnable 接口
區(qū)別:
- 繼承 Thread: 線程代碼存放 Thread子類 run 方法中。
- 實現(xiàn) Runnable: 線程代碼存在接口的子類的 run 方法。
實現(xiàn)方式的好處:
- 避免了單繼承的局限性
- 多個線程可以共享同一個接口實現(xiàn)類的對象,非常適合多個相同線程來處理同一份資源。
2.4、注意事項:
- 如果自己手動調(diào)用 run() 方法,那么就只是普通方法,沒有啟動多線程模式。
- run() 方法由 JVM 調(diào)用,什么時候調(diào)用,執(zhí)行的過程控制都有操作系統(tǒng)的 CPU 調(diào)度決定。
- 想要啟動多線程,必須調(diào)用 start 方法。
- 一個線程對象只能調(diào)用一次 start() 方法啟動,如果重復(fù)調(diào)用了,則將拋出以上的異常 IllegalThreadStateException。
2.5、代碼測試
需求:創(chuàng)建兩個分線程,讓其中一個線程輸出1-100之間的偶數(shù),另一個線程輸出1-100之間的奇數(shù)。
public class ThreadTest01 { public static void main(String[] args) { //線程一:偶數(shù) new Thread() { @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + "線程一偶數(shù)的執(zhí)行數(shù):" + i); } } } }.start(); //線程二:奇數(shù) new Thread() { @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 != 0) { System.out.println(Thread.currentThread().getName() + "線程二奇數(shù)的執(zhí)行數(shù):" + i); } } } }.start(); } }
2.6、Thread類的相關(guān)方法
void start(): 啟動線程,并執(zhí)行對象的 run() 方法
run(): 線程在被調(diào)度時執(zhí)行的操作String
getName(): 返回線程的名稱
void setName(String name): 設(shè)置該線程名稱
static Thread currentThread(): 返回當前線程。在 Thread 子類中就是 this,通常用于主線程和 Runnable 實現(xiàn)類static void yield(): 線程讓步
暫停當前正在執(zhí)行的線程,把執(zhí)行機會讓給優(yōu)先級相同或更高的線程若隊列中沒有同優(yōu)先級的線程,忽略此方法
join() : 當某個程序執(zhí)行流中調(diào)用其他線程的 join() 方法時,調(diào)用線程將被阻塞,直到 join() 方法加入的 join 線程執(zhí)行完為止
低優(yōu)先級的線程也可以獲得執(zhí)行
static void sleep(long millis):(指定時間:毫秒)
令當前活動線程在指定時間段內(nèi)放棄對 CPU 控制,使其他線程有機會被執(zhí)行,時間到后重排隊。
拋出 InterruptedException 異常
stop(): 強制線程生命期結(jié)束,不推薦使用
boolean isAlive(): 返回 boolean,判斷線程是否還活著
2.7、測試以上方法:
* @description: 多線程具體方法的使用 * @date 2021/4/16 18:32 */ class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i); //設(shè)置線程睡眠時間 /* try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }*/ } //當滿足當前條件時,終止線程 /*if (i % 20 == 0) { yield(); }*/ } } } public class ThreadMethodTest { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); //給主線程命名 thread.setName("我是線程一號"); //將分線程優(yōu)先級設(shè)置成最大 thread.setPriority(Thread.MAX_PRIORITY); Thread.currentThread().setName("我是主線程"); //將主線程的優(yōu)先級設(shè)置成最小 Thread.currentThread().setPriority(Thread.MIN_PRIORITY); for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i); } //join()表示:線程A中調(diào)用線程B的join()方法,那么此時線程A進入阻塞狀態(tài),直到線程B完全執(zhí)行完以后,線程A才 //結(jié)束阻塞狀態(tài)。 /*if (i == 20) { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } }*/ } //isAlive():判斷當前線程是否存活 // System.out.println("當前線程是否存活:" + thread.isAlive()); } }
結(jié)果:
使用 sleep() 方法時的結(jié)果
使用 Join方法時的結(jié)果
三、線程的生命周期
JDK中用Thread.State類定義了線程的幾種狀態(tài):
要想實現(xiàn)多線程,必須在主線程中創(chuàng)建新的線程對象。Java 語言使用 Thread 類及其子類的對象來表示線程,在它的一個完整的生命周期中通常要經(jīng)歷如下的五種狀態(tài):
新建: 當一個 Thread 類或其子類的對象被聲明并創(chuàng)建時,新生的線程對象處于新建狀態(tài)
就緒: 處于新建狀態(tài)的線程被 start() 后,將進入線程隊列等待 CPU 時間片,此時它已具備了運行的條件,只是沒分配到 CPU 資源
運行: 當就緒的線程被調(diào)度并獲得 CPU 資源時,便進入運行狀態(tài), run() 方法定義了線程的操作和功能
阻塞: 在某種特殊情況下,被人為掛起或執(zhí)行輸入輸出操作時,讓出 CPU 并臨時中止自己的執(zhí)行,進入阻塞狀態(tài)
死亡: 線程完成了它的全部工作或線程被提前強制性地中止或出現(xiàn)異常導致結(jié)束
生命周期結(jié)構(gòu)圖
四、線程的同步
4.1、為什么要線程同步
- 多個線程執(zhí)行的不確定性引起執(zhí)行結(jié)果的不穩(wěn)定
- 多個線程對賬本的共享,會造成操作的不完整性,會破壞數(shù)據(jù)
4.2、舉例說明:
創(chuàng)建三個窗口賣票,總票數(shù)為100張.使用繼承Thread類的方式
class Window extends Thread { private static int ticket = 100; @Override public void run() { while (true) { if (ticket > 0) { System.out.println(getName() + "買票編碼號為:" + ticket); ticket--; } else { break; } } } } public class TicketWindowTest { public static void main(String[] args) { //創(chuàng)建線程 Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); //設(shè)置線程名字 w1.setName("窗口一:"); w2.setName("窗口二:"); w3.setName("窗口三:"); //開啟線程 w1.start(); w2.start(); w3.start(); } }
正常情況下的效果:
不正常的情況下的效果:
上段代碼出現(xiàn)的漏洞:
問題: 賣票過程中,出現(xiàn)了重票、錯票,出現(xiàn)了線程的安全問題
原因: 當某個線程操作車票的過程中,尚未操作完成時,其他線程參與進來,也操作車票
解決: 當一個線程 a 在操作 ticket 的時候,其他線程不能參與進來。直到線程 a 操作完 ticket 時,其他線程才可以開始操作 ticket。這種情況即使線程a出現(xiàn)了阻塞,也不能被改變
4.3、Synchronized的使用方法
Java對于多線程的安全問題提供了專業(yè)的解決方式:同步機制
1、同步代碼塊: synchronized (對象){ // 需要被同步的代碼; } 2、synchronized還可以放在方法聲明中,表示整個方法為同步方法。 例如: public synchronized void show (String name){ …. }
4.3、 同步機制中的鎖
同步鎖機制:
在《Thinking in Java》中,是這么說的: 對于并發(fā)工作,你需要某種方式來防止兩個任務(wù)訪問相同的資源(其實就是共享資源競爭)。 防止這種沖突的方法就是當資源被一個任務(wù)使用時,在其上加鎖。第一個訪問某項資源的任務(wù)必須鎖定這項資源,使其他任務(wù)在其被解鎖之前,就無法訪問它了,而在其被解鎖之時,另一個任務(wù)就可以鎖定并使用它了。
synchronized的鎖是什么?
- 任意對象都可以作為同步鎖,所有對象都自動含有單一的鎖(監(jiān)視器)
- 同步方法的鎖: 靜態(tài)方法(類名.class)、非靜態(tài)方法(this)
- 同步代碼塊: 自己指定,很多時候也是指定為this或類名.class
注意:
- 必須確保使用同一個資源的多個線程共用一把鎖,這個非常重要,否則就無法保證共享資源的安全
- 一個線程類中的所有靜態(tài)方法共用同一把鎖(類名.class),所有非靜態(tài)方法共用同一把鎖(this),同步代碼塊(指定需謹慎)
4.4、同步的范圍
如何找問題,即代碼是否存在線程安全?(非常重要)
- 明確哪些代碼是多線程運行的代碼
- 明確多個線程是否有共享數(shù)據(jù)
- 明確多線程運行代碼中是否有多條語句操作共享數(shù)據(jù)
如何解決呢?(非常重要)
對多條操作共享數(shù)據(jù)的語句,只能讓一個線程都執(zhí)行完,在執(zhí)行過程中,其他線程不可以參與執(zhí)行。即所有操作共享數(shù)據(jù)的這些語句都要放在同步范圍中
注意:
- 范圍太?。?沒鎖住所有有安全問題的代碼
- 范圍太大: 沒發(fā)揮多線程的功能。
對于購票代碼的 bug 改進
class Windows02 implements Runnable { private int ticke = 100; @Override public void run() { while (true) { //使用同步代碼方式來解決線程安全問題,this:表示當前對象:【W(wǎng)indows02】 // synchronized (this) { // if (ticke > 0) { // try { // Thread.sleep(100); // } catch (InterruptedException e) { // e.printStackTrace(); // } // System.out.println(Thread.currentThread().getName() + "購票號碼為:" + ticke); // ticke--; // } else { // break; // } // } show(); } } //這里我們直接使用【同步方法】的方式來處理線程安全問題 //在方法中加入:synchronized的效果等同上面的this,因為指代的都是當前對象,只是在同步方法中幫我們做了隱試操作。 private synchronized void show() { if (ticke > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "購票號碼為:" + ticke); ticke--; } } } public class TickeWindowTest02 { public static void main(String[] args) { Windows02 windows = new Windows02(); Thread thread = new Thread(windows); Thread thread01 = new Thread(windows); Thread thread02 = new Thread(windows); thread.setName("窗口一:"); thread01.setName("窗口二:"); thread02.setName("窗口三:"); thread.start(); thread01.start(); thread02.start(); } }
測試結(jié)果:
4.5、釋放鎖的操作
- 當前線程的同步方法、同步代碼塊執(zhí)行結(jié)束。
- 當前線程在同步代碼塊、同步方法中遇到 break、return 終止了該代碼塊、該方法的繼續(xù)執(zhí)行。
- 當前線程在同步代碼塊、同步方法中出現(xiàn)了未處理的 Error 或 Exception,導致異常結(jié)束。
- 當前線程在同步代碼塊、同步方法中執(zhí)行了線程對象的 wait() 方法,當前線程暫停,并釋放鎖。
4.6、不釋放鎖的操作
- 線程執(zhí)行同步代碼塊或同步方法時,程序調(diào)用 Thread.sleep()、Thread.yield() 方法暫停當前線程的執(zhí)行
- 線程執(zhí)行同步代碼塊時,其他線程調(diào)用了該線程的 suspend() 方法將該線程掛起,該線程不會釋放鎖(同步監(jiān)視器)。
應(yīng)盡量避免使用 suspend() 和 resume() 來控制線程
4.7、線程的死鎖的問題
1、 什么事死鎖
不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖
出現(xiàn)死鎖后,不會出現(xiàn)異常,不會出現(xiàn)提示,只是所有的線程都處于阻塞狀態(tài),無法繼續(xù)。
代碼演示:
//死鎖的演示 class A { public synchronized void foo(B b) { //同步監(jiān)視器:A類的對象:a System.out.println("當前線程名: " + Thread.currentThread().getName() + " 進入了A實例的foo方法"); // ① // try { // Thread.sleep(200); // } catch (InterruptedException ex) { // ex.printStackTrace(); // } System.out.println("當前線程名: " + Thread.currentThread().getName() + " 企圖調(diào)用B實例的last方法"); // ③ b.last(); } public synchronized void last() {//同步監(jiān)視器:A類的對象:a System.out.println("進入了A類的last方法內(nèi)部"); } } class B { public synchronized void bar(A a) {//同步監(jiān)視器:b System.out.println("當前線程名: " + Thread.currentThread().getName() + " 進入了B實例的bar方法"); // ② // try { // Thread.sleep(200); // } catch (InterruptedException ex) { // ex.printStackTrace(); // } System.out.println("當前線程名: " + Thread.currentThread().getName() + " 企圖調(diào)用A實例的last方法"); // ④ a.last(); } public synchronized void last() {//同步監(jiān)視器:b System.out.println("進入了B類的last方法內(nèi)部"); } } public class DeadLock implements Runnable { A a = new A(); B b = new B(); public void init() { Thread.currentThread().setName("主線程"); // 調(diào)用a對象的foo方法 a.foo(b); System.out.println("進入了主線程之后"); } public void run() { Thread.currentThread().setName("副線程"); // 調(diào)用b對象的bar方法 b.bar(a); System.out.println("進入了副線程之后"); } public static void main(String[] args) { DeadLock dl = new DeadLock(); new Thread(dl).start(); dl.init(); } }
測試結(jié)果:概率性的出現(xiàn)
2、解決方法
- 專門的算法、原則
- 盡量減少同步資源的定義盡量
- 避免嵌套同步
3、什么是Lock鎖
- 從 JDK 5.0 開始,Java 提供了更強大的線程同步機制——通過顯式定義同步鎖對象來實現(xiàn)同步。同步鎖使用 Lock 對象充當。
- java.util.concurrent.locks.Lock 接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨占訪問,每次只能有一個線程對 Lock 對象加鎖,線程開始訪問共享資源之前應(yīng)先獲得 Lock 對象。
- ReentrantLock 類實現(xiàn)了 Lock ,它擁有與 synchronized 相同的并發(fā)性和內(nèi)存語義,在實現(xiàn)線程安全的控制中,比較常用的是 ReentrantLock,可以顯式加鎖、釋放鎖。
4、具體如何使用:
5、synchronized 與 Lock 鎖有何區(qū)別
Lock 是顯式鎖(手動開啟和關(guān)閉鎖,別忘記關(guān)閉鎖),synchronized 是隱式鎖,出了作用域自動釋放
Lock 只有代碼塊鎖,synchronized 有代碼塊鎖和方法鎖
使用 Lock 鎖,JVM將花費較少的時間來調(diào)度線程,性能更好。并且具有更好的擴展性(提供更多的子類)
優(yōu)先使用順序:
Lock →同步代碼塊(已經(jīng)進入了方法體,分配了相應(yīng)資源) → 同步方法(在方法體之外)
五、線程的通信
5.1、方法介紹與注意事項:
wait() 與 notify() 和 notifyAll()
wait(): 令當前線程掛起并放棄 CPU、同步資源并等待,使別的線程可訪問并修改共享資源,而當前線程排隊等候其他線程調(diào)用 notify()或notifyAll() 方法喚醒,喚醒后等待重新獲得對監(jiān)視器的所有權(quán)后才能繼續(xù)執(zhí)行。
notify(): 喚醒正在排隊等待同步資源的線程中優(yōu)先級最高者結(jié)束等待
notifyAll (): 喚醒正在排隊等待資源的所有線程結(jié)束等待.
注意事項:
這三個方法只有在 synchronized 方法或 synchronized 代碼塊中才能使用,否則會報 java.lang.IllegalMonitorStateException 異常。
因為這三個方法必須有鎖對象調(diào)用,而任意對象都可以作為 synchronized 的同步鎖,因此這三個方法只能在 Object 類中聲明。
sleep() 和 wait() 有何不同之處:
相同點: 一旦執(zhí)行方法,都可以使得當前的線程進入阻塞狀態(tài)。
不同點:
兩個方法聲明的位置不同: Thread 類中聲明 sleep() , Object 類中聲明 wait()
調(diào)用的要求不同: sleep() 可以在任何需要的場景下調(diào)用。 wait() 必須使用在同步代碼塊或同步方法中
關(guān)于是否釋放同步監(jiān)視器: 如果兩個方法都使用在同步代碼塊或同步方法中,sleep() 不會釋放鎖,wait()會釋放鎖。
案例一:
使用兩個線程打印 1-100。線程1, 線程2 交替打印
class Number implements Runnable { private int number = 1; @Override public void run() { while (true) { synchronized (this) { //因為現(xiàn)在使用的是當前對象,所以前面省略this. //如果使用的是其他對象,那么就用對象.的方式去調(diào)用該方法 notify(); if (number <= 100) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " :" + "打印數(shù)為" + number); number++; try { //調(diào)用該方法時,線程進入阻塞狀態(tài) wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } } } public class CommunicationTest { public static void main(String[] args) { Number number = new Number(); Thread thread = new Thread(number); Thread thread01 = new Thread(number); thread.setName("線程一"); thread01.setName("線程二"); thread.start(); thread01.start(); } }
執(zhí)行結(jié)果:
六、JDK5.0新增的線程創(chuàng)建方式
新增方式一:實現(xiàn)Callable接口
與使用 Runnable 相比, Callable 功能更強大些
相比 run() 方法,可以有返回值
方法可以拋出異常
支持泛型的返回值需要
借助 FutureTask 類,比如獲取返回結(jié)果
- Future 接口
可以對具體 Runnable、Callable 任務(wù)的執(zhí)行結(jié)果進行取消、查詢是否完成、獲取結(jié)果等。
FutrueTask 是 Futrue 接口的唯一的實現(xiàn)類
FutureTask 同時實現(xiàn)了 Runnable, Future 接口。它既可以作為 Runnable 被線程執(zhí)行,又可以作為 Future 得到 Callable 的返回值
- 新增方式二:使用線程池
沒使用線程池: 經(jīng)常創(chuàng)建和銷毀、使用量特別大的資源,比如并發(fā)情況下的線程,對性能影響很大。
使用線程池后: 提前創(chuàng)建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創(chuàng)建銷毀、實現(xiàn)重復(fù)利用,類似生活中的公共交通工具。
- 好處:
提高響應(yīng)速度(減少了創(chuàng)建新線程的時間)
降低資源消耗(重復(fù)利用線程池中線程,不需要每次都創(chuàng)建)
便于線程管理
corePoolSize: 核心池的大小
maximumPoolSize: 最大線程數(shù)
keepAliveTime: 線程沒有任務(wù)時最多保持多長時間后會終止
- 線程池相關(guān)的API
JDK 5.0 起提供了線程池相關(guān) API:ExecutorService 和 Executors
ExecutorService: 真正的線程池接口。常見子類 ThreadPoolExecutor
void execute(Runnable command) : 執(zhí)行任務(wù)命令,沒有返回值,一般用來執(zhí)行 Runnable
Future submit(Callable task): 執(zhí)行任務(wù),有返回值,一般又來執(zhí)行 Callable
- void shutdown() : 關(guān)閉連接池
Executors: 工具類、線程池的工廠類,用于創(chuàng)建并返回不同類型的線程池
Executors.newCachedThreadPool(): 創(chuàng)建一個可根據(jù)需要創(chuàng)建新線程的線程池
Executors.newFixedThreadPool(n): 創(chuàng)建一個可重用固定線程數(shù)的線程池
Executors.newSingleThreadExecutor() : 創(chuàng)建一個只有一個線程的線程池
Executors.newScheduledThreadPool(n): 創(chuàng)建一個線程池,它可安排在給定延遲后運行命令或者定期地執(zhí)行。
- 代碼演示:
創(chuàng)建線程的方式三:使用 Callable 接口
//1.創(chuàng)建一個實現(xiàn)Callable的實現(xiàn)類 class NumThread implements Callable<Integer> { //2.實現(xiàn)call方法,將此線程需要執(zhí)行的操作聲明在call()中 @Override public Integer call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { if(i % 2 == 0){ System.out.println(i); sum += i; } } return sum; } } public class ThreadNew { public static void main(String[] args) { //3.創(chuàng)建Callable接口實現(xiàn)類的對象 NumThread numThread = new NumThread(); //4.將此Callable接口實現(xiàn)類的對象作為傳遞到FutureTask構(gòu)造器中,創(chuàng)建FutureTask的對象 FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread); //5.將FutureTask的對象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread對象,并調(diào)用start() new Thread(futureTask).start(); try { //6.獲取Callable中call方法的返回值 //get()返回值即為FutureTask構(gòu)造器參數(shù)Callable實現(xiàn)類重寫的call()的返回值。 Integer sum = futureTask.get(); System.out.println("總和為:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
創(chuàng)建線程方式四:使用線程池技術(shù)
class NumberThread implements Runnable{ @Override public void run() { for(int i = 0;i <= 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ": " + i); } } } } class NumberThread1 implements Runnable{ @Override public void run() { for(int i = 0;i <= 100;i++){ if(i % 2 != 0){ System.out.println(Thread.currentThread().getName() + ": " + i); } } } } public class ThreadPool { public static void main(String[] args) { //1. 提供指定線程數(shù)量的線程池 ExecutorService service = Executors.newFixedThreadPool(10); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //設(shè)置線程池的屬性 // System.out.println(service.getClass()); // service1.setCorePoolSize(15); // service1.setKeepAliveTime(); //2.執(zhí)行指定的線程的操作。需要提供實現(xiàn)Runnable接口或Callable接口實現(xiàn)類的對象 service.execute(new NumberThread());//適合適用于Runnable service.execute(new NumberThread1());//適合適用于Runnable // service.submit(Callable callable);//適合使用于Callable //3.關(guān)閉連接池 service.shutdown(); } }
到此這篇關(guān)于Java多線程的具體介紹與使用筆記小結(jié)的文章就介紹到這了,更多相關(guān)java多線程介紹與使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java多線程累加計數(shù)的實現(xiàn)方法
- Java多線程之Interrupt中斷線程詳解
- JAVA多線程中join()方法的使用方法
- Java實現(xiàn)多線程中的靜態(tài)代理模式
- Java進階必備之多線程編程
- java如何實現(xiàn)多線程的順序執(zhí)行
- 總結(jié)java多線程之互斥與同步解決方案
- 如何解決Java多線程死鎖問題
- java多線程創(chuàng)建及線程安全詳解
- Java多線程之線程同步
- Java多線程之ReentrantReadWriteLock源碼解析
- Java多線程之線程的創(chuàng)建
- Java多線程之synchronized關(guān)鍵字的使用
- Java多線程之Disruptor入門
- Java基礎(chǔ)之多線程的三種實現(xiàn)方式
- Java多線程之Park和Unpark原理
- Java多線程之深入理解ReentrantLock
- Java實戰(zhàn)之多線程模擬站點售票
相關(guān)文章
SpringBoot實現(xiàn)自定義Starter的步驟詳解
在SpringBoot中,Starter是一種特殊的依賴,它可以幫助我們快速地集成一些常用的功能,例如數(shù)據(jù)庫連接、消息隊列、Web框架等。在本文中,我們將介紹如何使用Spring Boot實現(xiàn)自定義Starter,需要的朋友可以參考下2023-06-06Spring Security實現(xiàn)基于角色的訪問控制框架
Spring Security是一個功能強大的安全框架,提供了基于角色的訪問控制、身份驗證、授權(quán)等安全功能,可輕松保護Web應(yīng)用程序的安全,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2023-04-04Javabean轉(zhuǎn)換成json字符并首字母大寫代碼實例
這篇文章主要介紹了javabean轉(zhuǎn)成json字符并首字母大寫代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-02-02Java中的CopyOnWriteArrayList原理詳解
這篇文章主要介紹了Java中的CopyOnWriteArrayList原理詳解,如源碼所示,CopyOnWriteArrayList和ArrayList一樣,都在內(nèi)部維護了一個數(shù)組,操作CopyOnWriteArrayList其實就是在操作內(nèi)部的數(shù)組,需要的朋友可以參考下2023-12-12springboot下添加全局異常處理和自定義異常處理的過程解析
在spring項目中,優(yōu)雅處理異常,好處是可以將系統(tǒng)產(chǎn)生的全部異常統(tǒng)一捕獲處理,自定義的異常也由全局異常來捕獲,如果涉及到validator參數(shù)校驗器使用全局異常捕獲也是較為方便,這篇文章主要介紹了springboot下添加全局異常處理和自定義異常處理,需要的朋友可以參考下2023-12-12