我有一個使用JPA休眠在由REST API接口的春天引導應用程序保存到DB會議類的數量有限,我有行動必須是性能問題線程安全。這是會議類:高效的線程安全的實現業務服務運營與執行
@Entity
public class Meeting {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
@ManyToOne(optional = false)
@JoinColumn(name = "account_id", nullable = false)
private Account account;
private Integer maxAttendees;
private Integer numAttendees; // current number of attendees
...
}
正如你可以看到我有一個帳戶實體,一個賬戶可以有相關的許多會議。會議擁有最大數量的與會者,並且帳戶擁有最大數量的預定會議,與帳戶具有maxSchedules和numSchedules變量類似。
的基本工作流程是:A會議創建的,那麼計劃,然後參加者單獨註冊。
注意:這裏的主要目標是避免超過允許的操作(調度或寄存器)的最大數量。
最初我更注重的是性能的業務邏輯,最初調度和登記參加我的商業服務是這樣的:
@Service
public class MeetingService {
...
@Transactional
public synchronized void scheduleMeeting(Long meetingId, Date meetingDate) {
Meeting meeting = repository.findById(meetingId);
Account account = meeting.getAccount();
if(account.getNumSchedules() + 1 <= account.getMaxSchedules()
&& meeting.getStatus() != SCHEDULED) {
meeting.setDate(meetingDate);
account.setNumSchedules(account.getNumSchedules()+1);
// save meeting and account here
}
else { throw new MaxSchedulesReachedException(); }
}
@Transactional
public synchronized void registerAttendee(Long meetingId, String name) {
Meeting meeting = repository.findById(meetingId);
if(meeting.getNumAttendees() + 1 <= meeting.getMaxAttendees()
&& meeting.getStatus() == SCHEDULED) {
meeting.setDate(meetingDate);
meeting.setNumAttendees(account.getNumAttendees()+1);
repository.save(meeting);
}
else { throw new NoMoreAttendeesException(); }
}
...
}
這種方法的問題是,同步方法鎖對象(this)中,服務是單例實例,所以當多個線程試圖執行兩個同步操作中的任何一個時,它們需要等待鎖釋放。
我來使用用於調度和登記分離鎖第二種方法:
...
private final Object scheduleLock = new Object();
private final Object registerLock = new Object();
...
@Transactional
public void scheduleMeeting(Long meetingId, Date meetingDate) {
synchronized (scheduleLock) {
Meeting meeting = repository.findById(meetingId);
Account account = meeting.getAccount();
if(account.getNumSchedules() + 1 <= account.getMaxSchedules()
&& meeting.getStatus() != SCHEDULED) {
meeting.setDate(meetingDate);
account.setNumSchedules(account.getNumSchedules()+1);
// save meeting and account here
}
else { throw new MaxSchedulesReachedException(); }
}
}
@Transactional
public void registerAttendee(Long meetingId, String name) {
synchronized (registerLock) {
Meeting meeting = repository.findById(meetingId);
if(meeting.getNumAttendees() + 1 <= meeting.getMaxAttendees()
&& meeting.getStatus() == SCHEDULED) {
meeting.setDate(meetingDate);
meeting.setNumAttendees(account.getNumAttendees()+1);
repository.save(meeting);
}
else { throw new NoMoreAttendeesException(); }
}
}
...
利用該予解決了幀間操作阻塞問題,這意味着,要登記的螺紋不應該被阻止由一個正在調度的線程。
現在的問題是,一個正在調度一個帳戶會議的線程不應該被嘗試從另一個帳戶安排會議的線程鎖定,同樣可以說註冊參加者到不同的會議在同一個帳戶。
用於固定,我來了,我還沒有實現的設想,但這個想法是有一個鎖定供應商,是這樣的:
@Component
public class LockProvider {
private final ConcurrentMap<String, Object> lockMap = new ConcurrentHashMap();
private Object addAccountLock(Long accountId) {
String key = makeAccountKey(accountId);
Object candidate = new Object();
Object existing = lockMap.putIfAbsent(key, candidate);
return (existing != null ? existing : candidate);
}
private Object addMeetingLock(Long accountId, Long meetingId) {
String key = makeMeetingKey(accountId, meetingId);
Object candidate = new Object();
Object existing = lockMap.putIfAbsent(key, candidate);
return (existing != null ? existing : candidate);
}
private String makeAccountKey(Long accountId) {
return "acc"+accountId.toString();
}
private String makeMeetingKey(Long accountId, Long meetingId) {
return "meet"+accountId.toString()+meetingId.toString();
}
public Object getAccountLock(Long accountId) {
return addAccountLock(accountId);
}
public Object getMeetingLock(Long accountId, Long meetingId) {
return addMeetingLock(accountId, meetingId);
}
}
但是這種方法涉及到很多額外的工作對維護例如,確保在賬戶,會議被刪除或者他們達到不能再完成同步操作的狀態時,確保不再使用鎖。
的問題或者是是否值得實施,如果有這樣做的更有效的方式。
回到起點,爲什麼'service'需要是單身人士? –
這是一個Spring bean,默認情況下它是一個單例,這使得框架更加高效,尤其是因爲這些服務與單身的其餘控制器連接在一起,這樣我們在運行大量併發服務時重用實例。但除此之外,即使服務不是單身,我們每次都有新的實例,但我不明白這是如何解決問題的。 – raspacorp
所有你的同步的東西是有缺陷的。您不需要同步服務的方法,因爲它可能發生(並且非常希望)可以同時安排不同的會議。與註冊參加者相同:您想讓不同的參與者同時註冊不同的會議。而你的同步方法恰恰避免了這一點。刪除所有這些東西並讓數據庫決定,理想情況下使用樂觀鎖定機制(即Hibernate默認實現一個)。 –