JVM本地锁失效的三种情况

# 多例模式

在SpringBoot中,默认的service层以及其他的层都是单例的,操作的都是同一个对象,我们可以自定义多例模式。在类上方加入以下注解即可。

@Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
1

我们可以查看ScopedProxyMode代理模式的源码:

public enum ScopedProxyMode {

	/**
	 * Default typically equals {@link #NO}, unless a different default
	 * has been configured at the component-scan instruction level.
	 */
	DEFAULT,

	/**
	 * Do not create a scoped proxy.
	 * <p>This proxy-mode is not typically useful when used with a
	 * non-singleton scoped instance, which should favor the use of the
	 * {@link #INTERFACES} or {@link #TARGET_CLASS} proxy-modes instead if it
	 * is to be used as a dependency.
	 */
	NO,

	/**
	 * Create a JDK dynamic proxy implementing <i>all</i> interfaces exposed by
	 * the class of the target object.
	 */
	INTERFACES,

	/**
	 * Create a class-based proxy (uses CGLIB).
	 */
	TARGET_CLASS

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

完整代码:

@Service
@Scope(value = "prototype",proxyMode = ScopedProxyMode.TARGET_CLASS)
public class StockService {

    private ReentrantLock lock = new ReentrantLock();

    @Autowired
    private StockMapper stockMapper;

    public void deduct() {
        lock.lock();
        try {
            QueryWrapper<Stock> stockQueryWrapper = new QueryWrapper<>();
            stockQueryWrapper.eq("product_code", "1001");
            Stock stock = stockMapper.selectOne(stockQueryWrapper);
            if (stock != null && stock.getCount() > 0) {
                stock.setCount(stock.getCount() - 1);
                stockMapper.updateById(stock);
            }
        } finally {
            lock.unlock();
        }

    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

Spring默认是JDK代理,SpringBoot2.X使用的是CGLIB代理

测试结果如下,出现并发问题

image-20221002213626064

# 事务

事务的存在也会让JVM本地锁失效

将代码加上事务注解:

@Transactional
    public void deduct() {
//        lock.lock();
        try {
            QueryWrapper<Stock> stockQueryWrapper = new QueryWrapper<>();
            stockQueryWrapper.eq("product_code", "1001");
            Stock stock = stockMapper.selectOne(stockQueryWrapper);
            if (stock != null && stock.getCount() > 0) {
                stock.setCount(stock.getCount() - 1);
                stockMapper.updateById(stock);
            }
        } finally {
//            lock.unlock();
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

测试结果如下,锁失效了

image-20221002214001003

# 分析

@Transactional 是AOP思想,步骤:
1.在前置通知内开启事务
2.获取锁
3.查询数据库
4.更新数据库
5.释放锁
6.提交/回滚事务
* 并发情况下:A事务释放锁但没有提交前,B事务进来了
1
2
3
4
5
6
7
8

# 集群

集群下,JVM本地锁也会失效,这里用Nginx做一个负载均衡,测试失效案例

nginx.conf配置如下:

upstream ideaProject {
		server localhost:9680;
		server localhost:9681;
	}

    server {
        listen       80;
        server_name  localhost;

        location / {
            proxy_pass http://ideaProject;
        }

        
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

经过压力测试,出现并发问题,证明本地锁失效了:

image-20221002231855403