91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Synchronized鎖在Spring事務管理下,為啥還線程不安全?

發布時間:2020-07-15 02:44:35 來源:網絡 閱讀:556 作者:java架構師1 欄目:編程語言

大年初二,朋友問了我一個技術的問題(朋友實在是好學,佩服!)

開啟10000個線程,每個線程給員工表的money字段【初始值是0】加1,沒有使用悲觀鎖和樂觀鎖,但是在業務層方法上加了synchronized關鍵字,問題是代碼執行完畢后數據庫中的money 字段不是10000,而是小于10000 問題出在哪里?

Service層代碼:
Synchronized鎖在Spring事務管理下,為啥還線程不安全?
SQL代碼(沒有加悲觀/樂觀鎖):
Synchronized鎖在Spring事務管理下,為啥還線程不安全?
用1000個線程跑代碼:
Synchronized鎖在Spring事務管理下,為啥還線程不安全?
簡單來說:多線程跑一個使用synchronized關鍵字修飾的方法,方法內操作的是數據庫,按正常邏輯應該最終的值是1000,但經過多次測試,結果是低于1000。這是為什么呢?

一、我的思考
既然測試出來的結果是低于1000,那說明這段代碼不是線程安全的。不是線程安全的,那問題出現在哪呢?眾所周知,synchronized方法能夠保證所修飾的代碼塊、方法保證有序性、原子性、可見性。

講道理,以上的代碼跑起來,問題中Service層的increaseMoney()是有序的、原子的、可見的,所以斷定跟synchronized應該沒關系。既然Java層面上找不到原因,那分析一下數據庫層面的吧(因為方法內操作的是數據庫)。在increaseMoney()方法前加了@Transcational注解,說明這個方法是帶有事務的。事務能保證同組的SQL要么同時成功,要么同時失敗。講道理,如果沒有報錯的話,應該每個線程都對money值進行+1。從理論上來說,結果應該是1000的才對。
br/>既然Java層面上找不到原因,那分析一下數據庫層面的吧(因為方法內操作的是數據庫)。在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執行的順序,如下(貼出小部分):
Synchronized鎖在Spring事務管理下,為啥還線程不安全?
從打印的情況我們可以得出:多線程情況下并沒有串行執行addEmployee()方法。這就導致對同一個值做重復的修改,所以最終的數值比1000要少。

二、圖解出現的原因
發現并不是同步執行的,于是我就懷疑synchronized關鍵字和Spring肯定有點沖突。于是根據這兩個關鍵字搜了一下,找到了問題所在。

我們知道Spring事務的底層是Spring AOP,而Spring AOP的底層是動態代理技術。跟大家一起回顧一下動態代理:

  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():

Synchronized鎖在Spring事務管理下,為啥還線程不安全?
調用方法前開啟事務,調用方法后提交事務
Synchronized鎖在Spring事務管理下,為啥還線程不安全?
在多線程環境下,就可能會出現:方法執行完了(synchronized代碼塊執行完了),事務還沒提交,別的線程可以進入被synchronized修飾的方法,再讀取的時候,讀到的是還沒提交事務的數據,這個數據不是最新的,所以就出現了這個問題。

Synchronized鎖在Spring事務管理下,為啥還線程不安全?
三、解決問題
從上面我們可以發現,問題所在是因為@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事務上,這就不會出現線程安全的問題了。在測試的時候,我們可以發現1000個線程跑起來比之前要慢得多,當然我們的數據是正確的:
Synchronized鎖在Spring事務管理下,為啥還線程不安全?
最后
可以發現的是,雖然說Spring事務用起來我們是非常方便的,但如果不了解一些Spring事務的細節,很多時候出現Bug了就百思不得其解。還是得繼續加油努力呀~~~

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

上犹县| 普陀区| 江陵县| 肇州县| 绥芬河市| 胶州市| 商水县| 海盐县| 海林市| 德令哈市| 会宁县| 南汇区| 三台县| 灵璧县| 绿春县| 读书| 永泰县| 衡阳县| 沙雅县| 观塘区| 东乌| 乌鲁木齐县| 孝感市| 白沙| 仪陇县| 邻水| 越西县| 金乡县| 惠州市| 云和县| 星座| 乐至县| 方正县| 娄烦县| 正宁县| 神木县| 安溪县| 米易县| 玉环县| 新津县| 长丰县|