Java并發(fā)編程最佳實踐之構(gòu)建高并發(fā)、可伸縮的應(yīng)用實例
一、引言
在現(xiàn)代應(yīng)用開發(fā)中,并發(fā)編程已經(jīng)成為一個不可或缺的技能。隨著多核處理器的普及,充分利用系統(tǒng)資源、提高應(yīng)用性能成為了開發(fā)者的重要目標(biāo)。Java 作為企業(yè)級應(yīng)用的主流語言,提供了豐富的并發(fā)編程工具和 API。今天,我想和大家分享一下 Java 并發(fā)編程的最佳實踐,幫助大家構(gòu)建高并發(fā)、可伸縮的應(yīng)用。
二、并發(fā)編程基礎(chǔ)
1. 線程安全
- 原子性:操作要么全部執(zhí)行,要么全部不執(zhí)行
- 可見性:一個線程對共享變量的修改,其他線程能夠立即看到
- 有序性:程序執(zhí)行的順序按照代碼的先后順序執(zhí)行
2. 并發(fā)工具類
基本工具:
- synchronized:同步關(guān)鍵字
- volatile:保證可見性
- final:不可變對象
并發(fā)集合:
- ConcurrentHashMap:線程安全的哈希表
- CopyOnWriteArrayList:寫時復(fù)制的數(shù)組
- BlockingQueue:阻塞隊列
線程池:
- Executors:線程池工廠
- ThreadPoolExecutor:線程池實現(xiàn)
三、最佳實踐
1. 線程池使用
合理配置線程池:
// 好的做法
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, // 核心線程數(shù)
maximumPoolSize, // 最大線程數(shù)
keepAliveTime, // 線程存活時間
TimeUnit.SECONDS, // 時間單位
new LinkedBlockingQueue<>(queueCapacity), // 任務(wù)隊列
new ThreadFactoryBuilder().setNameFormat("worker-%d").build(), // 線程工廠
new ThreadPoolExecutor.CallerRunsPolicy() // 拒絕策略
);
// 不好的做法
ExecutorService executor = Executors.newFixedThreadPool(10); // 可能導(dǎo)致 OOM
線程池監(jiān)控:
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("task-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}2. 鎖的使用
選擇合適的鎖:
- synchronized:適合簡單場景
- ReentrantLock:適合復(fù)雜場景,支持公平鎖和非公平鎖
- ReadWriteLock:適合讀多寫少的場景
- StampedLock:JDK 8+ 提供,性能更好
鎖優(yōu)化:
- 減少鎖的范圍:只鎖定必要的代碼塊
- 使用無鎖數(shù)據(jù)結(jié)構(gòu):如 Atomic 類
- 避免死鎖:合理安排鎖的獲取順序
示例:
// 好的做法
public class Counter {
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
// 不好的做法
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}3. 并發(fā)集合使用
選擇合適的集合:
| 場景 | 推薦集合 |
|---|---|
| 高并發(fā)讀寫 | ConcurrentHashMap |
| 讀多寫少 | CopyOnWriteArrayList |
| 生產(chǎn)者-消費者 | BlockingQueue |
| 線程安全隊列 | ConcurrentLinkedQueue |
示例:
// 高并發(fā)讀寫 private final Map<String, User> userMap = new ConcurrentHashMap<>(); // 讀多寫少 private final List<User> userList = new CopyOnWriteArrayList<>(); // 生產(chǎn)者-消費者 private final BlockingQueue<Message> messageQueue = new LinkedBlockingQueue<>();
4. 線程安全的單例模式
枚舉單例:
// 最佳實踐
public enum Singleton {
INSTANCE;
private final UserService userService;
Singleton() {
userService = new UserService();
}
public UserService getUserService() {
return userService;
}
}
// 使用
UserService userService = Singleton.INSTANCE.getUserService();雙重檢查鎖:
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}5. 原子操作
Atomic 類:
- AtomicInteger:原子整數(shù)
- AtomicLong:原子長整型
- AtomicReference:原子引用
- AtomicStampedReference:帶版本號的原子引用
示例:
// 原子更新 AtomicInteger counter = new AtomicInteger(0); // 遞增 int value = counter.incrementAndGet(); // 比較并交換 boolean updated = counter.compareAndSet(expectedValue, newValue);
6. 并發(fā)工具
CountDownLatch:
// 等待多個線程完成
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
executor.submit(() -> {
try {
// 執(zhí)行任務(wù)
} finally {
latch.countDown();
}
});
}
// 等待所有任務(wù)完成
latch.await();CyclicBarrier:
// 等待多個線程到達屏障
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("所有線程已到達屏障");
});
for (int i = 0; i < 3; i++) {
executor.submit(() -> {
try {
// 執(zhí)行任務(wù)
barrier.await();
// 繼續(xù)執(zhí)行
} catch (Exception e) {
e.printStackTrace();
}
});
}Semaphore:
// 控制并發(fā)訪問數(shù)
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
try {
semaphore.acquire();
try {
// 執(zhí)行任務(wù)
} finally {
semaphore.release();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}CompletableFuture:
// 異步執(zhí)行
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
// 執(zhí)行任務(wù)
return "Result 1";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
// 執(zhí)行任務(wù)
return "Result 2";
});
// 組合結(jié)果
CompletableFuture<String> combined = future1.thenCombine(future2, (result1, result2) -> {
return result1 + ", " + result2;
});
// 獲取結(jié)果
String result = combined.join();四、并發(fā)編程的挑戰(zhàn)
1. 死鎖
避免死鎖:
- 按固定順序獲取鎖
- 使用超時機制
- 使用 Lock 接口的 tryLock() 方法
示例:
// 好的做法:按固定順序獲取鎖
public void transferMoney(Account from, Account to, int amount) {
int fromHash = System.identityHashCode(from);
int toHash = System.identityHashCode(to);
if (fromHash < toHash) {
synchronized (from) {
synchronized (to) {
from.debit(amount);
to.credit(amount);
}
}
} else if (fromHash > toHash) {
synchronized (to) {
synchronized (from) {
from.debit(amount);
to.credit(amount);
}
}
} else {
// 使用額外的鎖
synchronized (lock) {
synchronized (from) {
synchronized (to) {
from.debit(amount);
to.credit(amount);
}
}
}
}
}2. 活鎖
避免活鎖:
- 引入隨機延遲
- 使用優(yōu)先級機制
- 避免無限重試
3. 饑餓
避免饑餓:
- 使用公平鎖
- 合理設(shè)置線程優(yōu)先級
- 避免長時間持有鎖
五、實戰(zhàn)案例
案例:電商系統(tǒng)庫存管理
需求:
- 高并發(fā)下的庫存管理
- 避免超賣
- 保證數(shù)據(jù)一致性
實現(xiàn):
public class InventoryService {
private final ConcurrentHashMap<String, AtomicInteger> inventory = new ConcurrentHashMap<>();
public boolean decreaseStock(String productId, int quantity) {
AtomicInteger stock = inventory.computeIfAbsent(productId, k -> new AtomicInteger(0));
while (true) {
int currentStock = stock.get();
if (currentStock < quantity) {
return false; // 庫存不足
}
if (stock.compareAndSet(currentStock, currentStock - quantity)) {
return true; // 成功減少庫存
}
// 競爭失敗,重試
}
}
public int getStock(String productId) {
AtomicInteger stock = inventory.get(productId);
return stock != null ? stock.get() : 0;
}
public void increaseStock(String productId, int quantity) {
AtomicInteger stock = inventory.computeIfAbsent(productId, k -> new AtomicInteger(0));
stock.addAndGet(quantity);
}
}六、總結(jié)
Java 并發(fā)編程是一個復(fù)雜但強大的工具,通過合理使用并發(fā)工具和最佳實踐,我們可以構(gòu)建高并發(fā)、可伸縮的應(yīng)用。在實踐中,我們需要根據(jù)具體場景選擇合適的并發(fā)策略,避免常見的并發(fā)問題,確保應(yīng)用的正確性和性能。
這其實可以更優(yōu)雅一點。
到此這篇關(guān)于Java并發(fā)編程最佳實踐之構(gòu)建高并發(fā)、可伸縮的應(yīng)用實例的文章就介紹到這了,更多相關(guān)Java構(gòu)建高并發(fā)、可伸縮內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于Spring Cloud 本地屬性覆蓋的問題
這篇文章主要介紹了關(guān)于Spring Cloud 本地屬性覆蓋的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03
RestTemplate get請求攜帶headers自動拼接參數(shù)方式
這篇文章主要介紹了RestTemplate get請求攜帶headers自動拼接參數(shù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-07-07
Java連接Sql數(shù)據(jù)庫經(jīng)常用到的操作
這篇文章主要介紹了Java連接Sql數(shù)據(jù)庫經(jīng)常用到的操作的相關(guān)資料,需要的朋友可以參考下2016-02-02
java中l(wèi)ist和數(shù)組互相轉(zhuǎn)換的一些方法總結(jié)
在日常的Java開發(fā)中經(jīng)常會遇到需要在數(shù)組和List之間進行轉(zhuǎn)換的情況,這篇文章主要給大家介紹了關(guān)于java中l(wèi)ist和數(shù)組互相轉(zhuǎn)換的一些方法,文中通過代碼介紹的非常詳細,需要的朋友可以參考下2024-12-12

