Synchronized+Spring事務(wù) == 線程不安全??

Synchronized+Spring事務(wù) == 線程不安全??

某日進(jìn)行多線程實(shí)踐時(shí),突發(fā)奇想將@Transactional注解的spring事務(wù)方法用synchronized進(jìn)行了修飾。

那么理論上,事務(wù)方法指向時(shí)應(yīng)當(dāng)是線程安全的。然而在執(zhí)行如下方法時(shí),最終生成的訂單數(shù)>商品的剩余數(shù)量。

@Service
public class SecKillServiceImpl implements SecKillService {

    @Autowired
    SecKillMapper mapper;

    @Transactional
    @Override
    public synchronized void SecKillProduct(String productId) {
        //查詢剩余庫存
        Product product1 = mapper.queryProduceById(productId);
        if(product1.getStock()>0){
            product1.setStock(product1.getStock()-1);
            //更新庫存
            mapper.SecKillProduct(product1);
            //創(chuàng)建對(duì)應(yīng)訂單
            mapper.CreateOrder(product1.getProductId(), UUID.randomUUID().toString().substring(0, 25));

        }
    }
}

如上方法被synchronized修飾,那么多個(gè)不同的線程進(jìn)來不是應(yīng)該排隊(duì)串行執(zhí)行mapper方法?

結(jié)論顯然不是。

mappercontroller應(yīng)該也沒有問題

<mapper namespace="com.ed.concurrency.cdemo.mapper.SecKillMapper">
    <update id="SecKillProduct">
        update product_list
        set stock = #{stock}
        where id = #{productId}
    </update>

    <resultMap id="productMap" type="com.ed.concurrency.cdemo.POJO.Product">
        <result column="id" property="productId"/>
        <result column="stock" property="stock"/>
    </resultMap>
    <select id="queryProduceById" resultMap="productMap">
        select id, stock from unamed.product_list where id = #{id}
    </select>

    <insert id="CreateOrder">
        insert into unamed.order_list (produce_id, order_id) values (#{productId}, #{orderId})
    </insert>
</mapper>

Controller

@RestController
public class SecKillController {

    @Autowired
    SecKillService service;

    @RequestMapping("/seckill/{id}")
    public String SecKill(@PathVariable("id") String productId){
        try{
            service.SecKillProduct(productId);
        }catch (Exception e){
            e.printStackTrace();
            return "抱歉商品已經(jīng)售完";
        }
        return "商品秒殺成功";
    }

}

Synchronized應(yīng)該是安全的

@Transactional里面的syn并不能給整個(gè)事務(wù)加鎖

synchronized加在了這個(gè)方法上,但不是事務(wù)上。

為了說明這點(diǎn)需要先明白spring的事務(wù)是如何實(shí)現(xiàn)的。

Spring事務(wù)的底層是Spring AOP, 而Spring AOP的底層是動(dòng)態(tài)代理實(shí)現(xiàn)。

回顧如下代碼:

public static void main(String[] args) {

    // 目標(biāo)對(duì)象
    Object target ;

    Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), Main.class, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

            // 但凡帶有@Transcational注解的方法都會(huì)被攔截
            
            
            try{
                // 1... 開啟事務(wù)
                method.invoke(target);
                // 2... 提交事務(wù)
            }catch(Exception e){
                //異常處理
            }finally{
                //最終處理
            }
            return null;
        }
        
    });
}

不難發(fā)現(xiàn),我們所添加的synchronized關(guān)鍵字實(shí)際作用在method.invoke(target)這行代碼上。而整個(gè)動(dòng)態(tài)代理過程卻不是線程安全的。

  • 同時(shí),另外一個(gè)重要的點(diǎn)是:Spring的事務(wù)是執(zhí)行成功后,才一次性寫入到Mysql中(或其他數(shù)據(jù)庫)
  • 所以注意區(qū)分Spring中的事務(wù)和Mysql中的事務(wù),Spring的事務(wù)是完全在Spring中控制的,與Mysql的事務(wù)沒有任何關(guān)系

那么多線程下,不安全的情況就出現(xiàn)了:如果被事務(wù)修飾的方法執(zhí)行完了synchronized方法,但還未提交事務(wù)(也就是沒有寫入到數(shù)據(jù)庫中),而此時(shí)其他的線程進(jìn)入synchronized方法就獲取到了實(shí)際上已經(jīng)過時(shí)的數(shù)據(jù)。

問題如何解決

知道了聲明式事務(wù)修飾的方法是線程不安全的,那么在調(diào)用service出添加synchronized同步(也就是controller層)就可以解決這里線程不安全的問題。

@RequestMapping("/seckill/{id}")
public String SecKill(@PathVariable("id") String productId){
    try{
        synchronized (SecKillService.class){
            service.SecKillProduct(productId);
        }
    }catch (Exception e){
        e.printStackTrace();
        return "抱歉商品已經(jīng)售完";
    }
    return "商品秒殺成功";
}

當(dāng)然,正常的并發(fā)控制應(yīng)該不會(huì)這么解決。

奇怪的知識(shí)點(diǎn)增加了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容