一文讀懂Java多線程背后的故事
多線程的概念最早可以追溯到計(jì)算機(jī)科學(xué)的起源之一,也是第一個(gè)電子計(jì)算機(jī)ENIAC的開(kāi)發(fā)過(guò)程中。當(dāng)時(shí),計(jì)算速度相對(duì)較慢,為了充分利用CPU資源,信息科學(xué)家們開(kāi)始探索將一個(gè)任務(wù)拆分成多個(gè)小部分并同時(shí)執(zhí)行的方法。這就是多線程的雛形。
1970年代后期,操作系統(tǒng)出現(xiàn)了搶占式調(diào)度策略,這意味著一個(gè)進(jìn)程可以被打斷并轉(zhuǎn)而執(zhí)行另一個(gè)進(jìn)程,實(shí)現(xiàn)了不同的程序共享CPU時(shí)間。在這種情況下,處理器需要快速地切換上下文并管理多個(gè)進(jìn)程之間的共享資源,多線程編程變得更加重要。
隨著工業(yè)界軟件和互聯(lián)網(wǎng)的迅速發(fā)展,更多的應(yīng)用程序需要支持并發(fā)處理,以達(dá)到高效、實(shí)時(shí)的數(shù)據(jù)處理需求。而多線程編程正好可以提供這種并發(fā)性。
不僅如此,多核處理器技術(shù)的出現(xiàn)使得多線程編程變得更加必要。將單個(gè)進(jìn)程分解成多個(gè)線程并在多核處理器上同時(shí)運(yùn)行可以提高整體處理性能。
因此,多線程編程已經(jīng)成為現(xiàn)代計(jì)算機(jī)編程中重要的一部分,而隨著新型技術(shù)的出現(xiàn),如云計(jì)算、大數(shù)據(jù)等,多線程編程的重要性將會(huì)越來(lái)越突出。
1. 多線程的使用原則
在使用Java多線程的過(guò)程中,有一些原則需要遵守,以確保程序的正確性、可靠性和高效性。以下是幾個(gè)常見(jiàn)的多線程使用原則:
避免競(jìng)態(tài)條件:競(jìng)態(tài)條件是指當(dāng)兩個(gè)或更多的線程訪問(wèn)共享資源時(shí),由于執(zhí)行順序不確定而導(dǎo)致的問(wèn)題。為了避免競(jìng)態(tài)條件的發(fā)生,必須要使用同步機(jī)制(如synchronized關(guān)鍵字或Lock對(duì)象)和互斥量來(lái)保證線程的安全性。
最大化利用CPU資源:在多線程程序中,為了達(dá)到最高程度的并行ism,應(yīng)該將大任務(wù)分解成小任務(wù)并在多個(gè)線程上同時(shí)執(zhí)行,以充分利用CPU資源。
避免死鎖:死鎖是指當(dāng)兩個(gè)或多個(gè)線程相互等待對(duì)方的鎖時(shí),導(dǎo)致程序無(wú)限期地阻塞的情況。為了避免死鎖,應(yīng)該盡量避免嵌套鎖,以及使用統(tǒng)一的鎖順序來(lái)避免死鎖的發(fā)生。
減少上下文切換:上下文切換是指在多線程環(huán)境下,在處理器從一個(gè)線程轉(zhuǎn)換到另一個(gè)線程時(shí),需要保存當(dāng)前上下文并加載另一個(gè)線程的上下文。上下文切換的開(kāi)銷(xiāo)是很大的,因此應(yīng)該盡量減少上下文切換的次數(shù),例如通過(guò)使用線程池來(lái)共享線程資源。
避免線程饑餓:線程饑餓是指一個(gè)線程由于等待某些資源而無(wú)法執(zhí)行的情況。為了避免線程饑餓,可以使用公平鎖來(lái)保證所有的線程都有機(jī)會(huì)獲得資源,或者使用線程優(yōu)先級(jí)來(lái)控制線程的調(diào)度順序。
以上是多線程使用中的一些常見(jiàn)原則,實(shí)際的多線程編程中還有很多需要考慮的問(wèn)題,需要根據(jù)實(shí)際情況進(jìn)行相應(yīng)的處理。
2. 改善用戶體驗(yàn)
在 Web 應(yīng)用程序中,用戶希望盡快得到反饋信息,而長(zhǎng)時(shí)間的等待會(huì)導(dǎo)致用戶體驗(yàn)的下降。多線程可以提高 Web 應(yīng)用程序的響應(yīng)速度,從而改善用戶體驗(yàn)。例如,當(dāng)用戶請(qǐng)求一個(gè)數(shù)據(jù)集合時(shí),可以使用多線程技術(shù)在后臺(tái)異步加載數(shù)據(jù),然后立即顯示首頁(yè),同時(shí)在數(shù)據(jù)準(zhǔn)備好后再更新頁(yè)面。這樣用戶就能立即看到頁(yè)面,并在后臺(tái)加載完成時(shí)獲得更多數(shù)據(jù)。
下面是一個(gè)示例,演示了如何使用多線程技術(shù)實(shí)現(xiàn)異步加載數(shù)據(jù):
public class DataLoader implements Runnable {
private final DataService dataService;
private final Consumer<Data> onDataLoaded;
public DataLoader(DataService dataService, Consumer<Data> onDataLoaded) {
this.dataService = dataService;
this.onDataLoaded = onDataLoaded;
}
@Override
public void run() {
Data data = dataService.loadData();
onDataLoaded.accept(data);
}
}
public class PageController {
private final DataService dataService;
public PageController(DataService dataService) {
this.dataService = dataService;
}
public void displayPage() {
// 顯示頁(yè)面,提供反饋信息
showLoadingIndicator();
// 異步加載數(shù)據(jù)
DataLoader dataLoader = new DataLoader(dataService, data -> {
hideLoadingIndicator();
updatePageWithData(data);
});
Thread thread = new Thread(dataLoader);
thread.start();
}
}在這個(gè)代碼示例中,DataLoader 類(lèi)負(fù)責(zé)異步加載數(shù)據(jù),而 PageController 類(lèi)負(fù)責(zé)顯示頁(yè)面并提供反饋信息。當(dāng) PageController 操作時(shí),它會(huì)創(chuàng)建一個(gè)新線程來(lái)執(zhí)行 DataLoader,然后立即顯示頁(yè)面,并在數(shù)據(jù)準(zhǔn)備好后更新頁(yè)面。
3. 最大化利用 CPU 資源
使用多線程可以使 CPU 資源得到充分利用,從而提高程序的性能和效率。例如,在計(jì)算密集型任務(wù)中,將任務(wù)拆分成多個(gè)部分并使用多個(gè)線程并行執(zhí)行可以大大縮短任務(wù)完成時(shí)間。下面是一個(gè)示例,演示了如何在并行環(huán)境下計(jì)算斐波那契數(shù)列:
public class FibonacciTask implements Callable<Long> {
private final int n;
public FibonacciTask(int n) {
this.n = n;
}
@Override
public Long call() {
if (n <= 1) {
return (long) n;
} else {
FibonacciTask f1 = new FibonacciTask(n - 1);
FibonacciTask f2 = new FibonacciTask(n - 2);
ForkJoinTask.invokeAll(f1, f2);
return f1.join() + f2.join();
}
}
}
public class FibonacciExample {
public static void main(String[] args) throws Exception {
int n = 50;
FibonacciTask task = new FibonacciTask(n);
long result = ForkJoinPool.commonPool().invoke(task);
System.out.println("Fibonacci number at position " + n + " is " + result);
}
} 在這個(gè)代碼示例中,FibonacciTask 類(lèi)負(fù)責(zé)計(jì)算斐波那契數(shù)列。當(dāng) n 大于 1 時(shí),它將任務(wù)分解成兩個(gè)子任務(wù)并使用 ForkJoinTask.invokeAll() 方法并行執(zhí)行。FibonacciExample 類(lèi)負(fù)責(zé)啟動(dòng)計(jì)算,并使用 ForkJoinPool 類(lèi)中的公共池來(lái)執(zhí)行任務(wù)。這樣,就可以將任務(wù)拆分成多個(gè)部分并使用多個(gè)線程并行執(zhí)行,以最大化利用 CPU 資源。
4. 簡(jiǎn)化代碼實(shí)現(xiàn)
多線程技術(shù)還可以幫助簡(jiǎn)化復(fù)雜的應(yīng)用程序代碼。例如,在面向?qū)ο缶幊讨?,程序通常需要維護(hù)一些狀態(tài),這可能導(dǎo)致代碼變得復(fù)雜和難以理解。使用多線程技術(shù)可以分離狀態(tài)和處理過(guò)程,并將處理過(guò)程分解成多個(gè)線程以提高效率。下面是一個(gè)示例,演示了如何使用多線程技術(shù)簡(jiǎn)化代碼實(shí)現(xiàn)
public class BankAccount {
private int balance;
public synchronized void deposit(int amount) {
balance += amount;
}
public synchronized void withdraw(int amount) {
balance -= amount;
}
public synchronized int getBalance() {
return balance;
}
}
public class TransferTask implements Runnable {
private final BankAccount sourceAccount;
private final BankAccount targetAccount;
private final int amount;
public TransferTask(BankAccount sourceAccount, BankAccount targetAccount, int amount) {
this.sourceAccount = sourceAccount;
this.targetAccount = targetAccount;
this.amount = amount;
}
@Override
public void run() {
sourceAccount.withdraw(amount);
targetAccount.deposit(amount);
}
}
public class BankExample {
public static void main(String[] args) {
BankAccount account1 = new BankAccount();
BankAccount account2 = new BankAccount();
account1.deposit(1000);
TransferTask transferTask1 = new TransferTask(account1, account2, 500);
TransferTask transferTask2 = new TransferTask(account2, account1, 300);
Thread thread1 = new Thread(transferTask1);
Thread thread2 = new Thread(transferTask2);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Account 1 balance: " + account1.getBalance());
System.out.println("Account 2 balance: " + account2.getBalance());
}
}在這個(gè)代碼示例中,BankAccount 類(lèi)負(fù)責(zé)管理銀行賬戶的余額。每個(gè)方法都使用 synchronized 關(guān)鍵字來(lái)確保方法調(diào)用之間的互斥性。TransferTask 類(lèi)負(fù)責(zé)執(zhí)行轉(zhuǎn)賬操作。最后,BankExample 類(lèi)負(fù)責(zé)啟動(dòng)轉(zhuǎn)賬并輸出賬戶余額。
5. 多線程編程框架
多線程編程的框架通常是一些類(lèi)或接口的集合,其目的是為了幫助開(kāi)發(fā)人員更容易地編寫(xiě)并發(fā)代碼。這些框架提供了高層次的抽象,隱藏了多線程代碼中的復(fù)雜性和細(xì)節(jié),并使得程序員能夠更加專(zhuān)注于業(yè)務(wù)邏輯的實(shí)現(xiàn)。
以下是多線程框架中的幾個(gè)常見(jiàn)抽象:
線程池:線程池用來(lái)管理一組已經(jīng)創(chuàng)建的線程,以便在程序需要時(shí)重復(fù)利用它們。線程池的抽象類(lèi)通常包含上限和下限線程數(shù)、任務(wù)隊(duì)列、拒絕策略等屬性,以及方法來(lái)提交任務(wù),啟動(dòng)線程池等。使用線程池可以大大提高系統(tǒng)的效率,降低線程創(chuàng)建與銷(xiāo)毀的開(kāi)銷(xiāo)。
并發(fā)集合:并發(fā)集合可以讓多個(gè)線程安全地訪問(wèn)同一個(gè)集合。Java并發(fā)框架提供了一些并發(fā)集合,例如ConcurrentHashMap、ConcurrentLinkedQueue等。這些集合實(shí)現(xiàn)了線程安全的讀寫(xiě)操作,同時(shí)不會(huì)導(dǎo)致鎖競(jìng)爭(zhēng)。
信號(hào)量(Semaphore):信號(hào)量是一種控制并發(fā)訪問(wèn)資源的機(jī)制,它可以保證在任何時(shí)刻,只有指定數(shù)量的線程能夠訪問(wèn)共享資源。Java并發(fā)框架中的Semaphore類(lèi)提供了這個(gè)機(jī)制的實(shí)現(xiàn),通過(guò)acquire()方法獲取信號(hào)量,release()方法釋放信號(hào)量。Semaphore可以應(yīng)用于限流、資源控制等場(chǎng)景。
同步器(Synchronizer):同步器是一種比鎖更高級(jí)的并發(fā)控制機(jī)制,它可以用來(lái)實(shí)現(xiàn)各種復(fù)雜的同步和互斥需求。Java并發(fā)框架中的ReentrantLock、CountDownLatch和CyclicBarrier等就是同步器的典型實(shí)現(xiàn)。
Fork/Join框架:Fork/Join框架是Java 7中引入的并發(fā)框架,它建立在Executor框架之上,特別適合處理計(jì)算密集型的任務(wù)。該框架采用分治策略,將大問(wèn)題拆分成多個(gè)小問(wèn)題,并在多個(gè)線程上并行執(zhí)行,最終將結(jié)果合并返回。
多線程編程框架的抽象層次越高,越能夠幫助我們避免寫(xiě)出低效、高風(fēng)險(xiǎn)的代碼。因此,在編寫(xiě)多線程應(yīng)用程序時(shí),使用已經(jīng)存在的高級(jí)別的抽象是一個(gè)非常明智的選擇。
6. 總結(jié)
本文介紹了 Java 多線程的實(shí)際應(yīng)用場(chǎng)景,包括改善用戶體驗(yàn)、最大化利用 CPU 資源和簡(jiǎn)化代碼實(shí)現(xiàn)等。我只是涉及了一些比較基礎(chǔ)的應(yīng)用場(chǎng)景介紹它,還有很多其他領(lǐng)域可以使用 Java 多線程進(jìn)行優(yōu)化。Java 多線程是 Java 程序員必備的技能之一,深入理解多線程技術(shù)的應(yīng)用場(chǎng)景可以幫助您編寫(xiě)更高效、更健壯的程序,很開(kāi)心能夠分享這些。
以上就是一文讀懂Java多線程背后的故事的詳細(xì)內(nèi)容,更多關(guān)于Java多線程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
MyBatis批量添加數(shù)據(jù)2種實(shí)現(xiàn)方法
這篇文章主要介紹了MyBatis批量添加數(shù)據(jù)2種實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
SpringAOP事務(wù)配置語(yǔ)法及實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了SpringAOP事務(wù)配置語(yǔ)法及實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06
Springboot整合JPA配置多數(shù)據(jù)源流程詳解
這篇文章主要介紹了Springboot整合JPA配置多數(shù)據(jù)源,JPA可以通過(guò)實(shí)體類(lèi)生成數(shù)據(jù)庫(kù)的表,同時(shí)自帶很多增刪改查方法,大部分sql語(yǔ)句不需要我們自己寫(xiě),配置完成后直接調(diào)用方法即可,很方便2022-11-11
Spring Boot與Spark、Cassandra系統(tǒng)集成開(kāi)發(fā)示例
本文演示以Spark作為分析引擎,Cassandra作為數(shù)據(jù)存儲(chǔ),而使用Spring Boot來(lái)開(kāi)發(fā)驅(qū)動(dòng)程序的示例。對(duì)spring boot 與spark cassandra集成開(kāi)發(fā)示例代碼感興趣的朋友跟著腳本之家小編一起學(xué)習(xí)吧2018-02-02
Spring如何基于注解顯式實(shí)現(xiàn)自動(dòng)裝配
這篇文章主要介紹了Spring如何基于注解顯式實(shí)現(xiàn)自動(dòng)裝配,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
mybatis plus 的動(dòng)態(tài)表名的配置詳解
這篇文章主要介紹了mybatis plus 的動(dòng)態(tài)表名的配置詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
基于springBoot配置文件properties和yml中數(shù)組的寫(xiě)法
這篇文章主要介紹了springBoot配置文件properties和yml中數(shù)組的寫(xiě)法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11

