这篇文章是一个初步了解分布式应用的线程安全和锁的文章,所有截图及代码全部来自亲身实践
1.对于单机应用我们可以把锁加在方法维度(有用,不推荐)
像这样
但是我们应该缩小锁的范围,我们这里是在派单,避免大于1个订单派给同一个司机,所以锁住司机即可:
注意这一句中的锁是加在司机的driverId转换为String之后进行intern操作的,这里就不得不看一下intern干了什么
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java™ Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/
public native String intern();
简单解释一下重点:
When the intern method is invoked, if the pool already contains a * string equal to this {@code String} object as determined by * the {@link #equals(Object)} method, then the string from the pool is * returned. Otherwise, this {@code String} object is added to the * pool and a reference to this {@code String} object is returned.
如果常量池中已经包含这个String了直接返回,如果不存在则添加返回这个常量的引用。
所以如果driverId已经被另外一个线程锁了,这里就无法再次锁定了
2.当我们有了多个服务器(或者测试的时候起了多个服务),这个时候我们原来加的锁就锁不住了,本地启动两个服务的情况我们发现两个订单派给了同一个司机,这是不对的
只能锁住那个服务内部或者服务器本身的jvm,也就是说我们原来的锁只能保证同一个服务中不会出现同一个资源被多个线程占用。这个时候我们就需要分布式的锁来解决问题,主流就是使用redis来锁
我们这里使用redisson
先添加maven依赖
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.17.7</version> </dependency>
然后创建Redisson的config类(用于创建RedissonClient)
这里使用单机的模式(生产上肯定使用集群)
使用10个线程压测,都执行成功
数据库里可以看到只有一个订单派单成功了
清除数据再来一次
都成功了之后我们看一下数据库
一样的结果
我这里至少测试了10遍,结果可以看到都是现成安全的。
那是不是真的打到了不同的服务器了呢(是不是分布式的线程安全),当然了
对于RedisConfig这个配置类的一些小的优化,主要是为了和其他的用到redis的地方保持一致,而不是上线或者测试的时候就需要修改代码,这也是为了安全,毕竟人都可能犯错,这里我们改成取yml文件中的配置
yml中的配置是这样的
代码写的比较粗鄙,大家参考这个思想即可,对于没有接触过的人来说,也算是一个可以参考的例子,面试也用的到