大年初二,朋友問了我一個技術的問題(朋友實在是好學,佩服!)
創(chuàng)新互聯(lián)是專業(yè)的網(wǎng)站建設公司,提供網(wǎng)站建設,網(wǎng)站制作,網(wǎng)站設計等網(wǎng)站開發(fā)一體化解決方案;包括html5,成都小程序開發(fā),網(wǎng)站定制,企業(yè)網(wǎng)站建設,購物商城網(wǎng)站建設,成都響應式網(wǎng)站建設公司,建網(wǎng)站,PHP網(wǎng)站建設,軟件開發(fā),軟文營銷,網(wǎng)站營銷。歡迎做網(wǎng)站的企業(yè)前來合作洽談,創(chuàng)新互聯(lián)將竭誠為您服務!開啟10000個線程,每個線程給員工表的money字段【初始值是0】加1,沒有使用悲觀鎖和樂觀鎖,但是在業(yè)務層方法上加了synchronized關鍵字,問題是代碼執(zhí)行完畢后數(shù)據(jù)庫中的money 字段不是10000,而是小于10000 問題出在哪里?
Service層代碼:
SQL代碼(沒有加悲觀/樂觀鎖):
用1000個線程跑代碼:
簡單來說:多線程跑一個使用synchronized關鍵字修飾的方法,方法內操作的是數(shù)據(jù)庫,按正常邏輯應該最終的值是1000,但經過多次測試,結果是低于1000。這是為什么呢?
一、我的思考
既然測試出來的結果是低于1000,那說明這段代碼不是線程安全的。不是線程安全的,那問題出現(xiàn)在哪呢?眾所周知,synchronized方法能夠保證所修飾的代碼塊、方法保證有序性、原子性、可見性。
講道理,以上的代碼跑起來,問題中Service層的increaseMoney()是有序的、原子的、可見的,所以斷定跟synchronized應該沒關系。既然Java層面上找不到原因,那分析一下數(shù)據(jù)庫層面的吧(因為方法內操作的是數(shù)據(jù)庫)。在increaseMoney()方法前加了@Transcational注解,說明這個方法是帶有事務的。事務能保證同組的SQL要么同時成功,要么同時失敗。講道理,如果沒有報錯的話,應該每個線程都對money值進行+1。從理論上來說,結果應該是1000的才對。
br/>既然Java層面上找不到原因,那分析一下數(shù)據(jù)庫層面的吧(因為方法內操作的是數(shù)據(jù)庫)。在increaseMoney()方法前加了@Transcational注解,說明這個方法是帶有事務的。事務能保證同組的SQL要么同時成功,要么同時失敗。講道理,如果沒有報錯的話,應該每個線程都對money值進行+1。從理論上來說,結果應該是1000的才對。
首先貼一下我的測試代碼:
@RestController
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@RequestMapping("/add")
public void addEmployee() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> employeeService.addEmployee()).start();
}
}
}
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Transactional
public synchronized void addEmployee() {
// 查出ID為8的記錄,然后每次將年齡增加一
Employee employee = employeeRepository.getOne(8);
System.out.println(employee);
Integer age = employee.getAge();
employee.setAge(age + 1);
employeeRepository.save(employee);
}
}
簡單地打印了每次拿到的employee值,并且拿到了SQL執(zhí)行的順序,如下(貼出小部分):
從打印的情況我們可以得出:多線程情況下并沒有串行執(zhí)行addEmployee()方法。這就導致對同一個值做重復的修改,所以最終的數(shù)值比1000要少。
二、圖解出現(xiàn)的原因
發(fā)現(xiàn)并不是同步執(zhí)行的,于是我就懷疑synchronized關鍵字和Spring肯定有點沖突。于是根據(jù)這兩個關鍵字搜了一下,找到了問題所在。
我們知道Spring事務的底層是Spring AOP,而Spring AOP的底層是動態(tài)代理技術。跟大家一起回顧一下動態(tài)代理:
public static void main(String[] args) {
// 目標對象
Object target ;
Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Main.class, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 但凡帶有@Transcational注解的方法都會被攔截
// 1... 開啟事務
method.invoke(target);
// 2... 提交事務
return null;
}
});
}
實際上Spring做的處理跟以上的思路是一樣的,我們可以看一下TransactionAspectSupport類中invokeWithinTransaction():
調用方法前開啟事務,調用方法后提交事務
在多線程環(huán)境下,就可能會出現(xiàn):方法執(zhí)行完了(synchronized代碼塊執(zhí)行完了),事務還沒提交,別的線程可以進入被synchronized修飾的方法,再讀取的時候,讀到的是還沒提交事務的數(shù)據(jù),這個數(shù)據(jù)不是最新的,所以就出現(xiàn)了這個問題。
三、解決問題
從上面我們可以發(fā)現(xiàn),問題所在是因為@Transcational注解和synchronized一起使用了,加鎖的范圍沒有包括到整個事務。所以我們可以這樣做:
新建一個名叫SynchronizedService類,讓其去調用addEmployee()方法,整個代碼如下:
@RestController
public class EmployeeController {
@Autowired
private SynchronizedService synchronizedService ;
@RequestMapping("/add")
public void addEmployee() {
for (int i = 0; i < 1000; i++) {
new Thread(() -> synchronizedService.synchronizedAddEmployee()).start();
}
}
}
// 新建的Service類
@Service
public class SynchronizedService {
@Autowired
private EmployeeService employeeService ;
// 同步
public synchronized void synchronizedAddEmployee() {
employeeService.addEmployee();
}
}
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Transactional
public void addEmployee() {
// 查出ID為8的記錄,然后每次將年齡增加一
Employee employee = employeeRepository.getOne(8);
System.out.println(Thread.currentThread().getName() + employee);
Integer age = employee.getAge();
employee.setAge(age + 1);
employeeRepository.save(employee);
}
}
我們將synchronized鎖的范圍包含到整個Spring事務上,這就不會出現(xiàn)線程安全的問題了。在測試的時候,我們可以發(fā)現(xiàn)1000個線程跑起來比之前要慢得多,當然我們的數(shù)據(jù)是正確的:
最后
可以發(fā)現(xiàn)的是,雖然說Spring事務用起來我們是非常方便的,但如果不了解一些Spring事務的細節(jié),很多時候出現(xiàn)Bug了就百思不得其解。還是得繼續(xù)加油努力呀~~~
另外有需要云服務器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內外云服務器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務器、裸金屬服務器、高防服務器、香港服務器、美國服務器、虛擬主機、免備案服務器”等云主機租用服務以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務可用性高、性價比高”等特點與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應用場景需求。