前提
首先該場景是一個酒店開房的業務。為了朋友們閱讀簡單,我把業務都簡化了。
業務:開房後會新增一條賬單,新增一條房間排期記錄,房間排期主要是為了房間使用的時間不衝突。如:賬單A,使用房間1,使用時間段為2020-06-01 12:00 - 2020-06-02 12:00 ,那麼還需要使用房間1開房的時間段則不能與賬單A的時間段衝突。
業務類
為了簡單起見,我把幾個實體類都簡化了。
賬單類
public class Bill {
// 賬單號
private String serial;
// 房間排期id
private Integer room_schedule_id;
// ...get set
}
房間類
// 房間類
public class Room {
private Integer id;
// 房間名
private String name;
// get set...
}
房間排期類
import java.sql.Timestamp;
public class RoomSchedule {
private Integer id;
// 房間id
private Integer roomId;
// 開始時間
private Timestamp startTime;
// 結束時間
private Timestamp endTime;
// ...get set
}
實戰
併發實戰當然少不了Jmeter壓測工具,傳送門: https://jmeter.apache.org/download_jmeter.cgi
為了避免有些小夥伴訪問不到官網,我上傳到了百度雲:連結:https://pan.baidu.com/s/1c9l3Ri0KzkdIkef8qtKZeA
提取碼:kjh6
初次實戰(sychronized)
第一次進行併發實戰,我是首先想到sychronized
關鍵字的。沒辦法,基礎差。程式碼如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import java.sql.Timestamp;
/**
* 開房業務類
*/
@Service
public class OpenRoomService {
@Autowired
DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
TransactionDefinition transactionDefinition;
public void openRoom(Integer roomId, Timestamp startTime, Timestamp endTime) {
// 開啟事務
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
try {
synchronized (RoomSchedule.class) {
if (isConflict(roomId, startTime, endTime)) {
// throw exception
}
// 新增房間排期...
// 新增賬單
// 提交事務
dataSourceTransactionManager.commit(transaction);
}
} catch (Exception e) {
// 回滾事務
dataSourceTransactionManager.rollback(transaction);
throw e;
}
}
public boolean isConflict(Integer roomId, Timestamp startTime, Timestamp endTime) {
// 判斷房間排期是否有衝突...
}
}
sychronized(RoomSchedule.class)
,相當於的開房業務都是序列的。不管開房間1還是房間2。都需要等待上一個執行緒執行完開房業務,後續才能執行。這並不好哦。- 事務必須在同步程式碼塊
sychronized
中提交,這是必須的。否則當執行緒A使用房間1開房,同步程式碼塊執行完,事務還未提交,執行緒B發現房間1的房間排期沒有衝突,那麼此時是有問題的。
錯誤點: 有些朋友可能會想到都是序列執行了,為什麼不把synchronized
關鍵字寫到方法上?
首先openRoom
方法是非靜態方法,那麼synchronized
鎖定的就是this
物件。而Spring中的@Service
註解類是多例的,所以並不能把synchronized
關鍵字新增到方法上。
二次改進(等待-通知機制)
因為上面的例子當中,開房操作都是序列的。而實際情況使用房間1開房和房間2開房應該是可以並行才對。如果我們使用synchronized(Room例項)
可以嗎?答案是不行的。
在第三章 解決原子性問題當中,我講到了使用鎖必須是不可變物件,若把可變物件作為鎖,當可變物件被修改時相當於換鎖,這裡的鎖講的就是synchronized
鎖定的物件,也就是Room例項。因為Room例項是可變物件(set方法修改例項的屬性值,說明為可變物件),所以不能使用synchronized(Room例項)
。
在這次改進當中,我使用了第五章 等待-通知機制,我新增了RoomAllocator
房間資源分配器,當開房的時候需要在RoomAllocator
當中獲取鎖資源,獲取失敗則執行緒進入wait()
等待狀態。當執行緒釋放鎖資源則notiryAll()
喚醒所有等待中的執行緒。
RoomAllocator
房間資源分配器程式碼如下:
import java.util.ArrayList;
import java.util.List;
/**
* 房間資源分配器(單例類)
*/
public class RoomAllocator {
private final static RoomAllocator instance = new RoomAllocator();
private final List<Integer> lock = new ArrayList<>();
private RoomAllocator() {}
/**
* 獲取鎖資源
*/
public synchronized void lock(Integer roomId) throws InterruptedException {
// 是否有執行緒已佔用該房間資源
while (lock.contains(roomId)) {
// 執行緒等待
wait();
}
lock.add(roomId);
}
/**
* 釋放鎖資源
*/
public synchronized void unlock(Integer roomId) {
lock.remove(roomId);
// 喚醒所有執行緒
notifyAll();
}
public static RoomAllocator getInstance() {
return instance;
}
}
開房業務只需要修改openRoom的方法,修改如下:
public void openRoom(Integer roomId, Timestamp startTime, Timestamp endTime) throws InterruptedException {
RoomAllocator roomAllocator = RoomAllocator.getInstance();
// 開啟事務
TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
try {
roomAllocator.lock(roomId);
if (isConflict(roomId, startTime, endTime)) {
// throw exception
}
// 新增房間排期...
// 新增賬單
// 提交事務
dataSourceTransactionManager.commit(transaction);
} catch (Exception e) {
// 回滾事務
dataSourceTransactionManager.rollback(transaction);
throw e;
} finally {
roomAllocator.unlock(roomId);
}
}
那麼此次修改後,使用房間1開房和房間2開房就可以並行執行了。
總結
上面的例子可能會有其他更好的方法去解決,但是我的實力不允許我這麼做....。這個例子也是我自己在專案中搞事情搞出來的。畢竟沒有實戰經驗,只有理論,不足以學好併發。希望大家也可以在專案中搞事情[壞笑],當然不能瞎搞。
後續如果在其他場景用到了併發,也會繼續寫併發實戰的文章哦~
個人部落格網址: https://colablog.cn/
如果我的文章幫助到您,可以關注我的微信公眾號,第一時間分享文章給您