概括总结
在用到synchronized
关键字的时候,凭直觉就会加在方法上,比如public static synchronized void test(){}
,但是这种直觉不见得是对的,估计大部分时候是出图方便,想偷懒,才直接加到方法上的。推荐的做法是:仅仅只同步需要同步的代码。
009版本更新说明
这个版本主要是对比以下两种代码的性能:(代码为伪代码)
第一种:整个方法同步
public synchronized void submitOrderSynchronized(省略参数) {
// 1、查询用户
// 2、查询商品
// 3、查询地址
// 4、检查相关的参数是否正确
// 5、保存订单到数据库中,并返回订单ID
// 6、保存订单商品到数据库中
// 7、更新商品的库存与销量
}
第二种:方法中的部分代码同步
public void submitOrder(省略参数) {
// 1、查询用户
// 3、查询地址
synchronized (this) {
// 2、查询商品
// 4、检查全部相关的参数是否正确
// 7、更新商品的库存与销量
}
// 5、保存订单到数据库中,并返回订单ID
// 6、保存订单商品到数据库中
}
完整代码见Version009.java
测试结果
统计10次测试的平均值之后:
调用submitOrderSynchronized
提交订单方法时,每秒钟可以提交的订单数为:9
调用submitOrder
提交订单方法时,每秒钟可以提交的订单数为:14
调用submitOrderSynchronized
提交订单方法时,提交每个订单平均耗时的纳秒数:104506373
调用submitOrder
提交订单方法时,提交每个订单平均耗时的纳秒数:65949157
性能差别为:(104506373 - 65949157) / 104506373 * 100% = 36.89%,即第二种方法比第一种方法的性能好出36.89%
【备注】:不同的机器上的测试结果会不一样,以上测试结果仅供参考。
测试结果说明
首先要说明一个问题:为什么提交订单时需要同步?答案是:为了保证库存不会小于零。
在代码的第4步(检查相关的参数是否正确)中,会检查库存是否正确,如果库存已经为零,则不允许下单。这段代码的业务逻辑本身没有问题,但是如果没有同步,在多线程访问时,会出现以下情况:
线程A查询出商品,库存为1
线程B查询出商品,库存为1
线程A检查库存,发现大于零,于是提交订单,数据库中的库存变为0
线程B检查库存,发现大于零,于是提交订单,数据库中的库存变为-1
如果加上同步,则在多线程访问时,情况会变成这样:
线程A查询出商品,库存为1
线程A检查库存,发现大于零,于是提交订单,数据库中的库存变为0
线程B查询出商品,库存为0
线程B检查库存,发现等于零,不会提交订单
所以同步在这里起到的作用是:保证线程按我们期待的顺序来执行。
同步会降低性能,同步的范围越大,性能下降得越狠。第一种方案在方法上直接加同步,意味着方法中的7步操作都必须按顺序执行。而第二种方案中,只对7步中的其中3步加了同步,其他的4步可以并发执行,因此性能自然会好一些。
但是值得一提的是,这个例子仅仅只是为了说明整个方法同步
与方法中的部分代码同步
的区别,并没有完全按照概括总结中推荐的做法仅仅只同步需要同步的代码
来做,需要后期来完善。
源码
009版本的github源码在这里,
如果不知道怎样运行项目,请参考这里